diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ea8d23b7..67b792f8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,87 @@ Changelog ========= +0.8.0 - `2020-11-13` +~~~~~~~~~~~~~~ + +This release comes after more than two years and contains many interesting new features and improvements. +In addition to multiple new plugins, such as ACME-http01, ADCS, PowerDNS, UltraDNS, Entrust, SNS, many of Lemur's existing +flows have improved. + +In the future, we plan to do frequent releases. + + +Summary of notable changes: + +- AWS S3 plugin: added delete, get methods, and support for uploading/deleting acme tokens +- ACME plugin: + - revamp of the plugin + - support for http01 domain validation, via S3 and SFTP as destination for the acme token + - support for CNAME delegated domain validation + - store-acme-account-details +- PowerDNS plugin +- UltraDNS plugin +- ADCS plugin +- SNS plugin +- Entrust plugin +- Rotation: + - respecting keyType and extensions + - region-by-region rotation option + - default to auto-rotate when cert attached to endpoint + - default to 1y validity during rotation for multi-year browser-trusted certs +- Certificate: search_by_name, and important performance improvements +- UI + - reducing the EC curve options to the relevant ones + - edit option for notifications, destinations and sources + - showing 13 month validity as default + - option to hide certs expired since 3month + - faster Permalink (no search involved) + - commonName Auto Added as DNS in the UI + - improved search and cert lookup +- celery tasks instead of crone, for better logging and monitoring +- countless bugfixes + - group-lookup-fix-referral + - url_context_path + - duplicate notification + - digicert-time-bug-fix + - improved-csr-support + - fix-cryptography-intermediate-ca + - enhanced logging + - vault-k8s-auth + - cfssl-key-fix + - cert-sync-endpoint-find-by-hash + - nlb-naming-bug + - fix_vault_api_v2_append + - aid_openid_roles_provider_integration + - rewrite-java-keystore-use-pyjks + - vault_kv2 + + +To see the full list of changes, you can run + + $ git log --merges --first-parent master --pretty=format:"%h %<(10,trunc)%aN %C(white)%<(15)%ar%Creset %C(red bold)%<(15)%D%Creset %s" | grep -v "depend" + + +Special thanks to all who contributed to this release, notably: + +- `peschmae `_ +- `sirferl `_ +- `lukasmrtvy `_ +- `intgr `_ +- `kush-bavishi `_ +- `alwaysjolley `_ +- `jplana `_ +- `explody `_ +- `titouanc `_ +- `jramosf `_ + + +Upgrading +--------- + +.. note:: This release will need a migration change. Please follow the `documentation `_ to upgrade Lemur. + + 0.7 - `2018-05-07` ~~~~~~~~~~~~~~ diff --git a/docs/doing-a-release.rst b/docs/doing-a-release.rst index 09848eb6..747668fb 100644 --- a/docs/doing-a-release.rst +++ b/docs/doing-a-release.rst @@ -11,22 +11,47 @@ software. * Update the version number in ``lemur/__about__.py``. * Set the release date in the :doc:`/changelog`. -* Do a commit indicating this. -* Send a pull request with this. +* Do a commit indicating this, and raise a pull request with this. * Wait for it to be merged. Performing the release ---------------------- The commit that merged the version number bump is now the official release -commit for this release. You will need to have ``gpg`` installed and a ``gpg`` -key in order to do a release. Once this has happened: +commit for this release. You need an `API key `_, +which requires permissions to maintain the Lemur `project `_. -* Run ``invoke release {version}``. +For creating the release, follow these steps (more details `here `_) -The release should now be available on PyPI and a tag should be available in +* Make sure you have the latest versions of setuptools and wheel installed: + + ``python3 -m pip install --user --upgrade setuptools wheel`` + +* Now run this command from the same directory where setup.py is located: + + ``python3 setup.py sdist bdist_wheel`` + +* Once completed it should generate two files in the dist directory: + +.. code-block:: pycon + + $ ls dist/ + lemur-0.8.0-py2.py3-none-any.whl lemur-0.8.0.tar.gz + + +* In this step, the distribution will be uploaded. You’ll need to install Twine: + + ``python3 -m pip install --user --upgrade twine`` + +* Once installed, run Twine to upload all of the archives under dist. Once installed, run Twine to upload all of the archives under dist: + + ``python3 -m twine upload --repository pypi dist/*`` + +The release should now be available on `PyPI Lemur `_ and a tag should be available in the repository. +Make sure to also make a github `release `_ which will pick up the latest version. + Verifying the release --------------------- diff --git a/docs/production/index.rst b/docs/production/index.rst index c6f561ca..6b01e951 100644 --- a/docs/production/index.rst +++ b/docs/production/index.rst @@ -415,8 +415,8 @@ And the worker can be started with desired options such as the following:: supervisor or systemd configurations should be created for these in production environments as appropriate. -Add support for LetsEncrypt -=========================== +Add support for LetsEncrypt/ACME +================================ LetsEncrypt is a free, limited-feature certificate authority that offers publicly trusted certificates that are valid for 90 days. LetsEncrypt does not use organizational validation (OV), and instead relies on domain validation (DV). @@ -424,7 +424,10 @@ LetsEncrypt requires that we prove ownership of a domain before we're able to is time we want a certificate. The most common methods to prove ownership are HTTP validation and DNS validation. Lemur supports DNS validation -through the creation of DNS TXT records. +through the creation of DNS TXT records as well as HTTP validation, reusing the destination concept. + +ACME DNS Challenge +------------------ In a nutshell, when we send a certificate request to LetsEncrypt, they generate a random token and ask us to put that token in a DNS text record to prove ownership of a domain. If a certificate request has multiple domains, we must @@ -462,6 +465,24 @@ possible. To enable this functionality, periodically (or through Cron/Celery) ru This command will traverse all DNS providers, determine which zones they control, and upload this list of zones to Lemur's database (in the dns_providers table). Alternatively, you can manually input this data. +ACME HTTP Challenge +------------------- + +The flow for requesting a certificate using the HTTP challenge is not that different from the one described for the DNS +challenge. The only difference is, that instead of creating a DNS TXT record, a file is uploaded to a Webserver which +serves the file at `http:///.well-known/acme-challenge/` + +Currently the HTTP challenge also works without Celery, since it's done while creating the certificate, and doesn't +rely on celery to create the DNS record. This will change when we implement mix & match of acme challenge types. + +To create a HTTP compatible Authority, you first need to create a new destination that will be used to deploy the +challenge token. Visit `Admin` -> `Destination` and click `Create`. The path you provide for the destination needs to +be the exact path that is called when the ACME providers calls ``http:///.well-known/acme-challenge/`. The +token part will be added dynamically by the acme_upload. +Currently only the SFTP and S3 Bucket destination support the ACME HTTP challenge. + +Afterwards you can create a new certificate authority as described in the DNS challenge, but need to choose +`Acme HTTP-01` as the plugin type, and then the destination you created beforehand. LetsEncrypt: pinning to cross-signed ICA ---------------------------------------- diff --git a/lemur/__about__.py b/lemur/__about__.py index 766d3668..0926ef33 100644 --- a/lemur/__about__.py +++ b/lemur/__about__.py @@ -15,7 +15,7 @@ __title__ = "lemur" __summary__ = "Certificate management and orchestration service" __uri__ = "https://github.com/Netflix/lemur" -__version__ = "0.7.0" +__version__ = "0.8.0" __author__ = "The Lemur developers" __email__ = "security@netflix.com" diff --git a/lemur/notifications/messaging.py b/lemur/notifications/messaging.py index 75d227b1..2658e1a0 100644 --- a/lemur/notifications/messaging.py +++ b/lemur/notifications/messaging.py @@ -103,8 +103,9 @@ def send_plugin_notification(event_type, data, recipients, notification): function = f"{__name__}.{sys._getframe().f_code.co_name}" log_data = { "function": function, - "message": f"Sending expiration notification for to recipients {recipients}", - "notification_type": "expiration", + "message": f"Sending {event_type} notification for to recipients {recipients}", + "notification_type": event_type, + "notification_plugin": notification.plugin.slug, "certificate_targets": recipients, } status = FAILURE_METRIC_STATUS @@ -121,7 +122,7 @@ def send_plugin_notification(event_type, data, recipients, notification): "notification", "counter", 1, - metric_tags={"status": status, "event_type": event_type}, + metric_tags={"status": status, "event_type": event_type, "plugin": notification.plugin.slug}, ) if status == SUCCESS_METRIC_STATUS: @@ -142,7 +143,6 @@ def send_expiration_notifications(exclude): for notification_label, certificates in notification_group.items(): notification_data = [] - security_data = [] notification = certificates[0][0] @@ -152,33 +152,26 @@ def send_expiration_notifications(exclude): certificate ).data notification_data.append(cert_data) - security_data.append(cert_data) - - if send_default_notification( - "expiration", notification_data, [owner], notification.options - ): - success += 1 - else: - failure += 1 - - recipients = notification.plugin.filter_recipients(notification.options, security_email + [owner]) + email_recipients = notification.plugin.get_recipients(notification.options, security_email + [owner]) + # Plugin will ONLY use the provided recipients if it's email; any other notification plugin ignores them if send_plugin_notification( - "expiration", - notification_data, - recipients, - notification, + "expiration", notification_data, email_recipients, notification ): - success += 1 + success += len(email_recipients) else: - failure += 1 - - if send_default_notification( - "expiration", security_data, security_email, notification.options - ): - success += 1 - else: - failure += 1 + failure += len(email_recipients) + # If we're using an email plugin, we're done, + # since "security_email + [owner]" were added as email_recipients. + # If we're not using an email plugin, we also need to send an email to the security team and owner, + # since the plugin notification didn't send anything to them. + if notification.plugin.slug != "email-notification": + if send_default_notification( + "expiration", notification_data, email_recipients, notification.options + ): + success = 1 + len(email_recipients) + else: + failure = 1 + len(email_recipients) return success, failure @@ -195,15 +188,16 @@ def send_default_notification(notification_type, data, targets, notification_opt :return: """ function = f"{__name__}.{sys._getframe().f_code.co_name}" - log_data = { - "function": function, - "message": f"Sending notification for certificate data {data}", - "notification_type": notification_type, - } status = FAILURE_METRIC_STATUS notification_plugin = plugins.get( current_app.config.get("LEMUR_DEFAULT_NOTIFICATION_PLUGIN", "email-notification") ) + log_data = { + "function": function, + "message": f"Sending {notification_type} notification for certificate data {data} to targets {targets}", + "notification_type": notification_type, + "notification_plugin": notification_plugin.slug, + } try: current_app.logger.debug(log_data) @@ -212,7 +206,7 @@ def send_default_notification(notification_type, data, targets, notification_opt status = SUCCESS_METRIC_STATUS except Exception as e: log_data["message"] = f"Unable to send {notification_type} notification for certificate data {data} " \ - f"to target {targets}" + f"to targets {targets}" current_app.logger.error(log_data, exc_info=True) sentry.captureException() @@ -220,7 +214,7 @@ def send_default_notification(notification_type, data, targets, notification_opt "notification", "counter", 1, - metric_tags={"status": status, "event_type": notification_type}, + metric_tags={"status": status, "event_type": notification_type, "plugin": notification_plugin.slug}, ) if status == SUCCESS_METRIC_STATUS: @@ -247,15 +241,14 @@ def send_pending_failure_notification( data = pending_certificate_output_schema.dump(pending_cert).data data["security_email"] = current_app.config.get("LEMUR_SECURITY_TEAM_EMAIL") - notify_owner_success = False + email_recipients = [] if notify_owner: - notify_owner_success = send_default_notification("failed", data, [data["owner"]], pending_cert) + email_recipients = email_recipients + [data["owner"]] - notify_security_success = False if notify_security: - notify_security_success = send_default_notification("failed", data, data["security_email"], pending_cert) + email_recipients = email_recipients + data["security_email"] - return notify_owner_success or notify_security_success + return send_default_notification("failed", data, email_recipients, pending_cert) def needs_notification(certificate): diff --git a/lemur/plugins/bases/notification.py b/lemur/plugins/bases/notification.py index 76aa33de..03de95ce 100644 --- a/lemur/plugins/bases/notification.py +++ b/lemur/plugins/bases/notification.py @@ -20,14 +20,14 @@ class NotificationPlugin(Plugin): def send(self, notification_type, message, targets, options, **kwargs): raise NotImplementedError - def filter_recipients(self, options, excluded_recipients): + def get_recipients(self, options, additional_recipients): """ - Given a set of options (which should include configured recipient info), filters out recipients that - we do NOT want to notify. + Given a set of options (which should include configured recipient info), returns the parsed list of recipients + from those options plus the additional recipients specified. The returned value has no duplicates. - For any notification types where recipients can't be dynamically modified, this returns an empty list. + For any notification types where recipients can't be dynamically modified, this returns only the additional recipients. """ - return [] + return additional_recipients class ExpirationNotificationPlugin(NotificationPlugin): diff --git a/lemur/plugins/lemur_acme/acme_handlers.py b/lemur/plugins/lemur_acme/acme_handlers.py index c1ab5281..55e4a076 100644 --- a/lemur/plugins/lemur_acme/acme_handlers.py +++ b/lemur/plugins/lemur_acme/acme_handlers.py @@ -224,7 +224,7 @@ class AcmeHandler(object): def revoke_certificate(self, certificate): if not self.reuse_account(certificate.authority): raise InvalidConfiguration("There is no ACME account saved, unable to revoke the certificate.") - acme_client, _ = self.acme.setup_acme_client(certificate.authority) + acme_client, _ = self.setup_acme_client(certificate.authority) fullchain_com = jose.ComparableX509( OpenSSL.crypto.load_certificate( diff --git a/lemur/plugins/lemur_aws/plugin.py b/lemur/plugins/lemur_aws/plugin.py index b54787ac..fcc2e0cf 100644 --- a/lemur/plugins/lemur_aws/plugin.py +++ b/lemur/plugins/lemur_aws/plugin.py @@ -419,7 +419,7 @@ class S3DestinationPlugin(ExportDestinationPlugin): :param kwargs: :return: """ - current_app.logger.debug("S3 destination plugin is started for HTTP-01 challenge") + current_app.logger.debug("S3 destination plugin is started to upload HTTP-01 challenge") function = f"{__name__}.{sys._getframe().f_code.co_name}" @@ -431,16 +431,16 @@ class S3DestinationPlugin(ExportDestinationPlugin): if not prefix.endswith("/"): prefix + "/" - res = s3.put(bucket_name=bucket_name, - region_name=region, - prefix=prefix + filename, - data=token, - encrypt=False, - account_number=account_number) - res = "Success" if res else "Failure" + response = s3.put(bucket_name=bucket_name, + region_name=region, + prefix=prefix + filename, + data=token, + encrypt=False, + account_number=account_number) + res = "Success" if response else "Failure" log_data = { "function": function, - "message": "check if any valid certificate is revoked", + "message": "upload acme token challenge", "result": res, "bucket_name": bucket_name, "filename": filename @@ -449,6 +449,34 @@ class S3DestinationPlugin(ExportDestinationPlugin): metrics.send(f"{function}", "counter", 1, metric_tags={"result": res, "bucket_name": bucket_name, "filename": filename}) + return response + + def delete_acme_token(self, token_path, options, **kwargs): + + current_app.logger.debug("S3 destination plugin is started to delete HTTP-01 challenge") + + function = f"{__name__}.{sys._getframe().f_code.co_name}" + + account_number = self.get_option("accountNumber", options) + bucket_name = self.get_option("bucket", options) + prefix = self.get_option("prefix", options) + filename = token_path.split("/")[-1] + response = s3.delete(bucket_name=bucket_name, + prefixed_object_name=prefix + filename, + account_number=account_number) + res = "Success" if response else "Failure" + log_data = { + "function": function, + "message": "delete acme token challenge", + "result": res, + "bucket_name": bucket_name, + "filename": filename + } + current_app.logger.info(log_data) + metrics.send(f"{function}", "counter", 1, metric_tags={"result": res, + "bucket_name": bucket_name, + "filename": filename}) + return response class SNSNotificationPlugin(ExpirationNotificationPlugin): diff --git a/lemur/plugins/lemur_aws/tests/test_plugin.py b/lemur/plugins/lemur_aws/tests/test_plugin.py index be9b14fd..e032cf02 100644 --- a/lemur/plugins/lemur_aws/tests/test_plugin.py +++ b/lemur/plugins/lemur_aws/tests/test_plugin.py @@ -68,10 +68,11 @@ def test_upload_acme_token(app): s3_client.create_bucket(Bucket=bucket) p = plugins.get("aws-s3") - p.upload_acme_token(token_path=token_path, - token_content=token_content, - token=token_content, - options=additional_options) + response = p.upload_acme_token(token_path=token_path, + token_content=token_content, + token=token_content, + options=additional_options) + assert response response = get(bucket_name=bucket, prefixed_object_name=prefix + token_name, @@ -80,3 +81,8 @@ def test_upload_acme_token(app): # put data, and getting the same data assert (response == token_content) + + response = p.delete_acme_token(token_path=token_path, + options=additional_options, + account_number=account) + assert response diff --git a/lemur/plugins/lemur_email/plugin.py b/lemur/plugins/lemur_email/plugin.py index 041b27ec..214586ab 100644 --- a/lemur/plugins/lemur_email/plugin.py +++ b/lemur/plugins/lemur_email/plugin.py @@ -105,6 +105,8 @@ class EmailNotificationPlugin(ExpirationNotificationPlugin): @staticmethod def send(notification_type, message, targets, options, **kwargs): + if not targets: + return subject = "Lemur: {0} Notification".format(notification_type.capitalize()) @@ -119,11 +121,9 @@ class EmailNotificationPlugin(ExpirationNotificationPlugin): send_via_smtp(subject, body, targets) @staticmethod - def filter_recipients(options, excluded_recipients, **kwargs): + def get_recipients(options, additional_recipients, **kwargs): notification_recipients = get_plugin_option("recipients", options) if notification_recipients: notification_recipients = notification_recipients.split(",") - # removing owner and security_email from notification_recipient - notification_recipients = [i for i in notification_recipients if i not in excluded_recipients] - return notification_recipients + return list(set(notification_recipients + additional_recipients)) diff --git a/lemur/plugins/lemur_email/tests/test_email.py b/lemur/plugins/lemur_email/tests/test_email.py index fd4dc575..3522f21c 100644 --- a/lemur/plugins/lemur_email/tests/test_email.py +++ b/lemur/plugins/lemur_email/tests/test_email.py @@ -21,7 +21,6 @@ def get_options(): def test_render_expiration(certificate, endpoint): - new_cert = CertificateFactory() new_cert.replaces.append(certificate) @@ -54,7 +53,7 @@ def test_send_expiration_notification(): certificate.notifications[0].options = get_options() verify_sender_email() - assert send_expiration_notifications([]) == (3, 0) # owner, recipients (only counted as 1), and security + assert send_expiration_notifications([]) == (4, 0) # owner (1), recipients (2), and security (1) @mock_ses @@ -76,15 +75,20 @@ def test_send_pending_failure_notification(user, pending_certificate, async_issu verify_sender_email() assert send_pending_failure_notification(pending_certificate) + assert send_pending_failure_notification(pending_certificate, True, True) + assert send_pending_failure_notification(pending_certificate, True, False) + assert send_pending_failure_notification(pending_certificate, False, True) + assert send_pending_failure_notification(pending_certificate, False, False) -def test_filter_recipients(certificate, endpoint): +def test_get_recipients(certificate, endpoint): from lemur.plugins.lemur_email.plugin import EmailNotificationPlugin - options = [{"name": "recipients", "value": "security@example.com,bob@example.com,joe@example.com"}] - assert EmailNotificationPlugin.filter_recipients(options, []) == ["security@example.com", "bob@example.com", - "joe@example.com"] - assert EmailNotificationPlugin.filter_recipients(options, ["security@example.com"]) == ["bob@example.com", - "joe@example.com"] - assert EmailNotificationPlugin.filter_recipients(options, ["security@example.com", "bob@example.com", - "joe@example.com"]) == [] + options = [{"name": "recipients", "value": "security@example.com,joe@example.com"}] + two_emails = sorted(["security@example.com", "joe@example.com"]) + assert sorted(EmailNotificationPlugin.get_recipients(options, [])) == two_emails + assert sorted(EmailNotificationPlugin.get_recipients(options, ["security@example.com"])) == two_emails + three_emails = sorted(["security@example.com", "bob@example.com", "joe@example.com"]) + assert sorted(EmailNotificationPlugin.get_recipients(options, ["bob@example.com"])) == three_emails + assert sorted(EmailNotificationPlugin.get_recipients(options, ["security@example.com", "bob@example.com", + "joe@example.com"])) == three_emails diff --git a/lemur/plugins/lemur_entrust/plugin.py b/lemur/plugins/lemur_entrust/plugin.py index d3324db0..924345eb 100644 --- a/lemur/plugins/lemur_entrust/plugin.py +++ b/lemur/plugins/lemur_entrust/plugin.py @@ -45,7 +45,7 @@ def determine_end_date(end_date): return end_date.format('YYYY-MM-DD') -def process_options(options): +def process_options(options, client_id): """ Processes and maps the incoming issuer options to fields/options that Entrust understands @@ -78,11 +78,37 @@ def process_options(options): "eku": "SERVER_AND_CLIENT_AUTH", "certType": product_type, "certExpiryDate": validity_end, - "tracking": tracking_data + # "keyType": "RSA", Entrust complaining about this parameter + "tracking": tracking_data, + "org": options.get("organization"), + "clientId": client_id } return data +def get_client_id(my_response, organization): + """ + Helper function for parsing responses from the Entrust API. + :param content: + :return: :raise Exception: + """ + try: + d = json.loads(my_response.content) + except ValueError: + # catch an empty json object here + d = {'response': 'No detailed message'} + + found = False + for y in d["organizations"]: + if y["name"] == organization: + found = True + client_id = y["clientId"] + if found: + return client_id + else: + raise Exception(f"Error on Organization - Use on of the List: {d['organizations']}") + + def handle_response(my_response): """ Helper function for parsing responses from the Entrust API. @@ -192,9 +218,25 @@ class EntrustIssuerPlugin(IssuerPlugin): } current_app.logger.info(log_data) + # firstly we need the organization ID + url = current_app.config.get("ENTRUST_URL") + "/organizations" + try: + response = self.session.get(url, timeout=(15, 40)) + except requests.exceptions.Timeout: + raise Exception("Timeout for Getting Organizations") + except requests.exceptions.RequestException as e: + raise Exception(f"Error for Getting Organization {e}") + + client_id = get_client_id(response, issuer_options.get("organization")) + log_data = { + "function": f"{__name__}.{sys._getframe().f_code.co_name}", + "message": f"Organization id: {client_id}" + } + current_app.logger.info(log_data) + url = current_app.config.get("ENTRUST_URL") + "/certificates" - data = process_options(issuer_options) + data = process_options(issuer_options, client_id) data["csr"] = csr response_dict = order_and_download_certificate(self.session, url, data) @@ -202,7 +244,7 @@ class EntrustIssuerPlugin(IssuerPlugin): external_id = response_dict['trackingId'] cert = response_dict['endEntityCert'] if len(response_dict['chainCerts']) < 2: - # certificate signed by CA directly, no ICA included ini the chain + # certificate signed by CA directly, no ICA included in the chain chain = None else: chain = response_dict['chainCerts'][1] diff --git a/lemur/plugins/lemur_entrust/tests/test_entrust.py b/lemur/plugins/lemur_entrust/tests/test_entrust.py index 354e204e..2c501581 100644 --- a/lemur/plugins/lemur_entrust/tests/test_entrust.py +++ b/lemur/plugins/lemur_entrust/tests/test_entrust.py @@ -56,7 +56,10 @@ def test_process_options(mock_current_app, authority): "requesterName": mock_current_app.config.get("ENTRUST_NAME"), "requesterEmail": mock_current_app.config.get("ENTRUST_EMAIL"), "requesterPhone": mock_current_app.config.get("ENTRUST_PHONE") - } + }, + "org": "Example, Inc.", + "clientId": 1 } - assert expected == plugin.process_options(options) + client_id = 1 + assert expected == plugin.process_options(options, client_id) diff --git a/requirements-dev.txt b/requirements-dev.txt index e2eb7051..adc8304b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -24,7 +24,7 @@ keyring==21.2.0 # via twine mccabe==0.6.1 # via flake8 nodeenv==1.5.0 # via -r requirements-dev.in, pre-commit pkginfo==1.5.0.1 # via twine -pre-commit==2.8.2 # via -r requirements-dev.in +pre-commit==2.9.0 # via -r requirements-dev.in pycodestyle==2.6.0 # via flake8 pycparser==2.20 # via cffi pyflakes==2.2.0 # via flake8 @@ -32,7 +32,7 @@ pygments==2.6.1 # via readme-renderer pyyaml==5.3.1 # via -r requirements-dev.in, pre-commit readme-renderer==25.0 # via twine requests-toolbelt==0.9.1 # via twine -requests==2.24.0 # via requests-toolbelt, twine +requests==2.25.0 # via requests-toolbelt, twine rfc3986==1.4.0 # via twine secretstorage==3.1.2 # via keyring six==1.15.0 # via bleach, cryptography, readme-renderer, virtualenv diff --git a/requirements-docs.txt b/requirements-docs.txt index 1fcf06ab..0642dce7 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -17,8 +17,8 @@ bcrypt==3.1.7 # via -r requirements.txt, flask-bcrypt, paramiko beautifulsoup4==4.9.1 # via -r requirements.txt, cloudflare billiard==3.6.3.0 # via -r requirements.txt, celery blinker==1.4 # via -r requirements.txt, flask-mail, flask-principal, raven -boto3==1.16.14 # via -r requirements.txt -botocore==1.19.14 # via -r requirements.txt, boto3, s3transfer +boto3==1.16.24 # via -r requirements.txt +botocore==1.19.24 # via -r requirements.txt, boto3, s3transfer celery[redis]==4.4.2 # via -r requirements.txt certifi==2020.11.8 # via -r requirements.txt, requests certsrv==2.1.1 # via -r requirements.txt @@ -85,14 +85,14 @@ pyyaml==5.3.1 # via -r requirements.txt, cloudflare raven[flask]==6.10.0 # via -r requirements.txt redis==3.5.3 # via -r requirements.txt, celery requests-toolbelt==0.9.1 # via -r requirements.txt, acme -requests[security]==2.24.0 # via -r requirements.txt, acme, certsrv, cloudflare, hvac, requests-toolbelt, sphinx +requests[security]==2.25.0 # via -r requirements.txt, acme, certsrv, cloudflare, hvac, requests-toolbelt, sphinx retrying==1.3.3 # via -r requirements.txt s3transfer==0.3.3 # via -r requirements.txt, boto3 six==1.15.0 # via -r requirements.txt, acme, bcrypt, cryptography, flask-cors, flask-restful, hvac, josepy, jsonlines, packaging, pynacl, pyopenssl, python-dateutil, retrying, sphinxcontrib-httpdomain, sqlalchemy-utils snowballstemmer==2.0.0 # via sphinx soupsieve==2.0.1 # via -r requirements.txt, beautifulsoup4 sphinx-rtd-theme==0.5.0 # via -r requirements-docs.in -sphinx==3.3.0 # via -r requirements-docs.in, sphinx-rtd-theme, sphinxcontrib-httpdomain +sphinx==3.3.1 # via -r requirements-docs.in, sphinx-rtd-theme, sphinxcontrib-httpdomain sphinxcontrib-applehelp==1.0.2 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx sphinxcontrib-htmlhelp==1.0.3 # via sphinx diff --git a/requirements-tests.txt b/requirements-tests.txt index b82e2ac8..4fd96f95 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -10,9 +10,9 @@ aws-sam-translator==1.22.0 # via cfn-lint aws-xray-sdk==2.5.0 # via moto bandit==1.6.2 # via -r requirements-tests.in black==20.8b1 # via -r requirements-tests.in -boto3==1.16.14 # via aws-sam-translator, moto +boto3==1.16.24 # via aws-sam-translator, moto boto==2.49.0 # via moto -botocore==1.19.14 # via aws-xray-sdk, boto3, moto, s3transfer +botocore==1.19.24 # via aws-xray-sdk, boto3, moto, s3transfer certifi==2020.11.8 # via requests cffi==1.14.0 # via cryptography cfn-lint==0.29.5 # via moto @@ -24,7 +24,7 @@ decorator==4.4.2 # via networkx docker==4.2.0 # via moto ecdsa==0.14.1 # via moto, python-jose, sshpubkeys factory-boy==3.1.0 # via -r requirements-tests.in -faker==4.14.2 # via -r requirements-tests.in, factory-boy +faker==4.17.1 # via -r requirements-tests.in, factory-boy fakeredis==1.4.4 # via -r requirements-tests.in flask==1.1.2 # via pytest-flask freezegun==1.0.0 # via -r requirements-tests.in @@ -69,7 +69,7 @@ pyyaml==5.3.1 # via -r requirements-tests.in, bandit, cfn-lint, moto redis==3.5.3 # via fakeredis regex==2020.4.4 # via black requests-mock==1.8.0 # via -r requirements-tests.in -requests==2.24.0 # via docker, moto, requests-mock, responses +requests==2.25.0 # via docker, moto, requests-mock, responses responses==0.10.12 # via moto rsa==4.0 # via python-jose s3transfer==0.3.3 # via boto3 diff --git a/requirements.txt b/requirements.txt index d7b56f2b..e029b61c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,8 +15,8 @@ bcrypt==3.1.7 # via flask-bcrypt, paramiko beautifulsoup4==4.9.1 # via cloudflare billiard==3.6.3.0 # via celery blinker==1.4 # via flask-mail, flask-principal, raven -boto3==1.16.14 # via -r requirements.in -botocore==1.19.14 # via -r requirements.in, boto3, s3transfer +boto3==1.16.24 # via -r requirements.in +botocore==1.19.24 # via -r requirements.in, boto3, s3transfer celery[redis]==4.4.2 # via -r requirements.in certifi==2020.11.8 # via -r requirements.in, requests certsrv==2.1.1 # via -r requirements.in @@ -78,7 +78,7 @@ pyyaml==5.3.1 # via -r requirements.in, cloudflare raven[flask]==6.10.0 # via -r requirements.in redis==3.5.3 # via -r requirements.in, celery requests-toolbelt==0.9.1 # via acme -requests[security]==2.24.0 # via -r requirements.in, acme, certsrv, cloudflare, hvac, requests-toolbelt +requests[security]==2.25.0 # via -r requirements.in, acme, certsrv, cloudflare, hvac, requests-toolbelt retrying==1.3.3 # via -r requirements.in s3transfer==0.3.3 # via boto3 six==1.15.0 # via -r requirements.in, acme, bcrypt, cryptography, flask-cors, flask-restful, hvac, josepy, jsonlines, pynacl, pyopenssl, python-dateutil, retrying, sqlalchemy-utils