Merge branch 'master' into ilabun/optimize-certificates-sql

This commit is contained in:
Hossein Shafagh 2020-05-27 15:29:47 -07:00 committed by GitHub
commit 50091cca1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 310 additions and 42 deletions

View File

@ -22,7 +22,7 @@ Lemur
Lemur manages TLS certificate creation. While not able to issue certificates itself, Lemur acts as a broker between CAs Lemur manages TLS certificate creation. While not able to issue certificates itself, Lemur acts as a broker between CAs
and environments providing a central portal for developers to issue TLS certificates with 'sane' defaults. and environments providing a central portal for developers to issue TLS certificates with 'sane' defaults.
It works on CPython 3.5. We deploy on Ubuntu and develop on OS X. It works on Python 3.7. We deploy on Ubuntu and develop on OS X.
Project resources Project resources

64
docker/Dockerfile-src Normal file
View File

@ -0,0 +1,64 @@
FROM alpine:3.8
ARG VERSION
ENV VERSION master
ENV uid 1337
ENV gid 1337
ENV user lemur
ENV group lemur
RUN addgroup -S ${group} -g ${gid} && \
adduser -D -S ${user} -G ${group} -u ${uid} && \
apk --update add python3 libldap postgresql-client nginx supervisor curl tzdata openssl bash && \
apk --update add --virtual build-dependencies \
git \
tar \
curl \
python3-dev \
npm \
bash \
musl-dev \
gcc \
autoconf \
automake \
make \
nasm \
zlib-dev \
postgresql-dev \
libressl-dev \
libffi-dev \
cyrus-sasl-dev \
openldap-dev && \
pip3 install --upgrade pip && \
pip3 install --upgrade setuptools && \
mkdir -p /home/lemur/.lemur/ && \
mkdir -p /run/nginx/ /etc/nginx/ssl/
COPY ./ /opt/lemur
WORKDIR /opt/lemur
RUN chown -R $user:$group /opt/lemur/ /home/lemur/.lemur/ && \
npm install --unsafe-perm && \
pip3 install -e . && \
node_modules/.bin/gulp build && \
node_modules/.bin/gulp package --urlContextPath=$(urlContextPath) && \
apk del build-dependencies
COPY docker/entrypoint /
COPY docker/src/lemur.conf.py /home/lemur/.lemur/lemur.conf.py
COPY docker/supervisor.conf /
COPY docker/nginx/default.conf /etc/nginx/conf.d/
COPY docker/nginx/default-ssl.conf /etc/nginx/conf.d/
RUN chmod +x /entrypoint
WORKDIR /
HEALTHCHECK --interval=12s --timeout=12s --start-period=30s \
CMD curl --fail http://localhost:80/api/1/healthcheck | grep -q ok || exit 1
USER root
ENTRYPOINT ["/entrypoint"]
CMD ["/usr/bin/supervisord","-c","supervisor.conf"]

View File

@ -36,7 +36,7 @@ fi
# fi # fi
echo " # Running init" echo " # Running init"
su lemur -s /bin/bash -c "cd /opt/lemur/lemur; python3 /opt/lemur/lemur/manage.py init -p ${LEMUR_ADMIN_PASSWORD}" su lemur -s /bin/bash -c "cd /opt/lemur/lemur; lemur init -p ${LEMUR_ADMIN_PASSWORD}"
echo " # Done" echo " # Done"
# echo "Creating user" # echo "Creating user"
@ -47,11 +47,13 @@ echo " # Done"
cron_notify="${CRON_NOTIFY:-"0 22 * * *"}" cron_notify="${CRON_NOTIFY:-"0 22 * * *"}"
cron_sync="${CRON_SYNC:-"*/15 * * * *"}" cron_sync="${CRON_SYNC:-"*/15 * * * *"}"
cron_revoked="${CRON_CHECK_REVOKED:-"0 22 * * *"}" cron_revoked="${CRON_CHECK_REVOKED:-"0 22 * * *"}"
cron_reissue="${CRON_REISSUE:-"0 23 * * *"}"
echo " # Populating crontab" echo " # Populating crontab"
echo "${cron_notify} lemur python3 /opt/lemur/lemur/manage.py notify expirations" > /etc/crontabs/lemur_notify echo "${cron_notify} lemur notify expirations" > /etc/crontabs/lemur
echo "${cron_sync} lemur python3 /opt/lemur/lemur/manage.py source sync -s all" > /etc/crontabs/lemur_sync echo "${cron_sync} lemur source sync -s all" >> /etc/crontabs/lemur
echo "${cron_revoked} lemur python3 /opt/lemur/lemur/manage.py certificate check_revoked" > /etc/crontabs/lemur_revoked echo "${cron_revoked} lemur certificate check_revoked" >> /etc/crontabs/lemur
echo "${cron_reissue} lemur certificate reissue -c" >> /etc/crontabs/lemur
echo " # Done" echo " # Done"
exec "$@" exec "$@"

