Merge branch 'master' into stats_whitelist_01

This commit is contained in:
Chad S 2021-03-17 11:06:24 -07:00 committed by GitHub
commit f7938bf226
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 1952 additions and 281 deletions

15
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,15 @@
version: 2
updates:
- directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "08:00"
timezone: "America/Los_Angeles"
package-ecosystem: "pip"
reviewers:
- "hosseinsh"
- "csine-nflx"
- "charhate"
- "jtschladen"
versioning-strategy: lockfile-only

View File

@ -0,0 +1,14 @@
name: dependabot-auto-merge
on:
pull_request:
jobs:
auto-merge:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ahmadnassri/action-dependabot-auto-merge@v2
with:
target: minor
github-token: ${{ secrets.DEPENDABOT_GITHUB_TOKEN }}

View File

@ -0,0 +1,41 @@
# This workflow will upload a Python Package using Twine when a Lemur release is created via github
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
name: Publish Lemur's latest package to PyPI
on:
release:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Autobump version
run: |
# from refs/tags/v0.8.1 get 0.8.1
VERSION=$(echo $GITHUB_REF | sed 's#.*/v##')
PLACEHOLDER='__version__ = "develop"'
VERSION_FILE='lemur/__about__.py'
# in case placeholder is missing, exists with code 1 and github actions aborts the build
grep "$PLACEHOLDER" "$VERSION_FILE"
sed -i "s/$PLACEHOLDER/__version__ = \"${VERSION}\"/g" "$VERSION_FILE"
shell: bash
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.LEMUR_PYPI_API_USERNAME }}
TWINE_PASSWORD: ${{ secrets.LEMUR_PYPI_API_TOKEN }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*

1
.gitignore vendored
View File

@ -39,3 +39,4 @@ lemur/tests/tmp
/lemur/plugins/lemur_email/tests/expiration-rendered.html /lemur/plugins/lemur_email/tests/expiration-rendered.html
/lemur/plugins/lemur_email/tests/rotation-rendered.html /lemur/plugins/lemur_email/tests/rotation-rendered.html
.celerybeat-schedule

23
.readthedocs.yml Normal file
View File

@ -0,0 +1,23 @@
# .readthedocs.yml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
fail_on_warning: true
# Build docs in all formats (html, pdf, epub)
formats: all
# Set the version of Python and requirements required to build the docs
python:
version: 3.7
install:
- requirements: requirements-docs.txt
- method: setuptools
path: .
system_packages: true

View File

@ -1,8 +1,39 @@
Changelog Changelog
========= =========
0.8.1 - `2021-03-12`
~~~~~~~~~~~~~~~~~~~~
This release includes improvements on many fronts, such as:
- Notifications:
- Enhanced SNS flow
- Expiration Summary
- CA expiration email
- EC algorithm as the default
- Improved revocation flow
- Localized AWS STS option
- Improved Lemur doc building
- ACME:
- reduced failed attempts to 3x trials
- support for selecting the chain (Let's Encrypt X1 transition)
- revocation
- http01 documentation
- Entrust:
- Support for cross-signed intermediate CA
- Revised disclosure process
- Dependency updates and conflict resolutions
Special thanks to all who contributed to this release, notably:
- `peschmae <https://github.com/peschmae>`_
- `atugushev <https://github.com/atugushev>`_
- `sirferl <https://github.com/sirferl>`_
0.8.0 - `2020-11-13` 0.8.0 - `2020-11-13`
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
This release comes after more than two years and contains many interesting new features and improvements. 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 In addition to multiple new plugins, such as ACME-http01, ADCS, PowerDNS, UltraDNS, Entrust, SNS, many of Lemur's existing
@ -84,7 +115,7 @@ Upgrading
0.7 - `2018-05-07` 0.7 - `2018-05-07`
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
This release adds LetsEncrypt support with DNS providers Dyn, Route53, and Cloudflare, and expands on the pending certificate functionality. This release adds LetsEncrypt support with DNS providers Dyn, Route53, and Cloudflare, and expands on the pending certificate functionality.
The linux_dst plugin will also be deprecated and removed. The linux_dst plugin will also be deprecated and removed.
@ -121,8 +152,7 @@ Happy Holidays! This is a big release with lots of bug fixes and features. Below
Features: Features:
* Per-certificate rotation policies, requires a database migration. The default rotation policy for all certificates. * Per-certificate rotation policies, requires a database migration. The default rotation policy for all certificates is 30 days. Every certificate will gain a policy regardless of if auto-rotation is used.
is 30 days. Every certificate will gain a policy regardless of if auto-rotation is used.
* Adds per-user API Keys, allows users to issue multiple long-lived API tokens with the same permission as the user creating them. * Adds per-user API Keys, allows users to issue multiple long-lived API tokens with the same permission as the user creating them.
* Adds the ability to revoke certificates from the Lemur UI/API, this is currently only supported for the digicert CIS and cfssl plugins. * Adds the ability to revoke certificates from the Lemur UI/API, this is currently only supported for the digicert CIS and cfssl plugins.
* Allow destinations to support an export function. Useful for file system destinations e.g. S3 to specify the export plugin you wish to run before being sent to the destination. * Allow destinations to support an export function. Useful for file system destinations e.g. S3 to specify the export plugin you wish to run before being sent to the destination.
@ -166,13 +196,9 @@ Big thanks to neilschelly for quite a lot of improvements to the `lemur-cryptogr
Other Highlights: Other Highlights:
* Closed `#501 <https://github.com/Netflix/lemur/issues/501>`_ - Endpoint resource as now kept in sync via an * Closed `#501 <https://github.com/Netflix/lemur/issues/501>`_ - Endpoint resource as now kept in sync via an expiration mechanism. Such that non-existant endpoints gracefully fall out of Lemur. Certificates are never removed from Lemur.
expiration mechanism. Such that non-existant endpoints gracefully fall out of Lemur. Certificates are never * Closed `#551 <https://github.com/Netflix/lemur/pull/551>`_ - Added the ability to create a 4096 bit key during certificate creation. Closed `#528 <https://github.com/Netflix/lemur/pull/528>`_ to ensure that issuer plugins supported the new 4096 bit keys.
removed from Lemur. * Closed `#566 <https://github.com/Netflix/lemur/issues/566>`_ - Fixed an issue changing the notification status for certificates without private keys.
* Closed `#551 <https://github.com/Netflix/lemur/pull/551>`_ - Added the ability to create a 4096 bit key during certificate
creation. Closed `#528 <https://github.com/Netflix/lemur/pull/528>`_ to ensure that issuer plugins supported the new 4096 bit keys.
* Closed `#566 <https://github.com/Netflix/lemur/issues/566>`_ - Fixed an issue changing the notification status for certificates
without private keys.
* Closed `#594 <https://github.com/Netflix/lemur/issues/594>`_ - Added `replaced` field indicating if a certificate has been superseded. * Closed `#594 <https://github.com/Netflix/lemur/issues/594>`_ - Added `replaced` field indicating if a certificate has been superseded.
* Closed `#602 <https://github.com/Netflix/lemur/issues/602>`_ - AWS plugin added support for ALBs for endpoint tracking. * Closed `#602 <https://github.com/Netflix/lemur/issues/602>`_ - AWS plugin added support for ALBs for endpoint tracking.
@ -196,12 +222,8 @@ Upgrading
There have been quite a few issues closed in this release. Some notables: There have been quite a few issues closed in this release. Some notables:
* Closed `#284 <https://github.com/Netflix/lemur/issues/284>`_ - Created new models for `Endpoints` created associated * Closed `#284 <https://github.com/Netflix/lemur/issues/284>`_ - Created new models for `Endpoints` created associated AWS ELB endpoint tracking code. This was the major stated goal of this milestone and should serve as the basis for future enhancements of Lemur's certificate 'deployment' capabilities.
AWS ELB endpoint tracking code. This was the major stated goal of this milestone and should serve as the basis for * Closed `#334 <https://github.com/Netflix/lemur/issues/334>`_ - Lemur not has the ability to restrict certificate expiration dates to weekdays.
future enhancements of Lemur's certificate 'deployment' capabilities.
* Closed `#334 <https://github.com/Netflix/lemur/issues/334>`_ - Lemur not has the ability
to restrict certificate expiration dates to weekdays.
Several fixes/tweaks to Lemurs python3 support (thanks chadhendrie!) Several fixes/tweaks to Lemurs python3 support (thanks chadhendrie!)
@ -256,7 +278,7 @@ these keys should be fairly trivial, additionally pull requests have been submit
should be easier to determine what authorities are available and when an authority has actually been selected. should be easier to determine what authorities are available and when an authority has actually been selected.
* Closed `#254 <https://github.com/Netflix/lemur/issues/254>`_ - Forces certificate names to be generally unique. If a certificate name * Closed `#254 <https://github.com/Netflix/lemur/issues/254>`_ - Forces certificate names to be generally unique. If a certificate name
(generated or otherwise) is found to be a duplicate we increment by appending a counter. (generated or otherwise) is found to be a duplicate we increment by appending a counter.
* Closed `#254 <https://github.com/Netflix/lemur/issues/275>`_ - Switched to using Fernet generated passphrases for exported items. * Closed `#275 <https://github.com/Netflix/lemur/issues/275>`_ - Switched to using Fernet generated passphrases for exported items.
These are more sounds that pseudo random passphrases generated before and have the nice property of being in base64. These are more sounds that pseudo random passphrases generated before and have the nice property of being in base64.
* Closed `#278 <https://github.com/Netflix/lemur/issues/278>`_ - Added ability to specify a custom name to certificate creation, previously * Closed `#278 <https://github.com/Netflix/lemur/issues/278>`_ - Added ability to specify a custom name to certificate creation, previously
this was only available in the certificate import wizard. this was only available in the certificate import wizard.

View File

@ -78,13 +78,13 @@ Basic Configuration
The default connection pool size is 5 for sqlalchemy managed connections. Depending on the number of Lemur instances, The default connection pool size is 5 for sqlalchemy managed connections. Depending on the number of Lemur instances,
please specify per instance connection pool size. Below is an example to set connection pool size to 10. please specify per instance connection pool size. Below is an example to set connection pool size to 10.
:: ::
SQLALCHEMY_POOL_SIZE = 10 SQLALCHEMY_POOL_SIZE = 10
.. warning:: .. warning::
This is an optional setting but important to review and set for optimal database connection usage and for overall database performance. This is an optional setting but important to review and set for optimal database connection usage and for overall database performance.
.. data:: SQLALCHEMY_MAX_OVERFLOW .. data:: SQLALCHEMY_MAX_OVERFLOW
:noindex: :noindex:
@ -99,7 +99,7 @@ This is an optional setting but important to review and set for optimal database
.. note:: .. note::
Specifying the `SQLALCHEMY_MAX_OVERFLOW` to 0 will enforce limit to not create connections above specified pool size. Specifying the `SQLALCHEMY_MAX_OVERFLOW` to 0 will enforce limit to not create connections above specified pool size.
.. data:: LEMUR_ALLOW_WEEKEND_EXPIRATION .. data:: LEMUR_ALLOW_WEEKEND_EXPIRATION
@ -174,6 +174,7 @@ Specifying the `SQLALCHEMY_MAX_OVERFLOW` to 0 will enforce limit to not create c
.. data:: PUBLIC_CA_MAX_VALIDITY_DAYS .. data:: PUBLIC_CA_MAX_VALIDITY_DAYS
:noindex: :noindex:
Use this config to override the limit of 397 days of validity for certificates issued by CA/Browser compliant authorities. Use this config to override the limit of 397 days of validity for certificates issued by CA/Browser compliant authorities.
The authorities with cab_compliant option set to true will use this config. The example below overrides the default validity The authorities with cab_compliant option set to true will use this config. The example below overrides the default validity
of 397 days and sets it to 365 days. of 397 days and sets it to 365 days.
@ -185,6 +186,7 @@ Specifying the `SQLALCHEMY_MAX_OVERFLOW` to 0 will enforce limit to not create c
.. data:: DEFAULT_VALIDITY_DAYS .. data:: DEFAULT_VALIDITY_DAYS
:noindex: :noindex:
Use this config to override the default validity of 365 days for certificates offered through Lemur UI. Any CA which Use this config to override the default validity of 365 days for certificates offered through Lemur UI. Any CA which
is not CA/Browser Forum compliant will be using this value as default validity to be displayed on UI. Please is not CA/Browser Forum compliant will be using this value as default validity to be displayed on UI. Please
note that this config is used for cert issuance only through Lemur UI. The example below overrides the default validity note that this config is used for cert issuance only through Lemur UI. The example below overrides the default validity
@ -207,6 +209,11 @@ Specifying the `SQLALCHEMY_MAX_OVERFLOW` to 0 will enforce limit to not create c
in the UI. When set to False (the default), the certificate delete API will always return "405 method not allowed" in the UI. When set to False (the default), the certificate delete API will always return "405 method not allowed"
and deleted certificates will always be visible in the UI. (default: `False`) and deleted certificates will always be visible in the UI. (default: `False`)
.. data:: LEMUR_AWS_REGION
:noindex:
This is an optional config applicable for settings where Lemur is deployed in AWS. For accessing regionalized
STS endpoints, LEMUR_AWS_REGION defines the region where Lemur is deployed.
Certificate Default Options Certificate Default Options
--------------------------- ---------------------------
@ -864,6 +871,17 @@ account. See :ref:`Using a pre-existing ACME account <AcmeAccountReuse>` for mor
This is the registration for the ACME account, the most important part is the uri attribute (in JSON) This is the registration for the ACME account, the most important part is the uri attribute (in JSON)
.. data:: ACME_PREFERRED_ISSUER
:noindex:
This is an optional parameter to indicate the preferred chain to retrieve from ACME when finalizing the order.
This is applicable to Let's Encrypts recent `migration <https://letsencrypt.org/certificates/>`_ to their
own root, where they provide two distinct certificate chains (fullchain_pem vs. alternative_fullchains_pem);
the main chain will be the long chain that is rooted in the expiring DTS root, whereas the alternative chain
is rooted in X1 root CA.
Select "X1" to get the shorter chain (currently alternative), leave blank or "DST Root CA X3" for the longer chain.
Active Directory Certificate Services Plugin Active Directory Certificate Services Plugin
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -904,10 +922,12 @@ Active Directory Certificate Services Plugin
.. data:: ADCS_START .. data:: ADCS_START
:noindex: :noindex:
Used in ADCS-Sourceplugin. Minimum id of the first certificate to be returned. ID is increased by one until ADCS_STOP. Missing cert-IDs are ignored Used in ADCS-Sourceplugin. Minimum id of the first certificate to be returned. ID is increased by one until ADCS_STOP. Missing cert-IDs are ignored
.. data:: ADCS_STOP .. data:: ADCS_STOP
:noindex: :noindex:
Used for ADCS-Sourceplugin. Maximum id of the certificates returned. Used for ADCS-Sourceplugin. Maximum id of the certificates returned.
@ -1640,7 +1660,7 @@ Slack
AWS (Source) AWS (Source)
---- ------------
:Authors: :Authors:
Kevin Glisson <kglisson@netflix.com>, Kevin Glisson <kglisson@netflix.com>,
@ -1653,7 +1673,7 @@ AWS (Source)
AWS (Destination) AWS (Destination)
---- -----------------
:Authors: :Authors:
Kevin Glisson <kglisson@netflix.com>, Kevin Glisson <kglisson@netflix.com>,
@ -1666,7 +1686,7 @@ AWS (Destination)
AWS (SNS Notification) AWS (SNS Notification)
----- ----------------------
:Authors: :Authors:
Jasmine Schladen <jschladen@netflix.com> Jasmine Schladen <jschladen@netflix.com>

View File

@ -32,6 +32,9 @@ if on_rtd:
MOCK_MODULES = ["ldap"] MOCK_MODULES = ["ldap"]
sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES) sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)
autodoc_mock_imports = ["python-ldap", "acme", "certsrv", "dnspython3", "dyn", "factory-boy", "flask_replicated",
"josepy", "logmatic", "pem"]
# -- General configuration ------------------------------------------------ # -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here. # If your documentation needs a minimal Sphinx version, state it here.
@ -146,7 +149,7 @@ if not on_rtd: # only import and set the theme if we're building docs locally
# Add any paths that contain custom static files (such as style sheets) here, # Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"] # html_static_path = ["_static"]
# Add any extra paths that contain custom files (such as robots.txt or # Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied # .htaccess) here, relative to this directory. These files are copied