View File

@ -16,12 +16,16 @@ LEMUR_WHITELISTED_DOMAINS = []
LEMUR_EMAIL = '' LEMUR_EMAIL = ''
LEMUR_SECURITY_TEAM_EMAIL = [] LEMUR_SECURITY_TEAM_EMAIL = []
ALLOW_CERT_DELETION = os.environ.get('ALLOW_CERT_DELETION') == "True"
LEMUR_DEFAULT_COUNTRY = repr(os.environ.get('LEMUR_DEFAULT_COUNTRY','')) LEMUR_DEFAULT_COUNTRY = str(os.environ.get('LEMUR_DEFAULT_COUNTRY',''))
LEMUR_DEFAULT_STATE = repr(os.environ.get('LEMUR_DEFAULT_STATE','')) LEMUR_DEFAULT_STATE = str(os.environ.get('LEMUR_DEFAULT_STATE',''))
LEMUR_DEFAULT_LOCATION = repr(os.environ.get('LEMUR_DEFAULT_LOCATION','')) LEMUR_DEFAULT_LOCATION = str(os.environ.get('LEMUR_DEFAULT_LOCATION',''))
LEMUR_DEFAULT_ORGANIZATION = repr(os.environ.get('LEMUR_DEFAULT_ORGANIZATION','')) LEMUR_DEFAULT_ORGANIZATION = str(os.environ.get('LEMUR_DEFAULT_ORGANIZATION',''))
LEMUR_DEFAULT_ORGANIZATIONAL_UNIT = repr(os.environ.get('LEMUR_DEFAULT_ORGANIZATIONAL_UNIT','')) LEMUR_DEFAULT_ORGANIZATIONAL_UNIT = str(os.environ.get('LEMUR_DEFAULT_ORGANIZATIONAL_UNIT',''))
LEMUR_DEFAULT_ISSUER_PLUGIN = str(os.environ.get('LEMUR_DEFAULT_ISSUER_PLUGIN',''))
LEMUR_DEFAULT_AUTHORITY = str(os.environ.get('LEMUR_DEFAULT_AUTHORITY',''))
ACTIVE_PROVIDERS = [] ACTIVE_PROVIDERS = []

View File

@ -7,7 +7,7 @@ pidfile = /tmp/supervisord.pid
[program:lemur] [program:lemur]
environment=LEMUR_CONF=/home/lemur/.lemur/lemur.conf.py environment=LEMUR_CONF=/home/lemur/.lemur/lemur.conf.py
command=/usr/bin/python3 manage.py start -b 0.0.0.0:8000 command=lemur start -b 0.0.0.0:8000
user=lemur user=lemur
directory=/opt/lemur/lemur directory=/opt/lemur/lemur
stdout_logfile=/dev/stdout stdout_logfile=/dev/stdout
@ -24,6 +24,7 @@ stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0 stderr_logfile_maxbytes=0
[program:cron] [program:cron]
environment=LEMUR_CONF=/home/lemur/.lemur/lemur.conf.py
command=/usr/sbin/crond -f command=/usr/sbin/crond -f
user=root user=root
stdout_logfile=/dev/stdout stdout_logfile=/dev/stdout