View File

@ -43,6 +43,13 @@ Building Documentation
Inside the ``docs`` directory, you can run ``make`` to build the documentation. Inside the ``docs`` directory, you can run ``make`` to build the documentation.
See ``make help`` for available options and the `Sphinx Documentation <http://sphinx-doc.org/contents.html>`_ for more information. See ``make help`` for available options and the `Sphinx Documentation <http://sphinx-doc.org/contents.html>`_ for more information.
Adding New Modules to Documentation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When a new module is added, it will need to be added to the documentation.
Ideally, we might rely on `sphinx-apidoc <https://www.sphinx-doc.org/en/master/man/sphinx-apidoc.html>`_ to autogenerate our documentation.
Unfortunately, this causes some build problems.
Instead, you'll need to add new modules by hand.
Developing Against HEAD Developing Against HEAD
----------------------- -----------------------

View File

@ -0,0 +1,29 @@
defaults Package
================
:mod:`defaults` Module
----------------------------------------
.. automodule:: lemur.defaults
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`schemas` Module
-----------------------------
.. automodule:: lemur.defaults.schemas
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`views` Module
---------------------------
.. automodule:: lemur.defaults.views
:noindex:
:members:
:undoc-members:
:show-inheritance:

View File

@ -0,0 +1,20 @@
deployment Package
===================
:mod:`deployment` Module
----------------------------------------
.. automodule:: lemur.deployment
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`service` Module
------------------------------
.. automodule:: lemur.deployment.service
:noindex:
:members:
:undoc-members:
:show-inheritance:

View File

@ -0,0 +1,56 @@
endpoints Package
===================
:mod:`endpoints` Module
----------------------------------------
.. automodule:: lemur.endpoints
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`cli` Module
--------------------------
.. automodule:: lemur.endpoints.cli
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`models` Module
-----------------------------
.. automodule:: lemur.endpoints.models
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`schemas` Module
------------------------------
.. automodule:: lemur.endpoints.schemas
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`service` Module
------------------------------
.. automodule:: lemur.endpoints.service
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`views` Module
----------------------------
.. automodule:: lemur.endpoints.views
:noindex:
:members:
:undoc-members:
:show-inheritance:

View File

@ -0,0 +1,47 @@
logs Package
===================
:mod:`logs` Module
--------------------
.. automodule:: lemur.logs
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`models` Module
------------------------------
.. automodule:: lemur.logs.models
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`schemas` Module
------------------------------
.. automodule:: lemur.logs.schemas
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`service` Module
------------------------------
.. automodule:: lemur.logs.service
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`views` Module
------------------------------
.. automodule:: lemur.logs.views
:noindex:
:members:
:undoc-members:
:show-inheritance:

View File

@ -0,0 +1,83 @@
lemur_acme package
=================================
:mod:`lemur_acme` Module
----------------------------------------
.. automodule:: lemur.plugins.lemur_acme
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`acme_handlers` Module
-----------------------------------------------
.. automodule:: lemur.plugins.lemur_acme.acme_handlers
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`challenge_types` Module
-------------------------------------------------
.. automodule:: lemur.plugins.lemur_acme.challenge_types
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`cloudflare` Module
-------------------------------------------
.. automodule:: lemur.plugins.lemur_acme.cloudflare
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`dyn` Module
------------------------------------
.. automodule:: lemur.plugins.lemur_acme.dyn
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`plugin` Module
---------------------------------------
.. automodule:: lemur.plugins.lemur_acme.plugin
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`powerdns` Module
-----------------------------------------
.. automodule:: lemur.plugins.lemur_acme.powerdns
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`route53` Module
----------------------------------------
.. automodule:: lemur.plugins.lemur_acme.route53
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`ultradns` Module
-----------------------------------------
.. automodule:: lemur.plugins.lemur_acme.ultradns
:noindex:
:members:
:undoc-members:
:show-inheritance:

View File

@ -0,0 +1,20 @@
lemur_atlas package
==================================
:mod:`lemur_atlas` Module
----------------------------------------
.. automodule:: lemur.plugins.lemur_atlas
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`plugin` Module
--------------------
.. automodule:: lemur.plugins.lemur_atlas.plugin
:noindex:
:members:
:undoc-members:
:show-inheritance:

View File

@ -0,0 +1,20 @@
lemur_cryptography package
==================================
:mod:`lemur_cryptography` Module
----------------------------------------
.. automodule:: lemur.plugins.lemur_cryptography
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`plugin` Module
--------------------
.. automodule:: lemur.plugins.lemur_cryptography.plugin
:noindex:
:members:
:undoc-members:
:show-inheritance:

View File

@ -0,0 +1,20 @@
lemur_digicert package
==================================
:mod:`lemur_digicert` Module
----------------------------------------
.. automodule:: lemur.plugins.lemur_digicert
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`plugin` Module
--------------------
.. automodule:: lemur.plugins.lemur_digicert.plugin
:noindex:
:members:
:undoc-members:
:show-inheritance:

View File

@ -0,0 +1,20 @@
lemur_jks package
==================================
:mod:`lemur_jks` Module
----------------------------------------
.. automodule:: lemur.plugins.lemur_jks
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`plugin` Module
--------------------
.. automodule:: lemur.plugins.lemur_jks.plugin
:noindex:
:members:
:undoc-members:
:show-inheritance:

View File

@ -0,0 +1,20 @@
lemur_kubernetes package
==================================
:mod:`lemur_kubernetes` Module
----------------------------------------
.. automodule:: lemur.plugins.lemur_kubernetes
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`plugin` Module
--------------------
.. automodule:: lemur.plugins.lemur_kubernetes.plugin
:noindex:
:members:
:undoc-members:
:show-inheritance:

View File

@ -0,0 +1,20 @@
lemur_openssl package
==================================
:mod:`lemur_openssl` Module
----------------------------------------
.. automodule:: lemur.plugins.lemur_openssl
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`plugin` Module
--------------------
.. automodule:: lemur.plugins.lemur_openssl.plugin
:noindex:
:members:
:undoc-members:
:show-inheritance:

View File

@ -0,0 +1,20 @@
lemur_slack package
==================================
:mod:`lemur_slack` Module
----------------------------------------
.. automodule:: lemur.plugins.lemur_slack
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`plugin` Module
--------------------
.. automodule:: lemur.plugins.lemur_slack.plugin
:noindex:
:members:
:undoc-members:
:show-inheritance:

View File

@ -0,0 +1,38 @@
reporting Package
===================
:mod:`reporting` Module
----------------------------------------
.. automodule:: lemur.reporting
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`cli` Module
------------------------------
.. automodule:: lemur.reporting.cli
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`service` Module
------------------------------
.. automodule:: lemur.reporting.service
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`views` Module
------------------------------
.. automodule:: lemur.reporting.views
:noindex:
:members:
:undoc-members:
:show-inheritance:

View File

@ -28,15 +28,6 @@ lemur Package
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
:mod:`decorators` Module
------------------------
.. automodule:: lemur.decorators
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`exceptions` Module :mod:`exceptions` Module
------------------------ ------------------------
@ -108,7 +99,7 @@ Subpackages
lemur.plugins.lemur_atlas lemur.plugins.lemur_atlas
lemur.plugins.lemur_cryptography lemur.plugins.lemur_cryptography
lemur.plugins.lemur_digicert lemur.plugins.lemur_digicert
lemur.plugins.lemur_java lemur.plugins.lemur_jks
lemur.plugins.lemur_kubernetes lemur.plugins.lemur_kubernetes
lemur.plugins.lemur_openssl lemur.plugins.lemur_openssl
lemur.plugins.lemur_slack lemur.plugins.lemur_slack

View File

@ -0,0 +1,56 @@
sources Package
===================
:mod:`sources` Module
----------------------
.. automodule:: lemur.sources
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`cli` Module
------------------------------
.. automodule:: lemur.sources.cli
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`models` Module
------------------------------
.. automodule:: lemur.sources.models
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`schemas` Module
------------------------------
.. automodule:: lemur.sources.schemas
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`service` Module
------------------------------
.. automodule:: lemur.sources.service
:noindex:
:members:
:undoc-members:
:show-inheritance:
:mod:`views` Module
------------------------------
.. automodule:: lemur.sources.views
:noindex:
:members:
:undoc-members:
:show-inheritance:

View File

@ -0,0 +1,11 @@
tests Package
=============
:mod:`tests` Module
--------------------
.. automodule:: lemur.tests
:noindex:
:members:
:undoc-members:
:show-inheritance:

View File

@ -145,8 +145,7 @@ The `IssuerPlugin` doesn't have any options like Destination, Source, and Notifi
any fields you might need to submit a request to a third party. If there are additional options you need any fields you might need to submit a request to a third party. If there are additional options you need
in your plugin feel free to open an issue, or look into adding additional options to issuers yourself. in your plugin feel free to open an issue, or look into adding additional options to issuers yourself.
Asynchronous Certificates **Asynchronous Certificates**
^^^^^^^^^^^^^^^^^^^^^^^^^
An issuer may take some time to actually issue a certificate for an order. In this case, a `PendingCertificate` is returned, which holds information to recreate a `Certificate` object at a later time. Then, `get_ordered_certificate()` should be run periodically via `python manage.py pending_certs fetch -i all` to attempt to retrieve an ordered certificate:: An issuer may take some time to actually issue a certificate for an order. In this case, a `PendingCertificate` is returned, which holds information to recreate a `Certificate` object at a later time. Then, `get_ordered_certificate()` should be run periodically via `python manage.py pending_certs fetch -i all` to attempt to retrieve an ordered certificate::
def get_ordered_ceriticate(self, order_id): def get_ordered_ceriticate(self, order_id):
@ -154,9 +153,10 @@ An issuer may take some time to actually issue a certificate for an order. In t
# retrieve an order, and check if there is an issued certificate attached to it # retrieve an order, and check if there is an issued certificate attached to it
`cancel_ordered_certificate()` should be implemented to allow an ordered certificate to be canceled before it is issued:: `cancel_ordered_certificate()` should be implemented to allow an ordered certificate to be canceled before it is issued::
def cancel_ordered_certificate(self, pending_cert, **kwargs):
# pending_cert should contain the necessary information to match an order def cancel_ordered_certificate(self, pending_cert, **kwargs):
# kwargs can be given to provide information to the issuer for canceling # pending_cert should contain the necessary information to match an order
# kwargs can be given to provide information to the issuer for canceling
Destination Destination
----------- -----------
@ -286,7 +286,7 @@ The `ExportPlugin` object requires the implementation of one function::
Custom TLS Provider Custom TLS Provider
------ -------------------
Managing TLS at the enterprise scale could be hard and often organizations offer custom wrapper implementations. It could Managing TLS at the enterprise scale could be hard and often organizations offer custom wrapper implementations. It could
be ideal to use those while making calls to internal services. The `TLSPlugin` would help to achieve this. It requires the be ideal to use those while making calls to internal services. The `TLSPlugin` would help to achieve this. It requires the

View File

@ -1,10 +1,18 @@
Doing a release Doing a release
=============== ===============
Doing a release of ``lemur`` requires a few steps. Doing a release of ``lemur`` is now mostly automated and consists of the following steps:
Bumping the version number * Raise a PR to add the release date and summary in the :doc:`/changelog`.
-------------------------- * Merge above PR and create a new `Github release <https://github.com/Netflix/lemur/releaes>`_: set the tag starting with v, e.g., v0.9.0
The `publish workflow <https://github.com/Netflix/lemur/actions/workflows/lemur-publish-release-pypi.yml>`_ uses the git
tag to set the release version.
The following describes the manual release steps, which is now obsolete:
Manually Bumping the version number
-----------------------------------
The next step in doing a release is bumping the version number in the The next step in doing a release is bumping the version number in the
software. software.
@ -14,8 +22,8 @@ software.
* Do a commit indicating this, and raise a pull request with this. * Do a commit indicating this, and raise a pull request with this.
* Wait for it to be merged. * Wait for it to be merged.
Performing the release Manually Performing the release
---------------------- -------------------------------
The commit that merged the version number bump is now the official release The commit that merged the version number bump is now the official release
commit for this release. You need an `API key <https://pypi.org/manage/account/#api-tokens>`_, commit for this release. You need an `API key <https://pypi.org/manage/account/#api-tokens>`_,

View File

@ -65,6 +65,7 @@ Import an Existing Certificate
You can add notification options and upload the created certificate to a destination, both You can add notification options and upload the created certificate to a destination, both
of these are editable features and can be changed after the certificate has been created. of these are editable features and can be changed after the certificate has been created.
.. _CreateANewUser:
Create a New User Create a New User
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~

View File

@ -17,4 +17,5 @@ A list of additional contributors can be seen on `GitHub <https://github.com/net
Lemur License Lemur License
------------- -------------
.. include:: ../../LICENSE .. include:: ../../LICENSE
:literal:

View File

@ -501,7 +501,7 @@ rely on celery to create the DNS record. This will change when we implement mix
To create a HTTP compatible Authority, you first need to create a new destination that will be used to deploy the 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 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://<domain>/.well-known/acme-challenge/`. The be the exact path that is called when the ACME providers calls `http://<domain>/.well-known/acme-challenge/`. The
token part will be added dynamically by the acme_upload. token part will be added dynamically by the acme_upload.
Currently only the SFTP and S3 Bucket destination support the ACME HTTP challenge. Currently only the SFTP and S3 Bucket destination support the ACME HTTP challenge.

View File

@ -148,7 +148,7 @@ Before Lemur will run you need to fill in a few required variables in the config
LEMUR_DEFAULT_ORGANIZATIONAL_UNIT LEMUR_DEFAULT_ORGANIZATIONAL_UNIT
Set Up Postgres Set Up Postgres
-------------- ---------------
For production, a dedicated database is recommended, for this guide we will assume postgres has been installed and is on the same machine that Lemur is installed on. For production, a dedicated database is recommended, for this guide we will assume postgres has been installed and is on the same machine that Lemur is installed on.
@ -186,11 +186,12 @@ In addition to creating a new user, Lemur also creates a few default email notif
Your database installation requires the pg_trgm extension. If you do not have this installed already, you can allow the script to install this for you by adding the SUPERUSER permission to the lemur database user. Your database installation requires the pg_trgm extension. If you do not have this installed already, you can allow the script to install this for you by adding the SUPERUSER permission to the lemur database user.
.. code-block:: bash .. code-block:: bash
sudo -u postgres -i sudo -u postgres -i
psql psql
postgres=# ALTER USER lemur WITH SUPERUSER postgres=# ALTER USER lemur WITH SUPERUSER
Additional notifications can be created through the UI or API. See :ref:`Creating Notifications <CreatingNotifications>` and :ref:`Command Line Interface <CommandLineInterface>` for details. Additional notifications can be created through the UI or API. See :ref:`Notification Options <NotificationOptions>` and :ref:`Command Line Interface <CommandLineInterface>` for details.
**Make note of the password used as this will be used during first login to the Lemur UI.** **Make note of the password used as this will be used during first login to the Lemur UI.**
@ -202,15 +203,16 @@ Additional notifications can be created through the UI or API. See :ref:`Creati
.. note:: If you added the SUPERUSER permission to the lemur database user above, it is recommended you revoke that permission now. .. note:: If you added the SUPERUSER permission to the lemur database user above, it is recommended you revoke that permission now.
.. code-block:: bash .. code-block:: bash
sudo -u postgres -i sudo -u postgres -i
psql psql
postgres=# ALTER USER lemur WITH NOSUPERUSER postgres=# ALTER USER lemur WITH NOSUPERUSER
.. note:: It is recommended that once the ``lemur`` user is created that you create individual users for every day access. There is currently no way for a user to self enroll for Lemur access, they must have an administrator create an account for them or be enrolled automatically through SSO. This can be done through the CLI or UI. See :ref:`Creating Users <CreatingUsers>` and :ref:`Command Line Interface <CommandLineInterface>` for details. .. note:: It is recommended that once the ``lemur`` user is created that you create individual users for every day access. There is currently no way for a user to self enroll for Lemur access, they must have an administrator create an account for them or be enrolled automatically through SSO. This can be done through the CLI or UI. See :ref:`Creating a New User <CreateANewUser>` and :ref:`Command Line Interface <CommandLineInterface>` for details.
Set Up a Reverse Proxy Set Up a Reverse Proxy
--------------------- ----------------------
By default, Lemur runs on port 8000. Even if you change this, under normal conditions you won't be able to bind to port 80. To get around this (and to avoid running Lemur as a privileged user, which you shouldn't), we need to set up a simple web proxy. There are many different web servers you can use for this, we like and recommend Nginx. By default, Lemur runs on port 8000. Even if you change this, under normal conditions you won't be able to bind to port 80. To get around this (and to avoid running Lemur as a privileged user, which you shouldn't), we need to set up a simple web proxy. There are many different web servers you can use for this, we like and recommend Nginx.

View File

@ -22,7 +22,7 @@ Supported Versions
------------------ ------------------
At any given time, we will provide security support for the `master`_ branch At any given time, we will provide security support for the `master`_ branch
as well as the 2 most recent releases. as well as the most recent release.
Disclosure Process Disclosure Process
------------------ ------------------
@ -30,20 +30,15 @@ Disclosure Process
Our process for taking a security issue from private discussion to public Our process for taking a security issue from private discussion to public
disclosure involves multiple steps. disclosure involves multiple steps.
Approximately one week before full public disclosure, we will send advance Approximately one week before full public disclosure, we will provide advanced notification that a security issue exists. Depending on the severity of the issue, we may choose to either send a targeted email to known Lemur users and contributors or post an issue to the Lemur repository. In either case, the notification should contain the following.
notification of the issue to a list of people and organizations, primarily
composed of operating-system vendors and other distributors of
``lemur``. This notification will consist of an email message
containing:
* A full description of the issue and the affected versions of * A description of the potential impact
``lemur``. * The affected versions of ``lemur``.
* The steps we will be taking to remedy the issue. * The steps we will be taking to remedy the issue.
* The patches, if any, that will be applied to ``lemur``.
* The date on which the ``lemur`` team will apply these patches, issue * The date on which the ``lemur`` team will apply these patches, issue
new releases, and publicly disclose the issue. new releases, and publicly disclose the issue.
Simultaneously, the reporter of the issue will receive notification of the date If the issue was disclosed to us, the reporter will receive notification of the date
on which we plan to make the issue public. on which we plan to make the issue public.
On the day of disclosure, we will take the following steps: On the day of disclosure, we will take the following steps:
@ -52,7 +47,7 @@ On the day of disclosure, we will take the following steps:
messages for these patches will indicate that they are for security issues, messages for these patches will indicate that they are for security issues,
but will not describe the issue in any detail; instead, they will warn of but will not describe the issue in any detail; instead, they will warn of
upcoming disclosure. upcoming disclosure.
* Issue the relevant releases. * Issue an updated release.
If a reported issue is believed to be particularly time-sensitive due to a If a reported issue is believed to be particularly time-sensitive due to a
known exploit in the wild, for example the time between advance notification known exploit in the wild, for example the time between advance notification

View File

@ -40,7 +40,7 @@ function replaceAll(string, find, replace) {
function stringSrc(filename, string) { function stringSrc(filename, string) {
let src = require('stream').Readable({objectMode: true}); let src = require('stream').Readable({objectMode: true});
src._read = function () { src._read = function () {
this.push(new gutil.File({cwd: '', base: '', path: filename, contents: new Buffer(string)})); this.push(new gutil.File({cwd: '', base: '', path: filename, contents: Buffer.from(string)}));
this.push(null); this.push(null);
}; };
return src; return src;

View File

@ -15,7 +15,7 @@ __title__ = "lemur"
__summary__ = "Certificate management and orchestration service" __summary__ = "Certificate management and orchestration service"
__uri__ = "https://github.com/Netflix/lemur" __uri__ = "https://github.com/Netflix/lemur"
__version__ = "0.8.0" __version__ = "develop"
__author__ = "The Lemur developers" __author__ = "The Lemur developers"
__email__ = "security@netflix.com" __email__ = "security@netflix.com"

View File

@ -75,7 +75,7 @@ def create_token(user, aid=None, ttl=None):
if ttl == -1: if ttl == -1:
del payload["exp"] del payload["exp"]
else: else:
payload["exp"] = ttl payload["exp"] = datetime.utcnow() + timedelta(days=ttl)
token = jwt.encode(payload, current_app.config["LEMUR_TOKEN_SECRET"]) token = jwt.encode(payload, current_app.config["LEMUR_TOKEN_SECRET"])
return token return token
@ -116,9 +116,8 @@ def login_required(f):
return dict(message="Token has been revoked"), 403 return dict(message="Token has been revoked"), 403
if access_key.ttl != -1: if access_key.ttl != -1:
current_time = datetime.utcnow() current_time = datetime.utcnow()
expired_time = datetime.fromtimestamp( # API key uses days
access_key.issued_at + access_key.ttl expired_time = datetime.fromtimestamp(access_key.issued_at) + timedelta(days=access_key.ttl)
)
if current_time >= expired_time: if current_time >= expired_time:
return dict(message="Token has expired"), 403 return dict(message="Token has expired"), 403

View File

@ -132,31 +132,31 @@ class AuthoritiesList(AuthenticatedResource):
Accept: application/json, text/javascript Accept: application/json, text/javascript
Content-Type: application/json;charset=UTF-8 Content-Type: application/json;charset=UTF-8
{ {
"country": "US", "country": "US",
"state": "California", "state": "California",
"location": "Los Gatos", "location": "Los Gatos",
"organization": "Netflix", "organization": "Netflix",
"organizationalUnit": "Operations", "organizationalUnit": "Operations",
"type": "root", "type": "root",
"signingAlgorithm": "sha256WithRSA", "signingAlgorithm": "sha256WithRSA",
"sensitivity": "medium", "sensitivity": "medium",
"keyType": "RSA2048", "keyType": "RSA2048",
"plugin": { "plugin": {
"slug": "cloudca-issuer" "slug": "cloudca-issuer"
}, },
"name": "TimeTestAuthority5", "name": "TimeTestAuthority5",
"owner": "secure@example.com", "owner": "secure@example.com",
"description": "test", "description": "test",
"commonName": "AcommonName", "commonName": "AcommonName",
"validityYears": "20", "validityYears": "20",
"extensions": { "extensions": {
"subAltNames": { "subAltNames": {
"names": [] "names": []
}, },
"custom": [] "custom": []
} }
} }
**Example response**: **Example response**:
@ -218,8 +218,7 @@ class AuthoritiesList(AuthenticatedResource):
:arg parent: the parent authority if this is to be a subca :arg parent: the parent authority if this is to be a subca
:arg signingAlgorithm: algorithm used to sign the authority :arg signingAlgorithm: algorithm used to sign the authority
:arg keyType: key type :arg keyType: key type
:arg sensitivity: the sensitivity of the root key, for CloudCA this determines if the root keys are stored :arg sensitivity: the sensitivity of the root key, for CloudCA this determines if the root keys are stored in an HSM
in an HSM
:arg keyName: name of the key to store in the HSM (CloudCA) :arg keyName: name of the key to store in the HSM (CloudCA)
:arg serialNumber: serial number of the authority :arg serialNumber: serial number of the authority
:arg firstSerial: specifies the starting serial number for certificates issued off of this authority :arg firstSerial: specifies the starting serial number for certificates issued off of this authority
@ -494,23 +493,48 @@ class CertificateAuthority(AuthenticatedResource):
class AuthorityVisualizations(AuthenticatedResource): class AuthorityVisualizations(AuthenticatedResource):
def get(self, authority_id): def get(self, authority_id):
""" """
{"name": "flare", .. http:get:: /authorities/1/visualize
"children": [
{ Authority visualization
"name": "analytics",
"children": [ **Example request**:
{
"name": "cluster", .. sourcecode:: http
"children": [
{"name": "AgglomerativeCluster", "size": 3938}, GET /certificates/1/visualize HTTP/1.1
{"name": "CommunityStructure", "size": 3812}, Host: example.com
{"name": "HierarchicalCluster", "size": 6714}, Accept: application/json, text/javascript
{"name": "MergeEdge", "size": 743}
] **Example response**:
}
] .. sourcecode:: http
}
]} HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript
{"name": "flare",
"children": [
{
"name": "analytics",
"children": [
{
"name": "cluster",
"children": [
{"name": "AgglomerativeCluster", "size": 3938},
{"name": "CommunityStructure", "size": 3812},
{"name": "HierarchicalCluster", "size": 6714},
{"name": "MergeEdge", "size": 743}
]
}
]
}
]
}
:reqheader Authorization: OAuth token to authenticate
:statuscode 200: no error
:statuscode 403: unauthenticated
""" """
authority = service.get(authority_id) authority = service.get(authority_id)
return dict( return dict(

View File

@ -153,6 +153,7 @@ def get_all_certs_attached_to_endpoint_without_autorotate():
return ( return (
Certificate.query.filter(Certificate.endpoints.any()) Certificate.query.filter(Certificate.endpoints.any())
.filter(Certificate.rotation == false()) .filter(Certificate.rotation == false())
.filter(Certificate.revoked == false())
.filter(Certificate.not_after >= arrow.now()) .filter(Certificate.not_after >= arrow.now())
.filter(not_(Certificate.replaced.any())) .filter(not_(Certificate.replaced.any()))
.all() # noqa .all() # noqa

View File

@ -59,8 +59,8 @@ class CertificatesListValid(AuthenticatedResource):
**Example request**: **Example request**:
.. sourcecode:: http .. sourcecode:: http
GET /certificates/valid?filter=cn;*.test.example.net&owner=joe@example.com&page=1&count=20
HTTP/1.1 GET /certificates/valid?filter=cn;*.test.example.net&owner=joe@example.com&page=1&count=20 HTTP/1.1
Host: example.com Host: example.com
Accept: application/json, text/javascript Accept: application/json, text/javascript

View File

@ -21,7 +21,7 @@ def create(label, plugin_name, options, description=None):
:param label: Destination common name :param label: Destination common name
:param description: :param description:
:rtype : Destination :rtype: Destination
:return: New destination :return: New destination
""" """
# remove any sub-plugin objects before try to save the json options # remove any sub-plugin objects before try to save the json options
@ -50,7 +50,7 @@ def update(destination_id, label, plugin_name, options, description):
:param plugin_name: :param plugin_name:
:param options: :param options:
:param description: :param description:
:rtype : Destination :rtype: Destination
:return: :return:
""" """
destination = get(destination_id) destination = get(destination_id)
@ -81,7 +81,7 @@ def get(destination_id):
Retrieves an destination by its lemur assigned ID. Retrieves an destination by its lemur assigned ID.
:param destination_id: Lemur assigned ID :param destination_id: Lemur assigned ID
:rtype : Destination :rtype: Destination
:return: :return:
""" """
return database.get(Destination, destination_id) return database.get(Destination, destination_id)

View File

@ -36,7 +36,7 @@ def get_friendly(dns_provider_id):
Retrieves a dns provider by its lemur assigned ID. Retrieves a dns provider by its lemur assigned ID.
:param dns_provider_id: Lemur assigned ID :param dns_provider_id: Lemur assigned ID
:rtype : DnsProvider :rtype: DnsProvider
:return: :return:
""" """
dns_provider = get(dns_provider_id) dns_provider = get(dns_provider_id)

View File

@ -86,62 +86,79 @@ class DnsProvidersList(AuthenticatedResource):
@admin_permission.require(http_exception=403) @admin_permission.require(http_exception=403)
def post(self, data=None): def post(self, data=None):
""" """
Creates a DNS Provider .. http:post:: /dns_providers
**Example request**: Creates a DNS Provider
{
"providerType": { **Example request**:
"name": "route53",
"requirements": [ .. sourcecode:: http
{
"name": "account_id", POST /dns_providers HTTP/1.1
"type": "int", Host: example.com
"required": true, Accept: application/json, text/javascript
"helpMessage": "AWS Account number",
"value": 12345 {
} "providerType": {
], "name": "route53",
"route": "dns_provider_options", "requirements": [
"reqParams": null, {
"restangularized": true, "name": "account_id",
"fromServer": true, "type": "int",
"parentResource": null, "required": true,
"restangularCollection": false "helpMessage": "AWS Account number",
}, "value": 12345
"name": "provider_name", }
"description": "provider_description" ],
} "route": "dns_provider_options",
"reqParams": null,
"restangularized": true,
"fromServer": true,
"parentResource": null,
"restangularCollection": false
},
"name": "provider_name",
"description": "provider_description"
}
**Example request 2**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript
{
"providerType": {
"name": "cloudflare",
"requirements": [
{
"name": "email",
"type": "str",
"required": true,
"helpMessage": "Cloudflare Email",
"value": "test@example.com"
},
{
"name": "key",
"type": "str",
"required": true,
"helpMessage": "Cloudflare Key",
"value": "secretkey"
}
],
"route": "dns_provider_options",
"reqParams": null,
"restangularized": true,
"fromServer": true,
"parentResource": null,
"restangularCollection": false
},
"name": "provider_name",
"description": "provider_description"
}
**Example request 2**
{
"providerType": {
"name": "cloudflare",
"requirements": [
{
"name": "email",
"type": "str",
"required": true,
"helpMessage": "Cloudflare Email",
"value": "test@example.com"
},
{
"name": "key",
"type": "str",
"required": true,
"helpMessage": "Cloudflare Key",
"value": "secretkey"
}
],
"route": "dns_provider_options",
"reqParams": null,
"restangularized": true,
"fromServer": true,
"parentResource": null,
"restangularCollection": false
},
"name": "provider_name",
"description": "provider_description"
}
:return: :return:
""" """
return service.create(data) return service.create(data)

View File

@ -96,7 +96,7 @@ class DomainsList(AuthenticatedResource):
.. sourcecode:: http .. sourcecode:: http
GET /domains HTTP/1.1 POST /domains HTTP/1.1
Host: example.com Host: example.com
Accept: application/json, text/javascript Accept: application/json, text/javascript

View File

@ -200,6 +200,8 @@ def send_plugin_notification(event_type, data, recipients, notification):
"notification_type": event_type, "notification_type": event_type,
"notification_plugin": notification.plugin.slug, "notification_plugin": notification.plugin.slug,
"certificate_targets": recipients, "certificate_targets": recipients,
"plugin": notification.plugin.slug,
"notification_id": notification.id,
} }
status = FAILURE_METRIC_STATUS status = FAILURE_METRIC_STATUS
try: try:

View File

@ -21,6 +21,8 @@ class NotificationInputSchema(LemurInputSchema):
active = fields.Boolean() active = fields.Boolean()
plugin = fields.Nested(PluginInputSchema, required=True) plugin = fields.Nested(PluginInputSchema, required=True)
certificates = fields.Nested(AssociatedCertificateSchema, many=True, missing=[]) certificates = fields.Nested(AssociatedCertificateSchema, many=True, missing=[])
added_certificates = fields.Nested(AssociatedCertificateSchema, many=True, missing=[])
removed_certificates = fields.Nested(AssociatedCertificateSchema, many=True, missing=[])
class NotificationOutputSchema(LemurOutputSchema): class NotificationOutputSchema(LemurOutputSchema):

View File

@ -94,7 +94,7 @@ def create(label, plugin_name, options, description, certificates):
:param options: :param options:
:param description: :param description:
:param certificates: :param certificates:
:rtype : Notification :rtype: Notification
:return: :return:
""" """
notification = Notification( notification = Notification(
@ -104,7 +104,7 @@ def create(label, plugin_name, options, description, certificates):
return database.create(notification) return database.create(notification)
def update(notification_id, label, plugin_name, options, description, active, certificates): def update(notification_id, label, plugin_name, options, description, active, added_certificates, removed_certificates):
""" """
Updates an existing notification. Updates an existing notification.
@ -114,8 +114,9 @@ def update(notification_id, label, plugin_name, options, description, active, ce
:param options: :param options:
:param description: :param description:
:param active: :param active:
:param certificates: :param added_certificates:
:rtype : Notification :param removed_certificates:
:rtype: Notification
:return: :return:
""" """
notification = get(notification_id) notification = get(notification_id)
@ -125,7 +126,8 @@ def update(notification_id, label, plugin_name, options, description, active, ce
notification.options = options notification.options = options
notification.description = description notification.description = description
notification.active = active notification.active = active
notification.certificates = certificates notification.certificates = notification.certificates + added_certificates
notification.certificates = [c for c in notification.certificates if c not in removed_certificates]
return database.update(notification) return database.update(notification)
@ -144,7 +146,7 @@ def get(notification_id):
Retrieves an notification by its lemur assigned ID. Retrieves an notification by its lemur assigned ID.
:param notification_id: Lemur assigned ID :param notification_id: Lemur assigned ID
:rtype : Notification :rtype: Notification
:return: :return:
""" """
return database.get(Notification, notification_id) return database.get(Notification, notification_id)

View File

@ -117,7 +117,7 @@ class NotificationsList(AuthenticatedResource):
""" """
.. http:post:: /notifications .. http:post:: /notifications
Creates a new account Creates a new notification
**Example request**: **Example request**:
@ -214,9 +214,12 @@ class NotificationsList(AuthenticatedResource):
"id": 2 "id": 2
} }
:arg accountNumber: aws account number :label label: notification name
:arg label: human readable account label :label slug: notification plugin slug
:arg comments: some description about the account :label plugin_options: notification plugin options
:label description: notification description
:label active: whether or not the notification is active/enabled
:label certificates: certificates to attach to notification
:reqheader Authorization: OAuth token to authenticate :reqheader Authorization: OAuth token to authenticate
:statuscode 200: no error :statuscode 200: no error
""" """
@ -239,7 +242,7 @@ class Notifications(AuthenticatedResource):
""" """
.. http:get:: /notifications/1 .. http:get:: /notifications/1
Get a specific account Get a specific notification
**Example request**: **Example request**:
@ -306,17 +309,29 @@ class Notifications(AuthenticatedResource):
""" """
.. http:put:: /notifications/1 .. http:put:: /notifications/1
Updates an account Updates a notification
**Example request**: **Example request**:
.. sourcecode:: http .. sourcecode:: http
POST /notifications/1 HTTP/1.1 PUT /notifications/1 HTTP/1.1
Host: example.com Host: example.com
Accept: application/json, text/javascript Accept: application/json, text/javascript
Content-Type: application/json;charset=UTF-8 Content-Type: application/json;charset=UTF-8
{
"label": "labelChanged",
"plugin": {
"slug": "email-notification",
"plugin_options": "???"
},
"description": "Sample notification",
"active": "true",
"added_certificates": "???",
"removed_certificates": "???"
}
**Example response**: **Example response**:
@ -328,14 +343,24 @@ class Notifications(AuthenticatedResource):
{ {
"id": 1, "id": 1,
"accountNumber": 11111111111,
"label": "labelChanged", "label": "labelChanged",
"comments": "this is a thing" "plugin": {
"slug": "email-notification",
"plugin_options": "???"
},
"description": "Sample notification",
"active": "true",
"added_certificates": "???",
"removed_certificates": "???"
} }
:arg accountNumber: aws account number :label label: notification name
:arg label: human readable account label :label slug: notification plugin slug
:arg comments: some description about the account :label plugin_options: notification plugin options
:label description: notification description
:label active: whether or not the notification is active/enabled
:label added_certificates: certificates to add
:label removed_certificates: certificates to remove
:reqheader Authorization: OAuth token to authenticate :reqheader Authorization: OAuth token to authenticate
:statuscode 200: no error :statuscode 200: no error
""" """
@ -346,7 +371,8 @@ class Notifications(AuthenticatedResource):
data["plugin"]["plugin_options"], data["plugin"]["plugin_options"],
data["description"], data["description"],
data["active"], data["active"],
data["certificates"], data["added_certificates"],
data["removed_certificates"],
) )
def delete(self, notification_id): def delete(self, notification_id):

View File

@ -93,11 +93,10 @@ def get_pending_certs(pending_ids):
def create_certificate(pending_certificate, certificate, user): def create_certificate(pending_certificate, certificate, user):
""" """
Create and store a certificate with pending certificate's info Create and store a certificate with pending certificate's info
Args:
pending_certificate: PendingCertificate which will populate the certificate :arg pending_certificate: PendingCertificate which will populate the certificate
certificate: dict from Authority, which contains the body, chain and external id :arg certificate: dict from Authority, which contains the body, chain and external id
user: User that called this function, used as 'creator' of the certificate if it does :arg user: User that called this function, used as 'creator' of the certificate if it does not have an owner
not have an owner
""" """
certificate["owner"] = pending_certificate.owner certificate["owner"] = pending_certificate.owner
data, errors = CertificateUploadInputSchema().load(certificate) data, errors = CertificateUploadInputSchema().load(certificate)
@ -158,9 +157,9 @@ def cancel(pending_certificate, **kwargs):
""" """
Cancel a pending certificate. A check should be done prior to this function to decide to Cancel a pending certificate. A check should be done prior to this function to decide to
revoke the certificate or just abort cancelling. revoke the certificate or just abort cancelling.
Args:
pending_certificate: PendingCertificate to be cancelled :arg pending_certificate: PendingCertificate to be cancelled
Returns: the pending certificate if successful, raises Exception if there was an issue :return: the pending certificate if successful, raises Exception if there was an issue
""" """
plugin = plugins.get(pending_certificate.authority.plugin_name) plugin = plugins.get(pending_certificate.authority.plugin_name)
plugin.cancel_ordered_certificate(pending_certificate, **kwargs) plugin.cancel_ordered_certificate(pending_certificate, **kwargs)

View File

@ -221,7 +221,7 @@ class PendingCertificates(AuthenticatedResource):
.. sourcecode:: http .. sourcecode:: http
PUT /pending certificates/1 HTTP/1.1 PUT /pending_certificates/1 HTTP/1.1
Host: example.com Host: example.com
Accept: application/json, text/javascript Accept: application/json, text/javascript
Content-Type: application/json;charset=UTF-8 Content-Type: application/json;charset=UTF-8
@ -338,7 +338,7 @@ class PendingCertificates(AuthenticatedResource):
.. sourcecode:: http .. sourcecode:: http
DELETE /pending certificates/1 HTTP/1.1 DELETE /pending_certificates/1 HTTP/1.1
Host: example.com Host: example.com
Accept: application/json, text/javascript Accept: application/json, text/javascript

View File

@ -31,6 +31,11 @@ class ExportDestinationPlugin(DestinationPlugin):
@property @property
def options(self): def options(self):
"""
Gets/sets options for the plugin.
:return:
"""
return self.default_options + self.additional_options return self.default_options + self.additional_options
def export(self, body, private_key, cert_chain, options): def export(self, body, private_key, cert_chain, options):

View File

@ -57,6 +57,11 @@ class ExpirationNotificationPlugin(NotificationPlugin):
@property @property
def options(self): def options(self):
"""
Gets/sets options for the plugin.
:return:
"""
return self.default_options + self.additional_options return self.default_options + self.additional_options
def send(self, notification_type, message, excluded_targets, options, **kwargs): def send(self, notification_type, message, excluded_targets, options, **kwargs):

View File

@ -33,4 +33,9 @@ class SourcePlugin(Plugin):
@property @property
def options(self): def options(self):
"""
Gets/sets options for the plugin.
:return:
"""
return self.default_options + self.additional_options return self.default_options + self.additional_options

View File

@ -23,6 +23,7 @@ from acme import challenges, errors, messages
from acme.client import BackwardsCompatibleClientV2, ClientNetwork from acme.client import BackwardsCompatibleClientV2, ClientNetwork
from acme.errors import TimeoutError from acme.errors import TimeoutError
from acme.messages import Error as AcmeError from acme.messages import Error as AcmeError
from certbot import crypto_util as acme_crypto_util
from flask import current_app from flask import current_app
from lemur.common.utils import generate_private_key from lemur.common.utils import generate_private_key
@ -71,7 +72,7 @@ class AcmeHandler(object):
return False return False
def strip_wildcard(self, host): def strip_wildcard(self, host):
"""Removes the leading *. and returns Host and whether it was removed or not (True/False)""" """Removes the leading wildcard and returns Host and whether it was removed or not (True/False)"""
prefix = "*." prefix = "*."
if host.startswith(prefix): if host.startswith(prefix):
return host[len(prefix):], True return host[len(prefix):], True
@ -92,7 +93,8 @@ class AcmeHandler(object):
deadline = datetime.datetime.now() + datetime.timedelta(seconds=360) deadline = datetime.datetime.now() + datetime.timedelta(seconds=360)
try: try:
orderr = acme_client.poll_and_finalize(order, deadline) orderr = acme_client.poll_authorizations(order, deadline)
orderr = acme_client.finalize_order(orderr, deadline, fetch_alternative_chains=True)
except (AcmeError, TimeoutError): except (AcmeError, TimeoutError):
sentry.captureException(extra={"order_url": str(order.uri)}) sentry.captureException(extra={"order_url": str(order.uri)})
@ -112,14 +114,23 @@ class AcmeHandler(object):
f"Successfully resolved Acme order: {order.uri}", exc_info=True f"Successfully resolved Acme order: {order.uri}", exc_info=True
) )
pem_certificate, pem_certificate_chain = self.extract_cert_and_chain(orderr.fullchain_pem) pem_certificate, pem_certificate_chain = self.extract_cert_and_chain(orderr.fullchain_pem,
orderr.alternative_fullchains_pem)
current_app.logger.debug( current_app.logger.debug(
"{0} {1}".format(type(pem_certificate), type(pem_certificate_chain)) "{0} {1}".format(type(pem_certificate), type(pem_certificate_chain))
) )
return pem_certificate, pem_certificate_chain return pem_certificate, pem_certificate_chain
def extract_cert_and_chain(self, fullchain_pem): def extract_cert_and_chain(self, fullchain_pem, alternative_fullchains_pem, preferred_issuer=None):
if not preferred_issuer:
preferred_issuer = current_app.config.get("ACME_PREFERRED_ISSUER", None)
if preferred_issuer:
# returns first chain if not match
fullchain_pem = acme_crypto_util.find_chain_with_issuer([fullchain_pem] + alternative_fullchains_pem,
preferred_issuer)
pem_certificate = OpenSSL.crypto.dump_certificate( pem_certificate = OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_PEM,
OpenSSL.crypto.load_certificate( OpenSSL.crypto.load_certificate(
@ -127,12 +138,7 @@ class AcmeHandler(object):
), ),
).decode() ).decode()
if current_app.config.get("IDENTRUST_CROSS_SIGNED_LE_ICA", False) \ pem_certificate_chain = fullchain_pem[len(pem_certificate):].lstrip()
and datetime.datetime.now() < datetime.datetime.strptime(
current_app.config.get("IDENTRUST_CROSS_SIGNED_LE_ICA_EXPIRATION_DATE", "17/03/21"), '%d/%m/%y'):
pem_certificate_chain = current_app.config.get("IDENTRUST_CROSS_SIGNED_LE_ICA")
else:
pem_certificate_chain = fullchain_pem[len(pem_certificate):].lstrip()
return pem_certificate, pem_certificate_chain return pem_certificate, pem_certificate_chain

View File

@ -119,8 +119,10 @@ class AcmeHttpChallenge(AcmeChallenge):
current_app.logger.info("Uploaded HTTP-01 challenge tokens, trying to poll and finalize the order") current_app.logger.info("Uploaded HTTP-01 challenge tokens, trying to poll and finalize the order")
try: try:
finalized_orderr = acme_client.poll_and_finalize(orderr, deadline = datetime.datetime.now() + datetime.timedelta(seconds=90)
datetime.datetime.now() + datetime.timedelta(seconds=90)) orderr = acme_client.poll_authorizations(orderr, deadline)
finalized_orderr = acme_client.finalize_order(orderr, deadline, fetch_alternative_chains=True)
except errors.ValidationError as validationError: except errors.ValidationError as validationError:
for authz in validationError.failed_authzrs: for authz in validationError.failed_authzrs:
for chall in authz.body.challenges: for chall in authz.body.challenges:
@ -130,7 +132,8 @@ class AcmeHttpChallenge(AcmeChallenge):
ERROR_CODES[chall.error.code])) ERROR_CODES[chall.error.code]))
raise Exception('Validation error occured, can\'t complete challenges. See logs for more information.') raise Exception('Validation error occured, can\'t complete challenges. See logs for more information.')
pem_certificate, pem_certificate_chain = self.acme.extract_cert_and_chain(finalized_orderr.fullchain_pem) pem_certificate, pem_certificate_chain = self.acme.extract_cert_and_chain(finalized_orderr.fullchain_pem,
finalized_orderr.alternative_fullchains_pem)
if len(deployed_challenges) != 0: if len(deployed_challenges) != 0:
for token_path in deployed_challenges: for token_path in deployed_challenges:

View File

@ -5,6 +5,12 @@ from flask import Flask
from cryptography.x509 import DNSName from cryptography.x509 import DNSName
from lemur.plugins.lemur_acme import acme_handlers from lemur.plugins.lemur_acme import acme_handlers
from lemur.tests.vectors import (
ACME_CHAIN_SHORT_STR,
ACME_CHAIN_LONG_STR,
SAN_CERT_STR,
)
class TestAcmeHandler(unittest.TestCase): class TestAcmeHandler(unittest.TestCase):
def setUp(self): def setUp(self):
@ -110,3 +116,18 @@ class TestAcmeHandler(unittest.TestCase):
self.assertEqual( self.assertEqual(
result, [options["common_name"], "test2.netflix.net"] result, [options["common_name"], "test2.netflix.net"]
) )
def test_extract_cert_and_chain(self):
# expecting the short chain
leaf_pem, chain_pem = self.acme.extract_cert_and_chain(ACME_CHAIN_SHORT_STR,
[ACME_CHAIN_LONG_STR],
"(STAGING) Artificial Apricot R3")
self.assertEqual(leaf_pem, SAN_CERT_STR)
self.assertEqual(chain_pem, ACME_CHAIN_SHORT_STR[len(leaf_pem):].lstrip())
# expecting the long chain
leaf_pem, chain_pem = self.acme.extract_cert_and_chain(ACME_CHAIN_SHORT_STR,
[ACME_CHAIN_LONG_STR],
"(STAGING) Doctored Durian Root CA X3")
self.assertEqual(leaf_pem, SAN_CERT_STR)
self.assertEqual(chain_pem, ACME_CHAIN_LONG_STR[len(leaf_pem):].lstrip())

View File

@ -146,7 +146,8 @@ Q9ePRFBCiXOQ6wPLoUhrrbZ8LpFUFYDXHMtYM7P9sc9IAWoONXREJaO08zgFtMp4
idWw1VrejtwclobqNMVtG3EiPUIpJGpbMcJgbiLSmKkrvQtGng== idWw1VrejtwclobqNMVtG3EiPUIpJGpbMcJgbiLSmKkrvQtGng==
-----END CERTIFICATE----- -----END CERTIFICATE-----
""" """
mock_client.poll_and_finalize.return_value = mock_finalized_order mock_finalized_order.alternative_fullchains_pem = [mock_finalized_order.fullchain_pem]
mock_client.finalize_order.return_value = mock_finalized_order
mock_acme.return_value = (mock_client, "") mock_acme.return_value = (mock_client, "")

View File

@ -450,7 +450,8 @@ class S3DestinationPlugin(ExportDestinationPlugin):
def upload_acme_token(self, token_path, token, options, **kwargs): def upload_acme_token(self, token_path, token, options, **kwargs):
""" """
This is called from the acme http challenge This is called from the acme http challenge
:param self: :param self:
:param token_path: :param token_path:
:param token: :param token:
@ -563,4 +564,4 @@ class SNSNotificationPlugin(ExpirationNotificationPlugin):
f"{self.get_option('topicName', options)}" f"{self.get_option('topicName', options)}"
current_app.logger.info(f"Publishing {notification_type} notification to topic {topic_arn}") current_app.logger.info(f"Publishing {notification_type} notification to topic {topic_arn}")
sns.publish(topic_arn, message, notification_type, region_name=self.get_option("region", options)) sns.publish(topic_arn, message, notification_type, options, region_name=self.get_option("region", options))

View File

@ -11,21 +11,24 @@ import arrow
import boto3 import boto3
from flask import current_app from flask import current_app
from lemur.plugins.bases import ExpirationNotificationPlugin
def publish(topic_arn, certificates, notification_type, **kwargs):
def publish(topic_arn, certificates, notification_type, options, **kwargs):
sns_client = boto3.client("sns", **kwargs) sns_client = boto3.client("sns", **kwargs)
message_ids = {} message_ids = {}
subject = "Lemur: {0} Notification".format(notification_type.capitalize()) subject = "Lemur: {0} Notification".format(notification_type.capitalize())
for certificate in certificates: for certificate in certificates:
message_ids[certificate["name"]] = publish_single(sns_client, topic_arn, certificate, notification_type, subject) message_ids[certificate["name"]] = publish_single(sns_client, topic_arn, certificate, notification_type,
subject, options)
return message_ids return message_ids
def publish_single(sns_client, topic_arn, certificate, notification_type, subject): def publish_single(sns_client, topic_arn, certificate, notification_type, subject, options):
response = sns_client.publish( response = sns_client.publish(
TopicArn=topic_arn, TopicArn=topic_arn,
Message=format_message(certificate, notification_type), Message=format_message(certificate, notification_type, options),
Subject=subject, Subject=subject,
) )
@ -46,7 +49,7 @@ def create_certificate_url(name):
) )
def format_message(certificate, notification_type): def format_message(certificate, notification_type, options):
json_message = { json_message = {
"notification_type": notification_type, "notification_type": notification_type,
"certificate_name": certificate["name"], "certificate_name": certificate["name"],
@ -57,4 +60,19 @@ def format_message(certificate, notification_type):
"owner": certificate["owner"], "owner": certificate["owner"],
"details": create_certificate_url(certificate["name"]) "details": create_certificate_url(certificate["name"])
} }
if notification_type == "expiration":
json_message["notification_interval_days"] = calculate_expiration_days(options)
return json.dumps(json_message) return json.dumps(json_message)
def calculate_expiration_days(options):
unit = ExpirationNotificationPlugin.get_option("unit", options)
interval = ExpirationNotificationPlugin.get_option("interval", options)
if unit == "weeks":
return interval * 7
elif unit == "months":
return interval * 30
elif unit == "days":
return interval

View File

@ -20,7 +20,13 @@ def sts_client(service, service_type="client"):
def decorator(f): def decorator(f):
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
sts = boto3.client("sts", config=config) if current_app.config.get("LEMUR_AWS_REGION"):
deployment_region = current_app.config.get("LEMUR_AWS_REGION")
sts = boto3.client('sts', region_name=deployment_region,
endpoint_url=f"https://sts.{deployment_region}.amazonaws.com/",
config=config)
else:
sts = boto3.client("sts", config=config)
arn = "arn:aws:iam::{0}:role/{1}".format( arn = "arn:aws:iam::{0}:role/{1}".format(
kwargs.pop("account_number"), kwargs.pop("account_number"),
current_app.config.get("LEMUR_INSTANCE_PROFILE", "Lemur"), current_app.config.get("LEMUR_INSTANCE_PROFILE", "Lemur"),

View File

@ -13,9 +13,31 @@ from lemur.tests.test_messaging import verify_sender_email
@mock_sns() @mock_sns()
def test_format(certificate, endpoint): def test_format_nonexpiration(certificate, endpoint):
data = [certificate_notification_output_schema.dump(certificate).data] data = [certificate_notification_output_schema.dump(certificate).data]
for certificate in data:
expected_message = {
"notification_type": "not-expiration",
"certificate_name": certificate["name"],
"expires": arrow.get(certificate["validityEnd"]).format("YYYY-MM-DDTHH:mm:ss"),
"issuer": certificate["issuer"],
"id": certificate["id"],
"endpoints_detected": 0,
"owner": certificate["owner"],
"details": "https://lemur.example.com/#/certificates/{name}".format(name=certificate["name"])
}
# We don't currently support any SNS notifications besides expiration;
# when we do, this test will probably need to be refactored.
# For now, this is a placeholder proving empty options works as long as it's not "expiration" type
assert expected_message == json.loads(format_message(certificate, "not-expiration", None))
@mock_sns()
def test_format_expiration(certificate, endpoint):
data = [certificate_notification_output_schema.dump(certificate).data]
options = get_options()
for certificate in data: for certificate in data:
expected_message = { expected_message = {
"notification_type": "expiration", "notification_type": "expiration",
@ -25,9 +47,10 @@ def test_format(certificate, endpoint):
"id": certificate["id"], "id": certificate["id"],
"endpoints_detected": 0, "endpoints_detected": 0,
"owner": certificate["owner"], "owner": certificate["owner"],
"details": "https://lemur.example.com/#/certificates/{name}".format(name=certificate["name"]) "details": "https://lemur.example.com/#/certificates/{name}".format(name=certificate["name"]),
"notification_interval_days": 10 # 10 days specified in options
} }
assert expected_message == json.loads(format_message(certificate, "expiration")) assert expected_message == json.loads(format_message(certificate, "expiration", options))
@mock_sns() @mock_sns()
@ -52,7 +75,7 @@ def test_publish(certificate, endpoint):
topic_arn, sqs_client, queue_url = create_and_subscribe_to_topic() topic_arn, sqs_client, queue_url = create_and_subscribe_to_topic()
message_ids = publish(topic_arn, data, "expiration", region_name="us-east-1") message_ids = publish(topic_arn, data, "expiration", get_options(), region_name="us-east-1")
assert len(message_ids) == len(data) assert len(message_ids) == len(data)
received_messages = sqs_client.receive_message(QueueUrl=queue_url)["Messages"] received_messages = sqs_client.receive_message(QueueUrl=queue_url)["Messages"]
@ -61,7 +84,7 @@ def test_publish(certificate, endpoint):
actual_message = next( actual_message = next(
(m for m in received_messages if json.loads(m["Body"])["MessageId"] == expected_message_id), None) (m for m in received_messages if json.loads(m["Body"])["MessageId"] == expected_message_id), None)
actual_json = json.loads(actual_message["Body"]) actual_json = json.loads(actual_message["Body"])
assert actual_json["Message"] == format_message(certificate, "expiration") assert actual_json["Message"] == format_message(certificate, "expiration", get_options())
assert actual_json["Subject"] == "Lemur: Expiration Notification" assert actual_json["Subject"] == "Lemur: Expiration Notification"
@ -98,7 +121,8 @@ def test_send_expiration_notification():
received_messages = sqs_client.receive_message(QueueUrl=queue_url)["Messages"] received_messages = sqs_client.receive_message(QueueUrl=queue_url)["Messages"]
assert len(received_messages) == 1 assert len(received_messages) == 1
expected_message = format_message(certificate_notification_output_schema.dump(certificate).data, "expiration") expected_message = format_message(certificate_notification_output_schema.dump(certificate).data, "expiration",
notification.options)
actual_message = json.loads(received_messages[0]["Body"])["Message"] actual_message = json.loads(received_messages[0]["Body"])["Message"]
assert actual_message == expected_message assert actual_message == expected_message

View File

@ -114,7 +114,7 @@ class RolesList(AuthenticatedResource):
"username": null, "username": null,
"password": null, "password": null,
"users": [ "users": [
{'id': 1} {"id": 1}
] ]
} }
@ -177,7 +177,7 @@ class RoleViewCredentials(AuthenticatedResource):
Content-Type: text/javascript Content-Type: text/javascript
{ {
"username: "ausername", "username": "ausername",
"password": "apassword" "password": "apassword"
} }

View File

@ -255,7 +255,7 @@ def create(label, plugin_name, options, description=None):
:param plugin_name: :param plugin_name:
:param options: :param options:
:param description: :param description:
:rtype : Source :rtype: Source
:return: New source :return: New source
""" """
source = Source( source = Source(
@ -273,7 +273,7 @@ def update(source_id, label, plugin_name, options, description):
:param options: :param options:
:param plugin_name: :param plugin_name:
:param description: :param description:
:rtype : Source :rtype: Source
:return: :return:
""" """
source = get(source_id) source = get(source_id)
@ -300,7 +300,7 @@ def get(source_id):
Retrieves an source by its lemur assigned ID. Retrieves an source by its lemur assigned ID.
:param source_id: Lemur assigned ID :param source_id: Lemur assigned ID
:rtype : Source :rtype: Source
:return: :return:
""" """
return database.get(Source, source_id) return database.get(Source, source_id)

View File

@ -8,10 +8,35 @@ angular.module('lemur')
if (this.certificates === undefined) { if (this.certificates === undefined) {
this.certificates = []; this.certificates = [];
} }
if (this.addedCertificates === undefined) {
this.addedCertificates = [];
}
if (_.some(this.addedCertificates, function (cert) {
return cert.id === certificate.id;
})) {
return;
}
this.certificates.push(certificate); this.certificates.push(certificate);
this.addedCertificates.push(certificate);
if (this.removedCertificates !== undefined) {
const indexInRemovedList = _.findIndex(this.removedCertificates, function (cert) {
return cert.id === certificate.id;
});
this.removedCertificates.splice(indexInRemovedList, 1);
}
}, },
removeCertificate: function (index) { removeCertificate: function (index) {
this.certificates.splice(index, 1); if (this.removedCertificates === undefined) {
this.removedCertificates = [];
}
const removedCert = this.certificates.splice(index, 1)[0];
this.removedCertificates.push(removedCert);
if (this.addedCertificates !== undefined) {
const indexInAddedList = _.findIndex(this.addedCertificates, function (cert) {
return cert.id === removedCert.id;
});
this.addedCertificates.splice(indexInAddedList, 1);
}
} }
}); });
}); });

View File

@ -201,6 +201,7 @@ ACME_EMAIL = "jim@example.com"
ACME_TEL = "4088675309" ACME_TEL = "4088675309"
ACME_DIRECTORY_URL = "https://acme-v01.api.letsencrypt.org" ACME_DIRECTORY_URL = "https://acme-v01.api.letsencrypt.org"
ACME_DISABLE_AUTORESOLVE = True ACME_DISABLE_AUTORESOLVE = True
ACME_PREFERRED_ISSUER = "R3"
LDAP_AUTH = True LDAP_AUTH = True
LDAP_BIND_URI = "ldap://localhost" LDAP_BIND_URI = "ldap://localhost"

View File

@ -84,6 +84,25 @@ def test_get_by_serial(session, certificate):
assert found assert found
def test_get_all_certs_attached_to_endpoint_without_autorotate(session):
from lemur.certificates.service import get_all_certs_attached_to_endpoint_without_autorotate, \
cleanup_after_revoke
from lemur.tests.factories import EndpointFactory
# add a certificate with endpoint
EndpointFactory()
list_before = get_all_certs_attached_to_endpoint_without_autorotate()
len_list_before = len(list_before)
assert len_list_before > 0
# revoked the first certificate
first_cert_with_endpoint = list_before[0]
cleanup_after_revoke(first_cert_with_endpoint)
list_after = get_all_certs_attached_to_endpoint_without_autorotate()
assert len(list_after) + 1 == len_list_before
def test_delete_cert(session): def test_delete_cert(session):
from lemur.certificates.service import delete, get from lemur.certificates.service import delete, get
from lemur.tests.factories import CertificateFactory from lemur.tests.factories import CertificateFactory

View File

@ -32,7 +32,7 @@ def test_rotate_certificate(client, source_plugin):
) )
def test_endpoint_get(client, token, status): def test_endpoint_get(client, token, status):
assert ( assert (
client.get(api.url_for(Endpoints, endpoint_id=1), headers=token).status_code client.get(api.url_for(Endpoints, endpoint_id=2), headers=token).status_code
== status == status
) )

View File

@ -587,3 +587,102 @@ zwKDoqAD+L4wEg8d890Zy2mbzJnDu2HQiMIROaBldKEAMQA=
""" """
CERT_CHAIN_PKCS7_PEM = CERT_CHAIN_PKCS7_STR.encode('utf-8') CERT_CHAIN_PKCS7_PEM = CERT_CHAIN_PKCS7_STR.encode('utf-8')
ACME_CHAIN_LONG_STR = SAN_CERT_STR + """
-----BEGIN CERTIFICATE-----
MIIFWzCCA0OgAwIBAgIQTfQrldHumzpMLrM7jRBd1jANBgkqhkiG9w0BAQsFADBm
MQswCQYDVQQGEwJVUzEzMDEGA1UEChMqKFNUQUdJTkcpIEludGVybmV0IFNlY3Vy
aXR5IFJlc2VhcmNoIEdyb3VwMSIwIAYDVQQDExkoU1RBR0lORykgUHJldGVuZCBQ
ZWFyIFgxMB4XDTIwMDkwNDAwMDAwMFoXDTI1MDkxNTE2MDAwMFowWTELMAkGA1UE
BhMCVVMxIDAeBgNVBAoTFyhTVEFHSU5HKSBMZXQncyBFbmNyeXB0MSgwJgYDVQQD
Ex8oU1RBR0lORykgQXJ0aWZpY2lhbCBBcHJpY290IFIzMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAu6TR8+74b46mOE1FUwBrvxzEYLck3iasmKrcQkb+
gy/z9Jy7QNIAl0B9pVKp4YU76JwxF5DOZZhi7vK7SbCkK6FbHlyU5BiDYIxbbfvO
L/jVGqdsSjNaJQTg3C3XrJja/HA4WCFEMVoT2wDZm8ABC1N+IQe7Q6FEqc8NwmTS
nmmRQm4TQvr06DP+zgFK/MNubxWWDSbSKKTH5im5j2fZfg+j/tM1bGaczFWw8/lS
nukyn5J2L+NJYnclzkXoh9nMFnyPmVbfyDPOc4Y25aTzVoeBKXa/cZ5MM+WddjdL
biWvm19f1sYn1aRaAIrkppv7kkn83vcth8XCG39qC2ZvaQIDAQABo4IBEDCCAQww
DgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAS
BgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBTecnpI3zHDplDfn4Uj31c3S10u
ZTAfBgNVHSMEGDAWgBS182Xy/rAKkh/7PH3zRKCsYyXDFDA2BggrBgEFBQcBAQQq
MCgwJgYIKwYBBQUHMAKGGmh0dHA6Ly9zdGcteDEuaS5sZW5jci5vcmcvMCsGA1Ud
HwQkMCIwIKAeoByGGmh0dHA6Ly9zdGcteDEuYy5sZW5jci5vcmcvMCIGA1UdIAQb
MBkwCAYGZ4EMAQIBMA0GCysGAQQBgt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCN
DLam9yN0EFxxn/3p+ruWO6n/9goCAM5PT6cC6fkjMs4uas6UGXJjr5j7PoTQf3C1
vuxiIGRJC6qxV7yc6U0X+w0Mj85sHI5DnQVWN5+D1er7mp13JJA0xbAbHa3Rlczn
y2Q82XKui8WHuWra0gb2KLpfboYj1Ghgkhr3gau83pC/WQ8HfkwcvSwhIYqTqxoZ
Uq8HIf3M82qS9aKOZE0CEmSyR1zZqQxJUT7emOUapkUN9poJ9zGc+FgRZvdro0XB
yphWXDaqMYph0DxW/10ig5j4xmmNDjCRmqIKsKoWA52wBTKKXK1na2ty/lW5dhtA
xkz5rVZFd4sgS4J0O+zm6d5GRkWsNJ4knotGXl8vtS3X40KXeb3A5+/3p0qaD215
Xq8oSNORfB2oI1kQuyEAJ5xvPTdfwRlyRG3lFYodrRg6poUBD/8fNTXMtzydpRgy
zUQZh/18F6B/iW6cbiRN9r2Hkh05Om+q0/6w0DdZe+8YrNpfhSObr/1eVZbKGMIY
qKmyZbBNu5ysENIK5MPc14mUeKmFjpN840VR5zunoU52lqpLDua/qIM8idk86xGW
xx2ml43DO/Ya/tVZVok0mO0TUjzJIfPqyvr455IsIut4RlCR9Iq0EDTve2/ZwCuG
hSjpTUFGSiQrR2JK2Evp+o6AETUkBCO1aw0PpQBPDQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFVDCCBDygAwIBAgIRAO1dW8lt+99NPs1qSY3Rs8cwDQYJKoZIhvcNAQELBQAw
cTELMAkGA1UEBhMCVVMxMzAxBgNVBAoTKihTVEFHSU5HKSBJbnRlcm5ldCBTZWN1
cml0eSBSZXNlYXJjaCBHcm91cDEtMCsGA1UEAxMkKFNUQUdJTkcpIERvY3RvcmVk
IER1cmlhbiBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQw
M1owZjELMAkGA1UEBhMCVVMxMzAxBgNVBAoTKihTVEFHSU5HKSBJbnRlcm5ldCBT
ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEiMCAGA1UEAxMZKFNUQUdJTkcpIFByZXRl
bmQgUGVhciBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALbagEdD
Ta1QgGBWSYkyMhscZXENOBaVRTMX1hceJENgsL0Ma49D3MilI4KS38mtkmdF6cPW
nL++fgehT0FbRHZgjOEr8UAN4jH6omjrbTD++VZneTsMVaGamQmDdFl5g1gYaigk
kmx8OiCO68a4QXg4wSyn6iDipKP8utsE+x1E28SA75HOYqpdrk4HGxuULvlr03wZ
GTIf/oRt2/c+dYmDoaJhge+GOrLAEQByO7+8+vzOwpNAPEx6LW+crEEZ7eBXih6V
P19sTGy3yfqK5tPtTdXXCOQMKAp+gCj/VByhmIr+0iNDC540gtvV303WpcbwnkkL
YC0Ft2cYUyHtkstOfRcRO+K2cZozoSwVPyB8/J9RpcRK3jgnX9lujfwA/pAbP0J2
UPQFxmWFRQnFjaq6rkqbNEBgLy+kFL1NEsRbvFbKrRi5bYy2lNms2NJPZvdNQbT/
2dBZKmJqxHkxCuOQFjhJQNeO+Njm1Z1iATS/3rts2yZlqXKsxQUzN6vNbD8KnXRM
EeOXUYvbV4lqfCf8mS14WEbSiMy87GB5S9ucSV1XUrlTG5UGcMSZOBcEUpisRPEm
QWUOTWIoDQ5FOia/GI+Ki523r2ruEmbmG37EBSBXdxIdndqrjy+QVAmCebyDx9eV
EGOIpn26bW5LKerumJxa/CFBaKi4bRvmdJRLAgMBAAGjgfEwge4wDgYDVR0PAQH/
BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLXzZfL+sAqSH/s8ffNE
oKxjJcMUMB8GA1UdIwQYMBaAFAhX2onHolN5DE/d4JCPdLriJ3NEMDgGCCsGAQUF
BwEBBCwwKjAoBggrBgEFBQcwAoYcaHR0cDovL3N0Zy1kc3QzLmkubGVuY3Iub3Jn
LzAtBgNVHR8EJjAkMCKgIKAehhxodHRwOi8vc3RnLWRzdDMuYy5sZW5jci5vcmcv
MCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQBgt8TAQEBMA0GCSqGSIb3DQEB
CwUAA4IBAQB7tR8B0eIQSS6MhP5kuvGth+dN02DsIhr0yJtk2ehIcPIqSxRRmHGl
4u2c3QlvEpeRDp2w7eQdRTlI/WnNhY4JOofpMf2zwABgBWtAu0VooQcZZTpQruig
F/z6xYkBk3UHkjeqxzMN3d1EqGusxJoqgdTouZ5X5QTTIee9nQ3LEhWnRSXDx7Y0
ttR1BGfcdqHopO4IBqAhbkKRjF5zj7OD8cG35omywUbZtOJnftiI0nFcRaxbXo0v
oDfLD0S6+AC2R3tKpqjkNX6/91hrRFglUakyMcZU/xleqbv6+Lr3YD8PsBTub6lI
oZ2lS38fL18Aon458fbc0BPHtenfhKj5
-----END CERTIFICATE-----
"""
ACME_CHAIN_SHORT_STR = SAN_CERT_STR + """
-----BEGIN CERTIFICATE-----
MIIFWzCCA0OgAwIBAgIQTfQrldHumzpMLrM7jRBd1jANBgkqhkiG9w0BAQsFADBm
MQswCQYDVQQGEwJVUzEzMDEGA1UEChMqKFNUQUdJTkcpIEludGVybmV0IFNlY3Vy
aXR5IFJlc2VhcmNoIEdyb3VwMSIwIAYDVQQDExkoU1RBR0lORykgUHJldGVuZCBQ
ZWFyIFgxMB4XDTIwMDkwNDAwMDAwMFoXDTI1MDkxNTE2MDAwMFowWTELMAkGA1UE
BhMCVVMxIDAeBgNVBAoTFyhTVEFHSU5HKSBMZXQncyBFbmNyeXB0MSgwJgYDVQQD
Ex8oU1RBR0lORykgQXJ0aWZpY2lhbCBBcHJpY290IFIzMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAu6TR8+74b46mOE1FUwBrvxzEYLck3iasmKrcQkb+
gy/z9Jy7QNIAl0B9pVKp4YU76JwxF5DOZZhi7vK7SbCkK6FbHlyU5BiDYIxbbfvO
L/jVGqdsSjNaJQTg3C3XrJja/HA4WCFEMVoT2wDZm8ABC1N+IQe7Q6FEqc8NwmTS
nmmRQm4TQvr06DP+zgFK/MNubxWWDSbSKKTH5im5j2fZfg+j/tM1bGaczFWw8/lS
nukyn5J2L+NJYnclzkXoh9nMFnyPmVbfyDPOc4Y25aTzVoeBKXa/cZ5MM+WddjdL
biWvm19f1sYn1aRaAIrkppv7kkn83vcth8XCG39qC2ZvaQIDAQABo4IBEDCCAQww
DgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAS
BgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBTecnpI3zHDplDfn4Uj31c3S10u
ZTAfBgNVHSMEGDAWgBS182Xy/rAKkh/7PH3zRKCsYyXDFDA2BggrBgEFBQcBAQQq
MCgwJgYIKwYBBQUHMAKGGmh0dHA6Ly9zdGcteDEuaS5sZW5jci5vcmcvMCsGA1Ud
HwQkMCIwIKAeoByGGmh0dHA6Ly9zdGcteDEuYy5sZW5jci5vcmcvMCIGA1UdIAQb
MBkwCAYGZ4EMAQIBMA0GCysGAQQBgt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCN
DLam9yN0EFxxn/3p+ruWO6n/9goCAM5PT6cC6fkjMs4uas6UGXJjr5j7PoTQf3C1
vuxiIGRJC6qxV7yc6U0X+w0Mj85sHI5DnQVWN5+D1er7mp13JJA0xbAbHa3Rlczn
y2Q82XKui8WHuWra0gb2KLpfboYj1Ghgkhr3gau83pC/WQ8HfkwcvSwhIYqTqxoZ
Uq8HIf3M82qS9aKOZE0CEmSyR1zZqQxJUT7emOUapkUN9poJ9zGc+FgRZvdro0XB
yphWXDaqMYph0DxW/10ig5j4xmmNDjCRmqIKsKoWA52wBTKKXK1na2ty/lW5dhtA
xkz5rVZFd4sgS4J0O+zm6d5GRkWsNJ4knotGXl8vtS3X40KXeb3A5+/3p0qaD215
Xq8oSNORfB2oI1kQuyEAJ5xvPTdfwRlyRG3lFYodrRg6poUBD/8fNTXMtzydpRgy
zUQZh/18F6B/iW6cbiRN9r2Hkh05Om+q0/6w0DdZe+8YrNpfhSObr/1eVZbKGMIY
qKmyZbBNu5ysENIK5MPc14mUeKmFjpN840VR5zunoU52lqpLDua/qIM8idk86xGW
xx2ml43DO/Ya/tVZVok0mO0TUjzJIfPqyvr455IsIut4RlCR9Iq0EDTve2/ZwCuG
hSjpTUFGSiQrR2JK2Evp+o6AETUkBCO1aw0PpQBPDQ==
-----END CERTIFICATE-----
"""

View File

@ -101,7 +101,7 @@ class UsersList(AuthenticatedResource):
Creates a new user Creates a new user
**Example request**: **Example request with ID**:
.. sourcecode:: http .. sourcecode:: http
@ -115,7 +115,25 @@ class UsersList(AuthenticatedResource):
"email": "user3@example.com", "email": "user3@example.com",
"active": true, "active": true,
"roles": [ "roles": [
{'id': 1} - or - {'name': 'myRole'} {"id": 1}
]
}
**Example request with name**:
.. sourcecode:: http
POST /users HTTP/1.1
Host: example.com
Accept: application/json, text/javascript
Content-Type: application/json;charset=UTF-8
{
"username": "user3",
"email": "user3@example.com",
"active": true,
"roles": [
{"name": "myRole"}
] ]
} }
@ -130,7 +148,7 @@ class UsersList(AuthenticatedResource):
{ {
"id": 3, "id": 3,
"active": True, "active": True,
"email": "user3@example.com, "email": "user3@example.com",
"username": "user3", "username": "user3",
"profileImage": null "profileImage": null
} }
@ -202,7 +220,7 @@ class Users(AuthenticatedResource):
Update a user Update a user
**Example request**: **Example request with ID**:
.. sourcecode:: http .. sourcecode:: http
@ -216,7 +234,25 @@ class Users(AuthenticatedResource):
"email": "user1@example.com", "email": "user1@example.com",
"active": false, "active": false,
"roles": [ "roles": [
{'id': 1} - or - {'name': 'myRole'} {"id": 1}
]
}
**Example request with name**:
.. sourcecode:: http
PUT /users/1 HTTP/1.1
Host: example.com
Accept: application/json, text/javascript
Content-Type: application/json;charset=UTF-8
{
"username": "user1",
"email": "user1@example.com",
"active": false,
"roles": [
{"name": "myRole"}
] ]
} }

View File

@ -18,7 +18,7 @@ chardet==3.0.4
# via requests # via requests
colorama==0.4.3 colorama==0.4.3
# via twine # via twine
cryptography==3.4.5 cryptography==3.4.6
# via secretstorage # via secretstorage
distlib==0.3.0 distlib==0.3.0
# via virtualenv # via virtualenv
@ -50,7 +50,7 @@ packaging==20.9
# via bleach # via bleach
pkginfo==1.5.0.1 pkginfo==1.5.0.1
# via twine # via twine
pre-commit==2.10.1 pre-commit==2.11.1
# via -r requirements-dev.in # via -r requirements-dev.in
pycodestyle==2.6.0 pycodestyle==2.6.0
# via flake8 # via flake8

View File

@ -1,7 +1,51 @@
# Note: python-ldap from requirements breaks due to readthedocs.io not having the correct header files # Note: python-ldap from requirements breaks due to readthedocs.io not having the correct header files
# The `make up-reqs` will update all requirement text files, and forcibly remove python-ldap # The `make up-reqs` will update all requirement text files, and forcibly remove python-ldap
# from requirements-docs.txt # from requirements-docs.txt
# However, dependabot doesn't use `make up-reqs`, so `-r requirements.txt` has been removed completely. # However, dependabot doesn't use `make up-reqs`, so we have to replicate the necessary dependencies here
# Without including these dependencies, the docs are unable to include generated autodocs
acme
arrow
boto3
botocore
certbot
certsrv
CloudFlare
cryptography
dnspython3
dyn
Flask
Flask-Bcrypt
Flask-Cors
Flask-Mail
Flask-Migrate
Flask-Principal
Flask-RESTful
Flask-Script
Flask-SQLAlchemy
flask_replicated
gunicorn
hvac # required for the vault destination plugin
inflection
josepy
logmatic-python
marshmallow-sqlalchemy
marshmallow<2.20.5 #schema duplicate issues https://github.com/marshmallow-code/marshmallow-sqlalchemy/issues/121
paramiko # required for the SFTP destination plugin
pem
pyjks >= 19 # pyjks < 19 depends on pycryptodome, which conflicts with dyn's usage of pycrypto
pyjwt
pyOpenSSL
raven[flask]
redis
retrying
SQLAlchemy-Utils
tabulate
vine
xmltodict
# Test requirements are needed to allow test docs to build
-r requirements-tests.txt
# docs specific
sphinx sphinx
sphinxcontrib-httpdomain sphinxcontrib-httpdomain
sphinx-rtd-theme sphinx-rtd-theme

View File

@ -4,43 +4,513 @@
# #
# pip-compile --no-index --output-file=requirements-docs.txt requirements-docs.in # pip-compile --no-index --output-file=requirements-docs.txt requirements-docs.in
# #
acme==1.13.0
# via
# -r requirements-docs.in
# -r requirements-tests.txt
# certbot
alabaster==0.7.12 alabaster==0.7.12
# via sphinx # via sphinx
alembic==1.5.5
# via flask-migrate
aniso8601==9.0.0
# via flask-restful
appdirs==1.4.3
# via
# -r requirements-tests.txt
# black
arrow==1.0.3
# via -r requirements-docs.in
attrs==19.3.0
# via
# -r requirements-tests.txt
# jsonschema
# pytest
aws-sam-translator==1.22.0
# via
# -r requirements-tests.txt
# cfn-lint
aws-xray-sdk==2.5.0
# via
# -r requirements-tests.txt
# moto
babel==2.8.0 babel==2.8.0
# via sphinx # via sphinx
bandit==1.7.0
# via -r requirements-tests.txt
bcrypt==3.2.0
# via
# flask-bcrypt
# paramiko
beautifulsoup4==4.9.3
# via cloudflare
black==20.8b1
# via -r requirements-tests.txt
blinker==1.4
# via
# flask-mail
# flask-principal
# raven
boto3==1.17.27
# via
# -r requirements-docs.in
# -r requirements-tests.txt
# aws-sam-translator
# moto
boto==2.49.0
# via
# -r requirements-tests.txt
# moto
botocore==1.20.27
# via
# -r requirements-docs.in
# -r requirements-tests.txt
# aws-xray-sdk
# boto3
# moto
# s3transfer
certbot==1.13.0
# via
# -r requirements-docs.in
# -r requirements-tests.txt
certifi==2020.12.5 certifi==2020.12.5
# via requests # via
# -r requirements-tests.txt
# requests
certsrv==2.1.1
# via -r requirements-docs.in
cffi==1.14.0
# via
# -r requirements-tests.txt
# bcrypt
# cryptography
# pynacl
cfn-lint==0.29.5
# via
# -r requirements-tests.txt
# moto
chardet==3.0.4 chardet==3.0.4
# via requests # via
# -r requirements-tests.txt
# requests
click==7.1.2
# via
# -r requirements-tests.txt
# black
# flask
cloudflare==2.8.15
# via -r requirements-docs.in
configargparse==1.4
# via
# -r requirements-tests.txt
# certbot
configobj==5.0.6
# via
# -r requirements-tests.txt
# certbot
coverage==5.5
# via -r requirements-tests.txt
cryptography==3.4.6
# via
# -r requirements-docs.in
# -r requirements-tests.txt
# acme
# certbot
# josepy
# moto
# paramiko
# pyopenssl
# python-jose
# sshpubkeys
decorator==4.4.2
# via
# -r requirements-tests.txt
# networkx
distro==1.5.0
# via
# -r requirements-tests.txt
# certbot
dnspython3==1.15.0
# via -r requirements-docs.in
dnspython==1.15.0
# via dnspython3
docker==4.2.0
# via
# -r requirements-tests.txt
# moto
docutils==0.15.2 docutils==0.15.2
# via sphinx # via sphinx
dyn==1.8.1
# via -r requirements-docs.in
ecdsa==0.14.1
# via
# -r requirements-tests.txt
# moto
# python-jose
# sshpubkeys
factory-boy==3.2.0
# via -r requirements-tests.txt
faker==6.5.0
# via
# -r requirements-tests.txt
# factory-boy
fakeredis==1.4.5
# via -r requirements-tests.txt
flask-bcrypt==0.7.1
# via -r requirements-docs.in
flask-cors==3.0.10
# via -r requirements-docs.in
flask-mail==0.9.1
# via -r requirements-docs.in
flask-migrate==2.7.0
# via -r requirements-docs.in
flask-principal==0.4.0
# via -r requirements-docs.in
flask-replicated==1.4
# via -r requirements-docs.in
flask-restful==0.3.8
# via -r requirements-docs.in
flask-script==2.0.6
# via -r requirements-docs.in
flask-sqlalchemy==2.4.4
# via
# -r requirements-docs.in
# flask-migrate
flask==1.1.2
# via
# -r requirements-docs.in
# -r requirements-tests.txt
# flask-bcrypt
# flask-cors
# flask-mail
# flask-migrate
# flask-principal
# flask-restful
# flask-script
# flask-sqlalchemy
# pytest-flask
# raven
freezegun==1.1.0
# via -r requirements-tests.txt
future==0.18.2
# via
# -r requirements-tests.txt
# aws-xray-sdk
gitdb==4.0.4
# via
# -r requirements-tests.txt
# gitpython
gitpython==3.1.1
# via
# -r requirements-tests.txt
# bandit
gunicorn==20.0.4
# via -r requirements-docs.in
hvac==0.10.8
# via -r requirements-docs.in
idna==2.9 idna==2.9
# via requests # via
# -r requirements-tests.txt
# moto
# requests
imagesize==1.2.0 imagesize==1.2.0
# via sphinx # via sphinx
importlib-metadata==1.6.0
# via
# -r requirements-tests.txt
# jsonpickle
inflection==0.5.1
# via -r requirements-docs.in
iniconfig==1.0.1
# via
# -r requirements-tests.txt
# pytest
itsdangerous==1.1.0
# via
# -r requirements-tests.txt
# flask
javaobj-py3==0.4.2
# via pyjks
jinja2==2.11.3 jinja2==2.11.3
# via sphinx # via
# -r requirements-tests.txt
# flask
# moto
# sphinx
jmespath==0.9.5
# via
# -r requirements-tests.txt
# boto3
# botocore
josepy==1.7.0
# via
# -r requirements-docs.in
# -r requirements-tests.txt
# acme
# certbot
jsondiff==1.1.2
# via
# -r requirements-tests.txt
# moto
jsonlines==2.0.0
# via cloudflare
jsonpatch==1.25
# via
# -r requirements-tests.txt
# cfn-lint
jsonpickle==1.4
# via
# -r requirements-tests.txt
# aws-xray-sdk
jsonpointer==2.0
# via
# -r requirements-tests.txt
# jsonpatch
jsonschema==3.2.0
# via
# -r requirements-tests.txt
# aws-sam-translator
# cfn-lint
logmatic-python==0.1.7
# via -r requirements-docs.in
mako==1.1.4
# via alembic
markupsafe==1.1.1 markupsafe==1.1.1
# via jinja2 # via
# -r requirements-tests.txt
# jinja2
# mako
# moto
marshmallow-sqlalchemy==0.23.1
# via -r requirements-docs.in
marshmallow==2.20.4
# via
# -r requirements-docs.in
# marshmallow-sqlalchemy
mock==4.0.2
# via
# -r requirements-tests.txt
# moto
more-itertools==8.2.0
# via
# -r requirements-tests.txt
# moto
moto[ec2,elb,elbv2,iam,s3,ses,sns,sqs,sts]==1.3.16
# via -r requirements-tests.txt
mypy-extensions==0.4.3
# via
# -r requirements-tests.txt
# black
networkx==2.4
# via
# -r requirements-tests.txt
# cfn-lint
nose==1.3.7
# via -r requirements-tests.txt
packaging==20.3 packaging==20.3
# via sphinx # via
# -r requirements-tests.txt
# pytest
# sphinx
paramiko==2.7.2
# via -r requirements-docs.in
parsedatetime==2.6
# via
# -r requirements-tests.txt
# certbot
pathspec==0.8.0
# via
# -r requirements-tests.txt
# black
pbr==5.4.5
# via
# -r requirements-tests.txt
# stevedore
pem==21.1.0
# via -r requirements-docs.in
pluggy==0.13.1
# via
# -r requirements-tests.txt
# pytest
py==1.9.0
# via
# -r requirements-tests.txt
# pytest
pyasn1-modules==0.2.8
# via pyjks
pyasn1==0.4.8
# via
# -r requirements-tests.txt
# pyasn1-modules
# pyjks
# python-jose
# rsa
pycparser==2.20
# via
# -r requirements-tests.txt
# cffi
pycryptodomex==3.10.1
# via pyjks
pyflakes==2.2.0
# via -r requirements-tests.txt
pygments==2.6.1 pygments==2.6.1
# via sphinx # via sphinx
pyjks==20.0.0
# via -r requirements-docs.in
pyjwt==2.0.1
# via -r requirements-docs.in
pynacl==1.4.0
# via paramiko
pyopenssl==20.0.1
# via
# -r requirements-docs.in
# -r requirements-tests.txt
# acme
# josepy
pyparsing==2.4.7 pyparsing==2.4.7
# via packaging # via
# -r requirements-tests.txt
# packaging
pyrfc3339==1.1
# via
# -r requirements-tests.txt
# acme
# certbot
pyrsistent==0.16.0
# via
# -r requirements-tests.txt
# jsonschema
pytest-flask==1.2.0
# via -r requirements-tests.txt
pytest-mock==3.5.1
# via -r requirements-tests.txt
pytest==6.2.2
# via
# -r requirements-tests.txt
# pytest-flask
# pytest-mock
python-dateutil==2.8.1
# via
# -r requirements-tests.txt
# alembic
# arrow
# botocore
# faker
# freezegun
# moto
python-editor==1.0.4
# via alembic
python-jose[cryptography]==3.1.0
# via
# -r requirements-tests.txt
# moto
python-json-logger==2.0.1
# via logmatic-python
pytz==2019.3 pytz==2019.3
# via babel # via
# -r requirements-tests.txt
# acme
# babel
# certbot
# flask-restful
# moto
# pyrfc3339
pyyaml==5.4.1
# via
# -r requirements-tests.txt
# bandit
# cfn-lint
# cloudflare
# moto
raven[flask]==6.10.0
# via -r requirements-docs.in
redis==3.5.3
# via
# -r requirements-docs.in
# -r requirements-tests.txt
# fakeredis
regex==2020.4.4
# via
# -r requirements-tests.txt
# black
requests-mock==1.8.0
# via -r requirements-tests.txt
requests-toolbelt==0.9.1
# via
# -r requirements-tests.txt
# acme
requests==2.25.1 requests==2.25.1
# via sphinx # via
# -r requirements-tests.txt
# acme
# certsrv
# cloudflare
# docker
# hvac
# moto
# requests-mock
# requests-toolbelt
# responses
# sphinx
responses==0.10.12
# via
# -r requirements-tests.txt
# moto
retrying==1.3.3
# via -r requirements-docs.in
rsa==4.0
# via
# -r requirements-tests.txt
# python-jose
s3transfer==0.3.3
# via
# -r requirements-tests.txt
# boto3
six==1.15.0 six==1.15.0
# via # via
# -r requirements-tests.txt
# aws-sam-translator
# bandit
# bcrypt
# cfn-lint
# configobj
# docker
# ecdsa
# fakeredis
# flask-cors
# flask-restful
# hvac
# josepy
# jsonschema
# moto
# packaging # packaging
# pynacl
# pyopenssl
# pyrsistent
# python-dateutil
# python-jose
# requests-mock
# responses
# retrying
# sphinxcontrib-httpdomain # sphinxcontrib-httpdomain
# sqlalchemy-utils
# stevedore
# websocket-client
smmap==3.0.2
# via
# -r requirements-tests.txt
# gitdb
snowballstemmer==2.0.0 snowballstemmer==2.0.0
# via sphinx # via sphinx
sortedcontainers==2.1.0
# via
# -r requirements-tests.txt
# fakeredis
soupsieve==2.0.1
# via beautifulsoup4
sphinx-rtd-theme==0.5.1 sphinx-rtd-theme==0.5.1
# via -r requirements-docs.in # via -r requirements-docs.in
sphinx==3.5.0 sphinx==3.5.2
# via # via
# -r requirements-docs.in # -r requirements-docs.in
# sphinx-rtd-theme # sphinx-rtd-theme
@ -59,8 +529,104 @@ sphinxcontrib-qthelp==1.0.3
# via sphinx # via sphinx
sphinxcontrib-serializinghtml==1.1.4 sphinxcontrib-serializinghtml==1.1.4
# via sphinx # via sphinx
sqlalchemy-utils==0.36.8
# via -r requirements-docs.in
sqlalchemy==1.3.16
# via
# alembic
# flask-sqlalchemy
# marshmallow-sqlalchemy
# sqlalchemy-utils
sshpubkeys==3.1.0
# via
# -r requirements-tests.txt
# moto
stevedore==1.32.0
# via
# -r requirements-tests.txt
# bandit
tabulate==0.8.9
# via -r requirements-docs.in
text-unidecode==1.3
# via
# -r requirements-tests.txt
# faker
toml==0.10.1
# via
# -r requirements-tests.txt
# black
# pytest
twofish==0.3.0
# via pyjks
typed-ast==1.4.1
# via
# -r requirements-tests.txt
# black
typing-extensions==3.7.4.3
# via
# -r requirements-tests.txt
# black
urllib3==1.25.8 urllib3==1.25.8
# via requests # via
# -r requirements-tests.txt
# botocore
# requests
vine==1.3.0
# via -r requirements-docs.in
websocket-client==0.57.0
# via
# -r requirements-tests.txt
# docker
werkzeug==1.0.1
# via
# -r requirements-tests.txt
# flask
# moto
# pytest-flask
wrapt==1.12.1
# via
# -r requirements-tests.txt
# aws-xray-sdk
xmltodict==0.12.0
# via
# -r requirements-docs.in
# -r requirements-tests.txt
# moto
zipp==3.1.0
# via
# -r requirements-tests.txt
# importlib-metadata
# moto
zope.component==4.6.2
# via
# -r requirements-tests.txt
# certbot
zope.deferredimport==4.3.1
# via
# -r requirements-tests.txt
# zope.component
zope.deprecation==4.4.0
# via
# -r requirements-tests.txt
# zope.component
zope.event==4.5.0
# via
# -r requirements-tests.txt
# zope.component
zope.hookable==5.0.1
# via
# -r requirements-tests.txt
# zope.component
zope.interface==5.2.0
# via
# -r requirements-tests.txt
# certbot
# zope.component
# zope.proxy
zope.proxy==4.3.5
# via
# -r requirements-tests.txt
# zope.deferredimport
# The following packages are considered to be unsafe in a requirements file: # The following packages are considered to be unsafe in a requirements file:
# setuptools # setuptools

View File

@ -3,11 +3,12 @@
bandit bandit
black black
coverage coverage
certbot
factory-boy factory-boy
Faker Faker
fakeredis fakeredis
freezegun freezegun
moto moto[sts,ec2,elb,elbv2,iam,s3,sns,sqs,ses]
nose nose
pyflakes pyflakes
pytest pytest

View File

@ -4,6 +4,8 @@
# #
# pip-compile --no-index --output-file=requirements-tests.txt requirements-tests.in # pip-compile --no-index --output-file=requirements-tests.txt requirements-tests.in
# #
acme==1.13.0
# via certbot
appdirs==1.4.3 appdirs==1.4.3
# via black # via black
attrs==19.3.0 attrs==19.3.0
@ -18,18 +20,20 @@ bandit==1.7.0
# via -r requirements-tests.in # via -r requirements-tests.in
black==20.8b1 black==20.8b1
# via -r requirements-tests.in # via -r requirements-tests.in
boto3==1.17.7 boto3==1.17.27
# via # via
# aws-sam-translator # aws-sam-translator
# moto # moto
boto==2.49.0 boto==2.49.0
# via moto # via moto
botocore==1.20.7 botocore==1.20.27
# via # via
# aws-xray-sdk # aws-xray-sdk
# boto3 # boto3
# moto # moto
# s3transfer # s3transfer
certbot==1.13.0
# via -r requirements-tests.in
certifi==2020.12.5 certifi==2020.12.5
# via requests # via requests
cffi==1.14.0 cffi==1.14.0
@ -42,15 +46,25 @@ click==7.1.2
# via # via
# black # black
# flask # flask
coverage==5.4 configargparse==1.4
# via certbot
configobj==5.0.6
# via certbot
coverage==5.5
# via -r requirements-tests.in # via -r requirements-tests.in
cryptography==3.4.5 cryptography==3.4.6
# via # via
# acme
# certbot
# josepy
# moto # moto
# pyopenssl
# python-jose # python-jose
# sshpubkeys # sshpubkeys
decorator==4.4.2 decorator==4.4.2
# via networkx # via networkx
distro==1.5.0
# via certbot
docker==4.2.0 docker==4.2.0
# via moto # via moto
ecdsa==0.14.1 ecdsa==0.14.1
@ -60,7 +74,7 @@ ecdsa==0.14.1
# sshpubkeys # sshpubkeys
factory-boy==3.2.0 factory-boy==3.2.0
# via -r requirements-tests.in # via -r requirements-tests.in
faker==6.1.1 faker==6.5.0
# via # via
# -r requirements-tests.in # -r requirements-tests.in
# factory-boy # factory-boy
@ -94,6 +108,10 @@ jmespath==0.9.5
# via # via
# boto3 # boto3
# botocore # botocore
josepy==1.7.0
# via
# acme
# certbot
jsondiff==1.1.2 jsondiff==1.1.2
# via moto # via moto
jsonpatch==1.25 jsonpatch==1.25
@ -114,7 +132,7 @@ mock==4.0.2
# via moto # via moto
more-itertools==8.2.0 more-itertools==8.2.0
# via moto # via moto
moto==1.3.16 moto[ec2,elb,elbv2,iam,s3,ses,sns,sqs,sts]==1.3.16
# via -r requirements-tests.in # via -r requirements-tests.in
mypy-extensions==0.4.3 mypy-extensions==0.4.3
# via black # via black
@ -124,6 +142,8 @@ nose==1.3.7
# via -r requirements-tests.in # via -r requirements-tests.in
packaging==20.3 packaging==20.3
# via pytest # via pytest
parsedatetime==2.6
# via certbot
pathspec==0.8.0 pathspec==0.8.0
# via black # via black
pbr==5.4.5 pbr==5.4.5
@ -140,11 +160,19 @@ pycparser==2.20
# via cffi # via cffi
pyflakes==2.2.0 pyflakes==2.2.0
# via -r requirements-tests.in # via -r requirements-tests.in
pyopenssl==20.0.1
# via
# acme
# josepy
pyparsing==2.4.7 pyparsing==2.4.7
# via packaging # via packaging
pyrfc3339==1.1
# via
# acme
# certbot
pyrsistent==0.16.0 pyrsistent==0.16.0
# via jsonschema # via jsonschema
pytest-flask==1.1.0 pytest-flask==1.2.0
# via -r requirements-tests.in # via -r requirements-tests.in
pytest-mock==3.5.1 pytest-mock==3.5.1
# via -r requirements-tests.in # via -r requirements-tests.in
@ -162,7 +190,11 @@ python-dateutil==2.8.1
python-jose[cryptography]==3.1.0 python-jose[cryptography]==3.1.0
# via moto # via moto
pytz==2019.3 pytz==2019.3
# via moto # via
# acme
# certbot
# moto
# pyrfc3339
pyyaml==5.4.1 pyyaml==5.4.1
# via # via
# -r requirements-tests.in # -r requirements-tests.in
@ -175,11 +207,15 @@ regex==2020.4.4
# via black # via black
requests-mock==1.8.0 requests-mock==1.8.0
# via -r requirements-tests.in # via -r requirements-tests.in
requests-toolbelt==0.9.1
# via acme
requests==2.25.1 requests==2.25.1
# via # via
# acme
# docker # docker
# moto # moto
# requests-mock # requests-mock
# requests-toolbelt
# responses # responses
responses==0.10.12 responses==0.10.12
# via moto # via moto
@ -192,12 +228,15 @@ six==1.15.0
# aws-sam-translator # aws-sam-translator
# bandit # bandit
# cfn-lint # cfn-lint
# configobj
# docker # docker
# ecdsa # ecdsa
# fakeredis # fakeredis
# josepy
# jsonschema # jsonschema
# moto # moto
# packaging # packaging
# pyopenssl
# pyrsistent # pyrsistent
# python-dateutil # python-dateutil
# python-jose # python-jose
@ -242,6 +281,23 @@ zipp==3.1.0
# via # via
# importlib-metadata # importlib-metadata
# moto # moto
zope.component==4.6.2
# via certbot
zope.deferredimport==4.3.1
# via zope.component
zope.deprecation==4.4.0
# via zope.component
zope.event==4.5.0
# via zope.component
zope.hookable==5.0.1
# via zope.component
zope.interface==5.2.0
# via
# certbot
# zope.component
# zope.proxy
zope.proxy==4.3.5
# via zope.deferredimport
# The following packages are considered to be unsafe in a requirements file: # The following packages are considered to be unsafe in a requirements file:
# setuptools # setuptools

View File

@ -7,6 +7,7 @@ asyncpool
boto3 boto3
botocore botocore
celery[redis]==4.4.2 # need to first resolve the module not found error https://github.com/celery/celery/issues/6406 celery[redis]==4.4.2 # need to first resolve the module not found error https://github.com/celery/celery/issues/6406
certbot
certifi certifi
certsrv certsrv
CloudFlare CloudFlare

View File

@ -4,8 +4,10 @@
# #
# pip-compile --no-index --output-file=requirements.txt requirements.in # pip-compile --no-index --output-file=requirements.txt requirements.in
# #
acme==1.12.0 acme==1.13.0
# via -r requirements.in # via
# -r requirements.in
# certbot
alembic-autogenerate-enums==0.0.2 alembic-autogenerate-enums==0.0.2
# via -r requirements.in # via -r requirements.in
alembic==1.4.2 alembic==1.4.2
@ -14,7 +16,7 @@ amqp==2.5.2
# via kombu # via kombu
aniso8601==8.0.0 aniso8601==8.0.0
# via flask-restful # via flask-restful
arrow==0.17.0 arrow==1.0.3
# via -r requirements.in # via -r requirements.in
asyncpool==1.0 asyncpool==1.0
# via -r requirements.in # via -r requirements.in
@ -31,15 +33,17 @@ blinker==1.4
# flask-mail # flask-mail
# flask-principal # flask-principal
# raven # raven
boto3==1.17.7 boto3==1.17.27
# via -r requirements.in # via -r requirements.in
botocore==1.20.7 botocore==1.20.27
# via # via
# -r requirements.in # -r requirements.in
# boto3 # boto3
# s3transfer # s3transfer
celery[redis]==4.4.2 celery[redis]==4.4.2
# via -r requirements.in # via -r requirements.in
certbot==1.13.0
# via -r requirements.in
certifi==2020.12.5 certifi==2020.12.5
# via # via
# -r requirements.in # -r requirements.in
@ -57,14 +61,20 @@ click==7.1.2
# via flask # via flask
cloudflare==2.8.15 cloudflare==2.8.15
# via -r requirements.in # via -r requirements.in
cryptography==3.4.5 configargparse==1.4
# via certbot
configobj==5.0.6
# via certbot
cryptography==3.4.6
# via # via
# -r requirements.in # -r requirements.in
# acme # acme
# certbot
# josepy # josepy
# paramiko # paramiko
# pyopenssl # pyopenssl
# requests distro==1.5.0
# via certbot
dnspython3==1.15.0 dnspython3==1.15.0
# via -r requirements.in # via -r requirements.in
dnspython==1.15.0 dnspython==1.15.0
@ -77,7 +87,7 @@ flask-cors==3.0.10
# via -r requirements.in # via -r requirements.in
flask-mail==0.9.1 flask-mail==0.9.1
# via -r requirements.in # via -r requirements.in
flask-migrate==2.6.0 flask-migrate==2.7.0
# via -r requirements.in # via -r requirements.in
flask-principal==0.4.0 flask-principal==0.4.0
# via -r requirements.in # via -r requirements.in
@ -125,8 +135,10 @@ jmespath==0.9.5
# via # via
# boto3 # boto3
# botocore # botocore
josepy==1.3.0 josepy==1.7.0
# via acme # via
# acme
# certbot
jsonlines==1.2.0 jsonlines==1.2.0
# via cloudflare # via cloudflare
kombu==4.6.8 kombu==4.6.8
@ -151,6 +163,8 @@ ndg-httpsclient==0.5.1
# via -r requirements.in # via -r requirements.in
paramiko==2.7.2 paramiko==2.7.2
# via -r requirements.in # via -r requirements.in
parsedatetime==2.6
# via certbot
pem==21.1.0 pem==21.1.0
# via -r requirements.in # via -r requirements.in
psycopg2==2.8.6 psycopg2==2.8.6
@ -181,9 +195,10 @@ pyopenssl==20.0.1
# acme # acme
# josepy # josepy
# ndg-httpsclient # ndg-httpsclient
# requests
pyrfc3339==1.1 pyrfc3339==1.1
# via acme # via
# acme
# certbot
python-dateutil==2.8.1 python-dateutil==2.8.1
# via # via
# alembic # alembic
@ -199,6 +214,7 @@ pytz==2019.3
# via # via
# acme # acme
# celery # celery
# certbot
# flask-restful # flask-restful
# pyrfc3339 # pyrfc3339
pyyaml==5.4.1 pyyaml==5.4.1
@ -213,7 +229,7 @@ redis==3.5.3
# celery # celery
requests-toolbelt==0.9.1 requests-toolbelt==0.9.1
# via acme # via acme
requests[security]==2.25.1 requests==2.25.1
# via # via
# -r requirements.in # -r requirements.in
# acme # acme
@ -228,8 +244,8 @@ s3transfer==0.3.3
six==1.15.0 six==1.15.0
# via # via
# -r requirements.in # -r requirements.in
# acme
# bcrypt # bcrypt
# configobj
# flask-cors # flask-cors
# flask-restful # flask-restful
# hvac # hvac
@ -250,7 +266,7 @@ sqlalchemy==1.3.16
# flask-sqlalchemy # flask-sqlalchemy
# marshmallow-sqlalchemy # marshmallow-sqlalchemy
# sqlalchemy-utils # sqlalchemy-utils
tabulate==0.8.7 tabulate==0.8.9
# via -r requirements.in # via -r requirements.in
twofish==0.3.0 twofish==0.3.0
# via pyjks # via pyjks
@ -266,6 +282,22 @@ werkzeug==1.0.1
# via flask # via flask
xmltodict==0.12.0 xmltodict==0.12.0
# via -r requirements.in # via -r requirements.in
zope.component==4.6.2
# via certbot
zope.deferredimport==4.3.1
# via zope.component
zope.deprecation==4.4.0
# via zope.component
zope.event==4.5.0
# via zope.component
zope.hookable==5.0.1
# via zope.component
zope.interface==5.2.0
# via
# certbot
# zope.component
zope.proxy==4.3.5
# via zope.deferredimport
# The following packages are considered to be unsafe in a requirements file: # The following packages are considered to be unsafe in a requirements file:
# setuptools # setuptools