View File

@ -286,6 +286,178 @@ def rotate(endpoint_name, new_certificate_name, old_certificate_name, message, c
) )
def request_rotation_region(endpoint, new_cert, message, commit, log_data, region):
if region in endpoint.dnsname:
log_data["message"] = "Rotating endpoint in region"
request_rotation(endpoint, new_cert, message, commit)
else:
log_data["message"] = "Skipping rotation, region mismatch"
print(log_data)
current_app.logger.info(log_data)
@manager.option(
"-e",
"--endpoint",
dest="endpoint_name",
help="Name of the endpoint you wish to rotate.",
)
@manager.option(
"-n",
"--new-certificate",
dest="new_certificate_name",
help="Name of the certificate you wish to rotate to.",
)
@manager.option(
"-o",
"--old-certificate",
dest="old_certificate_name",
help="Name of the certificate you wish to rotate.",
)
@manager.option(
"-a",
"--notify",
dest="message",
action="store_true",
help="Send a rotation notification to the certificates owner.",
)
@manager.option(
"-c",
"--commit",
dest="commit",
action="store_true",
default=False,
help="Persist changes.",
)
@manager.option(
"-r",
"--region",
dest="region",
required=True,
help="Region in which to rotate the endpoint.",
)
def rotate_region(endpoint_name, new_certificate_name, old_certificate_name, message, commit, region):
"""
Rotates an endpoint in a defined region it if it has not already been replaced. If it has
been replaced, will use the replacement certificate for the rotation.
:param old_certificate_name: Name of the certificate you wish to rotate.
:param new_certificate_name: Name of the certificate you wish to rotate to.
:param endpoint_name: Name of the endpoint you wish to rotate.
:param message: Send a rotation notification to the certificates owner.
:param commit: Persist changes.
:param region: Region in which to rotate the endpoint.
"""
if commit:
print("[!] Running in COMMIT mode.")
print("[+] Starting endpoint rotation.")
status = FAILURE_METRIC_STATUS
log_data = {
"function": f"{__name__}.{sys._getframe().f_code.co_name}",
"region": region,
}
try:
old_cert = validate_certificate(old_certificate_name)
new_cert = validate_certificate(new_certificate_name)
endpoint = validate_endpoint(endpoint_name)
if endpoint and new_cert:
log_data["endpoint"] = endpoint.dnsname
log_data["certificate"] = new_cert.name
request_rotation_region(endpoint, new_cert, message, commit, log_data, region)
elif old_cert and new_cert:
log_data["certificate"] = new_cert.name
log_data["certificate_old"] = old_cert.name
log_data["message"] = "Rotating endpoint from old to new cert"
print(log_data)
current_app.logger.info(log_data)
for endpoint in old_cert.endpoints:
log_data["endpoint"] = endpoint.dnsname
request_rotation_region(endpoint, new_cert, message, commit, log_data, region)
else:
log_data["message"] = "Rotating all endpoints that have new certificates available"
print(log_data)
current_app.logger.info(log_data)
all_pending_rotation_endpoints = endpoint_service.get_all_pending_rotation()
for endpoint in all_pending_rotation_endpoints:
log_data["endpoint"] = endpoint.dnsname
if region not in endpoint.dnsname:
log_data["message"] = "Skipping rotation, region mismatch"
print(log_data)
current_app.logger.info(log_data)
metrics.send(
"endpoint_rotation_region_skipped",
"counter",
1,
metric_tags={
"region": region,
"old_certificate_name": str(old_cert),
"new_certificate_name": str(endpoint.certificate.replaced[0].name),
"endpoint_name": str(endpoint.dnsname),
},
)
if len(endpoint.certificate.replaced) == 1:
log_data["certificate"] = endpoint.certificate.replaced[0].name
log_data["message"] = "Rotating all endpoints in region"
print(log_data)
current_app.logger.info(log_data)
request_rotation(endpoint, endpoint.certificate.replaced[0], message, commit)
status = SUCCESS_METRIC_STATUS
else:
status = FAILURE_METRIC_STATUS
log_data["message"] = "Failed to rotate endpoint due to Multiple replacement certificates found"
print(log_data)
current_app.logger.info(log_data)
metrics.send(
"endpoint_rotation_region",
"counter",
1,
metric_tags={
"status": FAILURE_METRIC_STATUS,
"old_certificate_name": str(old_cert),
"new_certificate_name": str(endpoint.certificate.replaced[0].name),
"endpoint_name": str(endpoint.dnsname),
"message": str(message),
"region": str(region),
},
)
status = SUCCESS_METRIC_STATUS
print("[+] Done!")
except Exception as e:
sentry.captureException(
extra={
"old_certificate_name": str(old_certificate_name),
"new_certificate_name": str(new_certificate_name),
"endpoint": str(endpoint_name),
"message": str(message),
"region": str(region),
}
)
metrics.send(
"endpoint_rotation_region_job",
"counter",
1,
metric_tags={
"status": status,
"old_certificate_name": str(old_certificate_name),
"new_certificate_name": str(new_certificate_name),
"endpoint_name": str(endpoint_name),
"message": str(message),
"endpoint": str(globals().get("endpoint")),
"region": str(region),
},
)
@manager.option( @manager.option(
"-o", "-o",
"--old-certificate", "--old-certificate",

View File

@ -146,7 +146,8 @@ class CertificateInputSchema(CertificateCreationSchema):
data["extensions"]["subAltNames"] = {"names": []} data["extensions"]["subAltNames"] = {"names": []}
elif not data["extensions"]["subAltNames"].get("names"): elif not data["extensions"]["subAltNames"].get("names"):
data["extensions"]["subAltNames"]["names"] = [] data["extensions"]["subAltNames"]["names"] = []
data["extensions"]["subAltNames"]["names"] += csr_sans
data["extensions"]["subAltNames"]["names"] = csr_sans
return missing.convert_validity_years(data) return missing.convert_validity_years(data)

View File

@ -631,7 +631,8 @@ def certificate_reissue():
@celery.task(soft_time_limit=3600) @celery.task(soft_time_limit=3600)
def certificate_rotate(): def certificate_rotate(**kwargs):
""" """
This celery task rotates certificates which are reissued but having endpoints attached to the replaced cert This celery task rotates certificates which are reissued but having endpoints attached to the replaced cert
:return: :return:
@ -641,6 +642,7 @@ def certificate_rotate():
if celery.current_task: if celery.current_task:
task_id = celery.current_task.request.id task_id = celery.current_task.request.id
region = kwargs.get("region")
log_data = { log_data = {
"function": function, "function": function,
"message": "rotating certificates", "message": "rotating certificates",
@ -654,7 +656,11 @@ def certificate_rotate():
current_app.logger.debug(log_data) current_app.logger.debug(log_data)
try: try:
cli_certificate.rotate(None, None, None, None, True) if region:
log_data["region"] = region
cli_certificate.rotate_region(None, None, None, None, True, region)
else:
cli_certificate.rotate(None, None, None, None, True)
except SoftTimeLimitExceeded: except SoftTimeLimitExceeded:
log_data["message"] = "Certificate rotate: Time limit exceeded." log_data["message"] = "Certificate rotate: Time limit exceeded."
current_app.logger.error(log_data) current_app.logger.error(log_data)

View File

@ -14,7 +14,7 @@ import re
import hvac import hvac
from flask import current_app from flask import current_app
from lemur.common.defaults import common_name from lemur.common.defaults import common_name, country, state, location, organizational_unit, organization
from lemur.common.utils import parse_certificate from lemur.common.utils import parse_certificate
from lemur.plugins.bases import DestinationPlugin from lemur.plugins.bases import DestinationPlugin
from lemur.plugins.bases import SourcePlugin from lemur.plugins.bases import SourcePlugin
@ -58,7 +58,7 @@ class VaultSourcePlugin(SourcePlugin):
"helpMessage": "Authentication method to use", "helpMessage": "Authentication method to use",
}, },
{ {
"name": "tokenFile/VaultRole", "name": "tokenFileOrVaultRole",
"type": "str", "type": "str",
"required": True, "required": True,
"validation": "^([a-zA-Z0-9/._-]+/?)+$", "validation": "^([a-zA-Z0-9/._-]+/?)+$",
@ -94,7 +94,7 @@ class VaultSourcePlugin(SourcePlugin):
body = "" body = ""
url = self.get_option("vaultUrl", options) url = self.get_option("vaultUrl", options)
auth_method = self.get_option("authenticationMethod", options) auth_method = self.get_option("authenticationMethod", options)
auth_key = self.get_option("tokenFile/vaultRole", options) auth_key = self.get_option("tokenFileOrVaultRole", options)
mount = self.get_option("vaultMount", options) mount = self.get_option("vaultMount", options)
path = self.get_option("vaultPath", options) path = self.get_option("vaultPath", options)
obj_name = self.get_option("objectName", options) obj_name = self.get_option("objectName", options)
@ -185,7 +185,7 @@ class VaultDestinationPlugin(DestinationPlugin):
"helpMessage": "Authentication method to use", "helpMessage": "Authentication method to use",
}, },
{ {
"name": "tokenFile/VaultRole", "name": "tokenFileOrVaultRole",
"type": "str", "type": "str",
"required": True, "required": True,
"validation": "^([a-zA-Z0-9/._-]+/?)+$", "validation": "^([a-zA-Z0-9/._-]+/?)+$",
@ -202,15 +202,15 @@ class VaultDestinationPlugin(DestinationPlugin):
"name": "vaultPath", "name": "vaultPath",
"type": "str", "type": "str",
"required": True, "required": True,
"validation": "^([a-zA-Z0-9._-]+/?)+$", "validation": "^(([a-zA-Z0-9._-]+|{(CN|OU|O|L|S|C)})+/?)+$",
"helpMessage": "Must be a valid Vault secrets path", "helpMessage": "Must be a valid Vault secrets path. Support vars: {CN|OU|O|L|S|C}",
}, },
{ {
"name": "objectName", "name": "objectName",
"type": "str", "type": "str",
"required": False, "required": False,
"validation": "[0-9a-zA-Z.:_-]+", "validation": "^([0-9a-zA-Z.:_-]+|{(CN|OU|O|L|S|C)})+$",
"helpMessage": "Name to bundle certs under, if blank use cn", "helpMessage": "Name to bundle certs under, if blank use {CN}. Support vars: {CN|OU|O|L|S|C}",
}, },
{ {
"name": "bundleChain", "name": "bundleChain",
@ -241,11 +241,12 @@ class VaultDestinationPlugin(DestinationPlugin):
:param cert_chain: :param cert_chain:
:return: :return:
""" """
cname = common_name(parse_certificate(body)) cert = parse_certificate(body)
cname = common_name(cert)
url = self.get_option("vaultUrl", options) url = self.get_option("vaultUrl", options)
auth_method = self.get_option("authenticationMethod", options) auth_method = self.get_option("authenticationMethod", options)
auth_key = self.get_option("tokenFile/vaultRole", options) auth_key = self.get_option("tokenFileOrVaultRole", options)
mount = self.get_option("vaultMount", options) mount = self.get_option("vaultMount", options)
path = self.get_option("vaultPath", options) path = self.get_option("vaultPath", options)
bundle = self.get_option("bundleChain", options) bundle = self.get_option("bundleChain", options)
@ -285,10 +286,27 @@ class VaultDestinationPlugin(DestinationPlugin):
client.secrets.kv.default_kv_version = api_version client.secrets.kv.default_kv_version = api_version
if obj_name: t_path = path.format(
path = "{0}/{1}".format(path, obj_name) CN=cname,
else: OU=organizational_unit(cert),
path = "{0}/{1}".format(path, cname) O=organization(cert), # noqa: E741
L=location(cert),
S=state(cert),
C=country(cert)
)
if not obj_name:
obj_name = '{CN}'
f_obj_name = obj_name.format(
CN=cname,
OU=organizational_unit(cert),
O=organization(cert), # noqa: E741
L=location(cert),
S=state(cert),
C=country(cert)
)
path = "{0}/{1}".format(t_path, f_obj_name)
secret = get_secret(client, mount, path) secret = get_secret(client, mount, path)
secret["data"][cname] = {} secret["data"][cname] = {}

View File

@ -33,7 +33,7 @@ readme-renderer==25.0 # via twine
requests-toolbelt==0.9.1 # via twine requests-toolbelt==0.9.1 # via twine
requests==2.23.0 # via requests-toolbelt, twine requests==2.23.0 # via requests-toolbelt, twine
secretstorage==3.1.2 # via keyring secretstorage==3.1.2 # via keyring
six==1.14.0 # via bleach, cryptography, readme-renderer, virtualenv six==1.15.0 # via bleach, cryptography, readme-renderer, virtualenv
toml==0.10.0 # via pre-commit toml==0.10.0 # via pre-commit
tqdm==4.45.0 # via twine tqdm==4.45.0 # via twine
twine==3.1.1 # via -r requirements-dev.in twine==3.1.1 # via -r requirements-dev.in

View File

@ -16,8 +16,8 @@ babel==2.8.0 # via sphinx
bcrypt==3.1.7 # via -r requirements.txt, flask-bcrypt, paramiko bcrypt==3.1.7 # via -r requirements.txt, flask-bcrypt, paramiko
billiard==3.6.3.0 # via -r requirements.txt, celery billiard==3.6.3.0 # via -r requirements.txt, celery
blinker==1.4 # via -r requirements.txt, flask-mail, flask-principal, raven blinker==1.4 # via -r requirements.txt, flask-mail, flask-principal, raven
boto3==1.13.11 # via -r requirements.txt boto3==1.13.18 # via -r requirements.txt
botocore==1.16.11 # via -r requirements.txt, boto3, s3transfer botocore==1.16.18 # via -r requirements.txt, boto3, s3transfer
celery[redis]==4.4.2 # via -r requirements.txt celery[redis]==4.4.2 # via -r requirements.txt
certifi==2020.4.5.1 # via -r requirements.txt, requests certifi==2020.4.5.1 # via -r requirements.txt, requests
certsrv==2.1.1 # via -r requirements.txt certsrv==2.1.1 # via -r requirements.txt
@ -42,7 +42,7 @@ flask-sqlalchemy==2.4.1 # via -r requirements.txt, flask-migrate
flask==1.1.2 # via -r requirements.txt, flask-bcrypt, flask-cors, flask-mail, flask-migrate, flask-principal, flask-restful, flask-script, flask-sqlalchemy, raven flask==1.1.2 # via -r requirements.txt, flask-bcrypt, flask-cors, flask-mail, flask-migrate, flask-principal, flask-restful, flask-script, flask-sqlalchemy, raven
future==0.18.2 # via -r requirements.txt, cloudflare future==0.18.2 # via -r requirements.txt, cloudflare
gunicorn==20.0.4 # via -r requirements.txt gunicorn==20.0.4 # via -r requirements.txt
hvac==0.10.1 # via -r requirements.txt hvac==0.10.3 # via -r requirements.txt
idna==2.9 # via -r requirements.txt, requests idna==2.9 # via -r requirements.txt, requests
imagesize==1.2.0 # via sphinx imagesize==1.2.0 # via sphinx
inflection==0.4.0 # via -r requirements.txt inflection==0.4.0 # via -r requirements.txt
@ -87,7 +87,7 @@ requests-toolbelt==0.9.1 # via -r requirements.txt, acme
requests[security]==2.23.0 # via -r requirements.txt, acme, certsrv, cloudflare, hvac, requests-toolbelt, sphinx requests[security]==2.23.0 # via -r requirements.txt, acme, certsrv, cloudflare, hvac, requests-toolbelt, sphinx
retrying==1.3.3 # via -r requirements.txt retrying==1.3.3 # via -r requirements.txt
s3transfer==0.3.3 # via -r requirements.txt, boto3 s3transfer==0.3.3 # via -r requirements.txt, boto3
six==1.14.0 # via -r requirements.txt, acme, bcrypt, cryptography, flask-cors, flask-restful, hvac, josepy, jsonlines, packaging, pynacl, pyopenssl, python-dateutil, retrying, sphinxcontrib-httpdomain, sqlalchemy-utils six==1.15.0 # via -r requirements.txt, acme, bcrypt, cryptography, flask-cors, flask-restful, hvac, josepy, jsonlines, packaging, pynacl, pyopenssl, python-dateutil, retrying, sphinxcontrib-httpdomain, sqlalchemy-utils
snowballstemmer==2.0.0 # via sphinx snowballstemmer==2.0.0 # via sphinx
sphinx-rtd-theme==0.4.3 # via -r requirements-docs.in sphinx-rtd-theme==0.4.3 # via -r requirements-docs.in
sphinx==3.0.3 # via -r requirements-docs.in, sphinx-rtd-theme, sphinxcontrib-httpdomain sphinx==3.0.3 # via -r requirements-docs.in, sphinx-rtd-theme, sphinxcontrib-httpdomain
@ -98,7 +98,7 @@ sphinxcontrib-httpdomain==1.7.0 # via -r requirements-docs.in
sphinxcontrib-jsmath==1.0.1 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx
sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-qthelp==1.0.3 # via sphinx
sphinxcontrib-serializinghtml==1.1.4 # via sphinx sphinxcontrib-serializinghtml==1.1.4 # via sphinx
sqlalchemy-utils==0.36.5 # via -r requirements.txt sqlalchemy-utils==0.36.6 # via -r requirements.txt
sqlalchemy==1.3.16 # via -r requirements.txt, alembic, flask-sqlalchemy, marshmallow-sqlalchemy, sqlalchemy-utils sqlalchemy==1.3.16 # via -r requirements.txt, alembic, flask-sqlalchemy, marshmallow-sqlalchemy, sqlalchemy-utils
tabulate==0.8.7 # via -r requirements.txt tabulate==0.8.7 # via -r requirements.txt
twofish==0.3.0 # via -r requirements.txt, pyjks twofish==0.3.0 # via -r requirements.txt, pyjks

View File

@ -10,9 +10,9 @@ aws-sam-translator==1.22.0 # via cfn-lint
aws-xray-sdk==2.5.0 # via moto aws-xray-sdk==2.5.0 # via moto
bandit==1.6.2 # via -r requirements-tests.in bandit==1.6.2 # via -r requirements-tests.in
black==19.10b0 # via -r requirements-tests.in black==19.10b0 # via -r requirements-tests.in
boto3==1.13.11 # via aws-sam-translator, moto boto3==1.13.18 # via aws-sam-translator, moto
boto==2.49.0 # via moto boto==2.49.0 # via moto
botocore==1.16.11 # via aws-xray-sdk, boto3, moto, s3transfer botocore==1.16.18 # via aws-xray-sdk, boto3, moto, s3transfer
certifi==2020.4.5.1 # via requests certifi==2020.4.5.1 # via requests
cffi==1.14.0 # via cryptography cffi==1.14.0 # via cryptography
cfn-lint==0.29.5 # via moto cfn-lint==0.29.5 # via moto
@ -72,7 +72,7 @@ requests==2.23.0 # via docker, moto, requests-mock, responses
responses==0.10.12 # via moto responses==0.10.12 # via moto
rsa==4.0 # via python-jose rsa==4.0 # via python-jose
s3transfer==0.3.3 # via boto3 s3transfer==0.3.3 # via boto3
six==1.14.0 # via aws-sam-translator, bandit, cfn-lint, cryptography, docker, ecdsa, fakeredis, freezegun, jsonschema, moto, packaging, pyrsistent, python-dateutil, python-jose, requests-mock, responses, stevedore, websocket-client six==1.15.0 # via aws-sam-translator, bandit, cfn-lint, cryptography, docker, ecdsa, fakeredis, freezegun, jsonschema, moto, packaging, pyrsistent, python-dateutil, python-jose, requests-mock, responses, stevedore, websocket-client
smmap==3.0.2 # via gitdb smmap==3.0.2 # via gitdb
sortedcontainers==2.1.0 # via fakeredis sortedcontainers==2.1.0 # via fakeredis
sshpubkeys==3.1.0 # via moto sshpubkeys==3.1.0 # via moto

View File

@ -14,8 +14,8 @@ asyncpool==1.0 # via -r requirements.in
bcrypt==3.1.7 # via flask-bcrypt, paramiko bcrypt==3.1.7 # via flask-bcrypt, paramiko
billiard==3.6.3.0 # via celery billiard==3.6.3.0 # via celery
blinker==1.4 # via flask-mail, flask-principal, raven blinker==1.4 # via flask-mail, flask-principal, raven
boto3==1.13.11 # via -r requirements.in boto3==1.13.18 # via -r requirements.in
botocore==1.16.11 # via -r requirements.in, boto3, s3transfer botocore==1.16.18 # via -r requirements.in, boto3, s3transfer
celery[redis]==4.4.2 # via -r requirements.in celery[redis]==4.4.2 # via -r requirements.in
certifi==2020.4.5.1 # via -r requirements.in, requests certifi==2020.4.5.1 # via -r requirements.in, requests
certsrv==2.1.1 # via -r requirements.in certsrv==2.1.1 # via -r requirements.in
@ -40,7 +40,7 @@ flask-sqlalchemy==2.4.1 # via -r requirements.in, flask-migrate
flask==1.1.2 # via -r requirements.in, flask-bcrypt, flask-cors, flask-mail, flask-migrate, flask-principal, flask-restful, flask-script, flask-sqlalchemy, raven flask==1.1.2 # via -r requirements.in, flask-bcrypt, flask-cors, flask-mail, flask-migrate, flask-principal, flask-restful, flask-script, flask-sqlalchemy, raven
future==0.18.2 # via -r requirements.in, cloudflare future==0.18.2 # via -r requirements.in, cloudflare
gunicorn==20.0.4 # via -r requirements.in gunicorn==20.0.4 # via -r requirements.in
hvac==0.10.1 # via -r requirements.in hvac==0.10.3 # via -r requirements.in
idna==2.9 # via requests idna==2.9 # via requests
inflection==0.4.0 # via -r requirements.in inflection==0.4.0 # via -r requirements.in
itsdangerous==1.1.0 # via flask itsdangerous==1.1.0 # via flask
@ -81,8 +81,8 @@ requests-toolbelt==0.9.1 # via acme
requests[security]==2.23.0 # via -r requirements.in, acme, certsrv, cloudflare, hvac, requests-toolbelt requests[security]==2.23.0 # via -r requirements.in, acme, certsrv, cloudflare, hvac, requests-toolbelt
retrying==1.3.3 # via -r requirements.in retrying==1.3.3 # via -r requirements.in
s3transfer==0.3.3 # via boto3 s3transfer==0.3.3 # via boto3
six==1.14.0 # via -r requirements.in, acme, bcrypt, cryptography, flask-cors, flask-restful, hvac, josepy, jsonlines, pynacl, pyopenssl, python-dateutil, retrying, sqlalchemy-utils six==1.15.0 # via -r requirements.in, acme, bcrypt, cryptography, flask-cors, flask-restful, hvac, josepy, jsonlines, pynacl, pyopenssl, python-dateutil, retrying, sqlalchemy-utils
sqlalchemy-utils==0.36.5 # via -r requirements.in sqlalchemy-utils==0.36.6 # via -r requirements.in
sqlalchemy==1.3.16 # via alembic, flask-sqlalchemy, marshmallow-sqlalchemy, sqlalchemy-utils sqlalchemy==1.3.16 # via alembic, flask-sqlalchemy, marshmallow-sqlalchemy, sqlalchemy-utils
tabulate==0.8.7 # via -r requirements.in tabulate==0.8.7 # via -r requirements.in
twofish==0.3.0 # via pyjks twofish==0.3.0 # via pyjks