Merge branch 'github' into get_by_attributes

This commit is contained in:
Non Sequitur 2018-10-17 12:00:36 -04:00
commit 81d114092e
15 changed files with 214 additions and 88 deletions

View File

@ -5,41 +5,32 @@
:license: Apache, see LICENSE for more details. :license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com> .. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
""" """
import arrow
from datetime import timedelta from datetime import timedelta
from flask import current_app import arrow
from cryptography import x509 from cryptography import x509
from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.asymmetric import rsa
from flask import current_app
from idna.core import InvalidCodepoint from idna.core import InvalidCodepoint
from sqlalchemy import event, Integer, ForeignKey, String, PassiveDefault, func, Column, Text, Boolean, Index
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from sqlalchemy.sql.expression import case, extract from sqlalchemy.sql.expression import case, extract
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy import event, Integer, ForeignKey, String, PassiveDefault, func, Column, Text, Boolean
from sqlalchemy_utils.types.arrow import ArrowType from sqlalchemy_utils.types.arrow import ArrowType
from werkzeug.utils import cached_property from werkzeug.utils import cached_property
from lemur.database import db
from lemur.extensions import sentry
from lemur.utils import Vault
from lemur.common import defaults, utils from lemur.common import defaults, utils
from lemur.plugins.base import plugins
from lemur.extensions import metrics
from lemur.constants import SUCCESS_METRIC_STATUS, FAILURE_METRIC_STATUS from lemur.constants import SUCCESS_METRIC_STATUS, FAILURE_METRIC_STATUS
from lemur.database import db
from lemur.domains.models import Domain
from lemur.extensions import metrics
from lemur.extensions import sentry
from lemur.models import certificate_associations, certificate_source_associations, \ from lemur.models import certificate_associations, certificate_source_associations, \
certificate_destination_associations, certificate_notification_associations, \ certificate_destination_associations, certificate_notification_associations, \
certificate_replacement_associations, roles_certificates, pending_cert_replacement_associations certificate_replacement_associations, roles_certificates, pending_cert_replacement_associations
from lemur.plugins.base import plugins
from lemur.domains.models import Domain
from lemur.policies.models import RotationPolicy from lemur.policies.models import RotationPolicy
from lemur.utils import Vault
def get_sequence(name): def get_sequence(name):
@ -87,6 +78,7 @@ def get_or_increase_name(name, serial):
class Certificate(db.Model): class Certificate(db.Model):
__tablename__ = 'certificates' __tablename__ = 'certificates'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
ix = Index('ix_certificates_id_desc', id.desc(), postgresql_using='btree', unique=True)
external_id = Column(String(128)) external_id = Column(String(128))
owner = Column(String(128), nullable=False) owner = Column(String(128), nullable=False)
name = Column(String(256), unique=True) name = Column(String(256), unique=True)

View File

@ -193,6 +193,8 @@ class CertificateOutputSchema(LemurOutputSchema):
name = fields.String() name = fields.String()
dns_provider_id = fields.Integer(required=False, allow_none=True) dns_provider_id = fields.Integer(required=False, allow_none=True)
date_created = ArrowDateTime() date_created = ArrowDateTime()
resolved = fields.Boolean(required=False, allow_none=True)
resolved_cert_id = fields.Integer(required=False, allow_none=True)
rotation = fields.Boolean() rotation = fields.Boolean()

View File

@ -8,9 +8,8 @@ command: celery -A lemur.common.celery worker --loglevel=info -l DEBUG -B
""" """
import copy import copy
import datetime
import sys import sys
from datetime import timezone from datetime import datetime, timezone, timedelta
from celery import Celery from celery import Celery
from flask import current_app from flask import current_app
@ -20,7 +19,6 @@ from lemur.factory import create_app
from lemur.notifications.messaging import send_pending_failure_notification from lemur.notifications.messaging import send_pending_failure_notification
from lemur.pending_certificates import service as pending_certificate_service from lemur.pending_certificates import service as pending_certificate_service
from lemur.plugins.base import plugins from lemur.plugins.base import plugins
from lemur.users import service as user_service
flask_app = create_app() flask_app = create_app()
@ -57,7 +55,6 @@ def fetch_acme_cert(id):
"function": "{}.{}".format(__name__, sys._getframe().f_code.co_name) "function": "{}.{}".format(__name__, sys._getframe().f_code.co_name)
} }
pending_certs = pending_certificate_service.get_pending_certs([id]) pending_certs = pending_certificate_service.get_pending_certs([id])
user = user_service.get_by_username('lemur')
new = 0 new = 0
failed = 0 failed = 0
wrong_issuer = 0 wrong_issuer = 0
@ -78,12 +75,22 @@ def fetch_acme_cert(id):
real_cert = cert.get("cert") real_cert = cert.get("cert")
# It's necessary to reload the pending cert due to detached instance: http://sqlalche.me/e/bhk3 # It's necessary to reload the pending cert due to detached instance: http://sqlalche.me/e/bhk3
pending_cert = pending_certificate_service.get(cert.get("pending_cert").id) pending_cert = pending_certificate_service.get(cert.get("pending_cert").id)
if not pending_cert:
log_data["message"] = "Pending certificate doesn't exist anymore. Was it resolved by another process?"
current_app.logger.error(log_data)
continue
if real_cert: if real_cert:
# If a real certificate was returned from issuer, then create it in Lemur and delete # If a real certificate was returned from issuer, then create it in Lemur and mark
# the pending certificate # the pending certificate as resolved
pending_certificate_service.create_certificate(pending_cert, real_cert, user) final_cert = pending_certificate_service.create_certificate(pending_cert, real_cert, pending_cert.user)
pending_certificate_service.delete_by_id(pending_cert.id) pending_certificate_service.update(
cert.get("pending_cert").id,
resolved=True
)
pending_certificate_service.update(
cert.get("pending_cert").id,
resolved_cert_id=final_cert.id
)
# add metrics to metrics extension # add metrics to metrics extension
new += 1 new += 1
else: else:
@ -97,7 +104,11 @@ def fetch_acme_cert(id):
if pending_cert.number_attempts > 4: if pending_cert.number_attempts > 4:
error_log["message"] = "Deleting pending certificate" error_log["message"] = "Deleting pending certificate"
send_pending_failure_notification(pending_cert, notify_owner=pending_cert.notify) send_pending_failure_notification(pending_cert, notify_owner=pending_cert.notify)
pending_certificate_service.delete(pending_certificate_service.cancel(pending_cert)) # Mark the pending cert as resolved
pending_certificate_service.update(
cert.get("pending_cert").id,
resolved=True
)
else: else:
pending_certificate_service.increment_attempt(pending_cert) pending_certificate_service.increment_attempt(pending_cert)
pending_certificate_service.update( pending_certificate_service.update(
@ -124,12 +135,30 @@ def fetch_acme_cert(id):
@celery.task() @celery.task()
def fetch_all_pending_acme_certs(): def fetch_all_pending_acme_certs():
"""Instantiate celery workers to resolve all pending Acme certificates""" """Instantiate celery workers to resolve all pending Acme certificates"""
pending_certs = pending_certificate_service.get_pending_certs('all') pending_certs = pending_certificate_service.get_unresolved_pending_certs()
# We only care about certs using the acme-issuer plugin # We only care about certs using the acme-issuer plugin
for cert in pending_certs: for cert in pending_certs:
cert_authority = get_authority(cert.authority_id) cert_authority = get_authority(cert.authority_id)
if cert_authority.plugin_name == 'acme-issuer': if cert_authority.plugin_name == 'acme-issuer':
if cert.last_updated == cert.date_created or datetime.datetime.now( if cert.last_updated == cert.date_created or datetime.now(
timezone.utc) - cert.last_updated > datetime.timedelta(minutes=3): timezone.utc) - cert.last_updated > timedelta(minutes=3):
fetch_acme_cert.delay(cert.id) fetch_acme_cert.delay(cert.id)
@celery.task()
def remove_old_acme_certs():
"""Prune old pending acme certificates from the database"""
log_data = {
"function": "{}.{}".format(__name__, sys._getframe().f_code.co_name)
}
pending_certs = pending_certificate_service.get_pending_certs('all')
# Delete pending certs more than a week old
for cert in pending_certs:
if datetime.now(timezone.utc) - cert.last_updated > timedelta(days=7):
log_data['pending_cert_id'] = cert.id
log_data['pending_cert_name'] = cert.name
log_data['message'] = "Deleting pending certificate"
current_app.logger.debug(log_data)
pending_certificate_service.delete(cert.id)

View File

@ -15,7 +15,7 @@ from lemur.database import db
class Domain(db.Model): class Domain(db.Model):
__tablename__ = 'domains' __tablename__ = 'domains'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
name = Column(String(256)) name = Column(String(256), index=True)
sensitive = Column(Boolean, default=False) sensitive = Column(Boolean, default=False)
def __repr__(self): def __repr__(self):

View File

@ -0,0 +1,24 @@
"""Add status to pending certificate, and store resolved cert id
Revision ID: 984178255c83
Revises: f2383bf08fbc
Create Date: 2018-10-11 20:49:12.704563
"""
# revision identifiers, used by Alembic.
revision = '984178255c83'
down_revision = 'f2383bf08fbc'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('pending_certs', sa.Column('resolved', sa.Boolean(), nullable=True))
op.add_column('pending_certs', sa.Column('resolved_cert_id', sa.Integer(), nullable=True))
def downgrade():
op.drop_column('pending_certs', 'resolved_cert_id')
op.drop_column('pending_certs', 'resolved')

View File

@ -0,0 +1,19 @@
"""Create an index on the domains table for the domain name
Revision ID: c87cb989af04
Revises: 9392b9f9a805
Create Date: 2018-10-11 09:44:57.099854
"""
revision = 'c87cb989af04'
down_revision = '9392b9f9a805'
from alembic import op
def upgrade():
op.create_index(op.f('ix_domains_name'), 'domains', ['name'], unique=False)
def downgrade():
op.drop_index(op.f('ix_domains_name'), table_name='domains')

View File

@ -0,0 +1,23 @@
"""Create index on certificates table for id desc
Revision ID: f2383bf08fbc
Revises: c87cb989af04
Create Date: 2018-10-11 11:23:31.195471
"""
revision = 'f2383bf08fbc'
down_revision = 'c87cb989af04'
import sqlalchemy as sa
from alembic import op
def upgrade():
op.create_index('ix_certificates_id_desc', 'certificates', [sa.text('id DESC')], unique=True,
postgresql_using='btree')
def downgrade():
op.drop_index('ix_certificates_id_desc', table_name='certificates')

View File

@ -15,7 +15,6 @@ from lemur.authorities.service import get as get_authority
from lemur.notifications.messaging import send_pending_failure_notification from lemur.notifications.messaging import send_pending_failure_notification
from lemur.pending_certificates import service as pending_certificate_service from lemur.pending_certificates import service as pending_certificate_service
from lemur.plugins.base import plugins from lemur.plugins.base import plugins
from lemur.users import service as user_service
manager = Manager(usage="Handles pending certificate related tasks.") manager = Manager(usage="Handles pending certificate related tasks.")
@ -23,14 +22,14 @@ manager = Manager(usage="Handles pending certificate related tasks.")
@manager.option('-i', dest='ids', action='append', help='IDs of pending certificates to fetch') @manager.option('-i', dest='ids', action='append', help='IDs of pending certificates to fetch')
def fetch(ids): def fetch(ids):
""" """
Attempt to get full certificates for each pending certificate listed. Attempt to get full certificate for each pending certificate listed.
Args: Args:
ids: a list of ids of PendingCertificates (passed in by manager options when run as CLI) ids: a list of ids of PendingCertificates (passed in by manager options when run as CLI)
`python manager.py pending_certs fetch -i 123 321 all` `python manager.py pending_certs fetch -i 123 321 all`
""" """
pending_certs = pending_certificate_service.get_pending_certs(ids) pending_certs = pending_certificate_service.get_pending_certs(ids)
user = user_service.get_by_username('lemur')
new = 0 new = 0
failed = 0 failed = 0
@ -38,10 +37,17 @@ def fetch(ids):
authority = plugins.get(cert.authority.plugin_name) authority = plugins.get(cert.authority.plugin_name)
real_cert = authority.get_ordered_certificate(cert) real_cert = authority.get_ordered_certificate(cert)
if real_cert: if real_cert:
# If a real certificate was returned from issuer, then create it in Lemur and delete # If a real certificate was returned from issuer, then create it in Lemur and mark
# the pending certificate # the pending certificate as resolved
pending_certificate_service.create_certificate(cert, real_cert, user) final_cert = pending_certificate_service.create_certificate(cert, real_cert, cert.user)
pending_certificate_service.delete(cert) pending_certificate_service.update(
cert.id,
resolved=True
)
pending_certificate_service.update(
cert.id,
resolved_cert_id=final_cert.id
)
# add metrics to metrics extension # add metrics to metrics extension
new += 1 new += 1
else: else:
@ -66,8 +72,7 @@ def fetch_all_acme():
log_data = { log_data = {
"function": "{}.{}".format(__name__, sys._getframe().f_code.co_name) "function": "{}.{}".format(__name__, sys._getframe().f_code.co_name)
} }
pending_certs = pending_certificate_service.get_pending_certs('all') pending_certs = pending_certificate_service.get_unresolved_pending_certs()
user = user_service.get_by_username('lemur')
new = 0 new = 0
failed = 0 failed = 0
wrong_issuer = 0 wrong_issuer = 0
@ -90,10 +95,17 @@ def fetch_all_acme():
pending_cert = pending_certificate_service.get(cert.get("pending_cert").id) pending_cert = pending_certificate_service.get(cert.get("pending_cert").id)
if real_cert: if real_cert:
# If a real certificate was returned from issuer, then create it in Lemur and delete # If a real certificate was returned from issuer, then create it in Lemur and mark
# the pending certificate # the pending certificate as resolved
pending_certificate_service.create_certificate(pending_cert, real_cert, user) final_cert = pending_certificate_service.create_certificate(pending_cert, real_cert, pending_cert.user)
pending_certificate_service.delete_by_id(pending_cert.id) pending_certificate_service.update(
pending_cert.id,
resolved=True
)
pending_certificate_service.update(
pending_cert.id,
resolved_cert_id=final_cert.id
)
# add metrics to metrics extension # add metrics to metrics extension
new += 1 new += 1
else: else:
@ -105,9 +117,13 @@ def fetch_all_acme():
error_log["cn"] = pending_cert.cn error_log["cn"] = pending_cert.cn
if pending_cert.number_attempts > 4: if pending_cert.number_attempts > 4:
error_log["message"] = "Deleting pending certificate" error_log["message"] = "Marking pending certificate as resolved"
send_pending_failure_notification(pending_cert, notify_owner=pending_cert.notify) send_pending_failure_notification(pending_cert, notify_owner=pending_cert.notify)
pending_certificate_service.delete(pending_certificate_service.cancel(pending_cert)) # Mark "resolved" as True
pending_certificate_service.update(
cert.id,
resolved=True
)
else: else:
pending_certificate_service.increment_attempt(pending_cert) pending_certificate_service.increment_attempt(pending_cert)
pending_certificate_service.update( pending_certificate_service.update(

View File

@ -29,6 +29,8 @@ class PendingCertificate(db.Model):
notify = Column(Boolean, default=True) notify = Column(Boolean, default=True)
number_attempts = Column(Integer) number_attempts = Column(Integer)
rename = Column(Boolean, default=True) rename = Column(Boolean, default=True)
resolved = Column(Boolean, default=False)
resolved_cert_id = Column(Integer, nullable=True)
cn = Column(String(128)) cn = Column(String(128))
csr = Column(Text(), nullable=False) csr = Column(Text(), nullable=False)

View File

@ -37,6 +37,8 @@ class PendingCertificateOutputSchema(LemurOutputSchema):
number_attempts = fields.Integer() number_attempts = fields.Integer()
date_created = fields.Date() date_created = fields.Date()
last_updated = fields.Date() last_updated = fields.Date()
resolved = fields.Boolean(required=False)
resolved_cert_id = fields.Integer(required=False)
rotation = fields.Boolean() rotation = fields.Boolean()

View File

@ -4,25 +4,21 @@
.. moduleauthor:: James Chuong <jchuong@instartlogic.com> .. moduleauthor:: James Chuong <jchuong@instartlogic.com>
""" """
import arrow import arrow
from sqlalchemy import or_, cast, Integer from sqlalchemy import or_, cast, Integer
from lemur import database from lemur import database
from lemur.common.utils import truthiness
from lemur.plugins.base import plugins
from lemur.roles.models import Role
from lemur.domains.models import Domain
from lemur.authorities.models import Authority from lemur.authorities.models import Authority
from lemur.certificates import service as certificate_service
from lemur.certificates.schemas import CertificateUploadInputSchema
from lemur.common.utils import truthiness
from lemur.destinations.models import Destination from lemur.destinations.models import Destination
from lemur.domains.models import Domain
from lemur.notifications.models import Notification from lemur.notifications.models import Notification
from lemur.pending_certificates.models import PendingCertificate from lemur.pending_certificates.models import PendingCertificate
from lemur.plugins.base import plugins
from lemur.certificates import service as certificate_service from lemur.roles.models import Role
from lemur.users import service as user_service from lemur.users import service as user_service
from lemur.certificates.schemas import CertificateUploadInputSchema
def get(pending_cert_id): def get(pending_cert_id):
""" """
@ -63,6 +59,15 @@ def delete_by_id(id):
database.delete(get(id)) database.delete(get(id))
def get_unresolved_pending_certs():
"""
Retrieve a list of unresolved pending certs given a list of ids
Filters out non-existing pending certs
"""
query = database.session_query(PendingCertificate).filter(PendingCertificate.resolved.is_(False))
return database.find_all(query, PendingCertificate, {}).all()
def get_pending_certs(pending_ids): def get_pending_certs(pending_ids):
""" """
Retrieve a list of pending certs given a list of ids Retrieve a list of pending certs given a list of ids
@ -116,6 +121,7 @@ def create_certificate(pending_certificate, certificate, user):
# If generating name from certificate, remove the one from pending certificate # If generating name from certificate, remove the one from pending certificate
del data['name'] del data['name']
data['creator'] = creator data['creator'] = creator
cert = certificate_service.import_certificate(**data) cert = certificate_service.import_certificate(**data)
database.update(cert) database.update(cert)
return cert return cert
@ -221,4 +227,6 @@ def render(args):
now = arrow.now().format('YYYY-MM-DD') now = arrow.now().format('YYYY-MM-DD')
query = query.filter(PendingCertificate.not_after <= to).filter(PendingCertificate.not_after >= now) query = query.filter(PendingCertificate.not_after <= to).filter(PendingCertificate.not_after >= now)
# Only show unresolved certificates in the UI
query = query.filter(PendingCertificate.resolved.is_(False))
return database.sort_and_page(query, PendingCertificate, args) return database.sort_and_page(query, PendingCertificate, args)

View File

@ -5,26 +5,35 @@
# pip-compile --no-index --output-file requirements-dev.txt requirements-dev.in # pip-compile --no-index --output-file requirements-dev.txt requirements-dev.in
# #
aspy.yaml==1.1.1 # via pre-commit aspy.yaml==1.1.1 # via pre-commit
bleach==3.0.2 # via readme-renderer
cached-property==1.5.1 # via pre-commit cached-property==1.5.1 # via pre-commit
certifi==2018.8.24 # via requests certifi==2018.8.24 # via requests
cffi==1.11.5 # via cmarkgfm
cfgv==1.1.0 # via pre-commit cfgv==1.1.0 # via pre-commit
chardet==3.0.4 # via requests chardet==3.0.4 # via requests
cmarkgfm==0.4.2 # via readme-renderer
docutils==0.14 # via readme-renderer
flake8==3.5.0 flake8==3.5.0
identify==1.1.6 # via pre-commit future==0.16.0 # via readme-renderer
identify==1.1.7 # via pre-commit
idna==2.7 # via requests idna==2.7 # via requests
invoke==1.2.0 invoke==1.2.0
mccabe==0.6.1 # via flake8 mccabe==0.6.1 # via flake8
nodeenv==1.3.2 nodeenv==1.3.2
pkginfo==1.4.2 # via twine pkginfo==1.4.2 # via twine
pre-commit==1.11.0 pre-commit==1.11.2
pycodestyle==2.3.1 # via flake8 pycodestyle==2.3.1 # via flake8
pycparser==2.19 # via cffi
pyflakes==1.6.0 # via flake8 pyflakes==1.6.0 # via flake8
pygments==2.2.0 # via readme-renderer
pyyaml==3.13 # via aspy.yaml, pre-commit pyyaml==3.13 # via aspy.yaml, pre-commit
readme-renderer==22.0 # via twine
requests-toolbelt==0.8.0 # via twine requests-toolbelt==0.8.0 # via twine
requests==2.19.1 # via requests-toolbelt, twine requests==2.19.1 # via requests-toolbelt, twine
six==1.11.0 # via cfgv, pre-commit six==1.11.0 # via bleach, cfgv, pre-commit, readme-renderer
toml==0.9.6 # via pre-commit toml==0.10.0 # via pre-commit
tqdm==4.26.0 # via twine tqdm==4.26.0 # via twine
twine==1.11.0 twine==1.12.1
urllib3==1.23 # via requests urllib3==1.23 # via requests
virtualenv==16.0.0 # via pre-commit virtualenv==16.0.0 # via pre-commit
webencodings==0.5.1 # via bleach

View File

@ -5,7 +5,7 @@
# 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==0.27.1 acme==0.27.1
alabaster==0.7.11 # via sphinx alabaster==0.7.12 # via sphinx
alembic-autogenerate-enums==0.0.2 alembic-autogenerate-enums==0.0.2
alembic==1.0.0 alembic==1.0.0
amqp==2.3.2 amqp==2.3.2
@ -54,10 +54,10 @@ lockfile==0.12.2
mako==1.0.7 mako==1.0.7
markupsafe==1.0 markupsafe==1.0
marshmallow-sqlalchemy==0.14.1 marshmallow-sqlalchemy==0.14.1
marshmallow==2.15.4 marshmallow==2.15.5
mock==2.0.0 mock==2.0.0
ndg-httpsclient==0.5.1 ndg-httpsclient==0.5.1
packaging==17.1 # via sphinx packaging==18.0 # via sphinx
paramiko==2.4.1 paramiko==2.4.1
pbr==4.2.0 pbr==4.2.0
pem==18.1.0 pem==18.1.0
@ -69,7 +69,7 @@ pygments==2.2.0 # via sphinx
pyjwt==1.6.4 pyjwt==1.6.4
pynacl==1.2.1 pynacl==1.2.1
pyopenssl==18.0.0 pyopenssl==18.0.0
pyparsing==2.2.0 # via packaging pyparsing==2.2.2 # via packaging
pyrfc3339==1.1 pyrfc3339==1.1
python-dateutil==2.7.3 python-dateutil==2.7.3
python-editor==1.0.3 python-editor==1.0.3
@ -83,8 +83,8 @@ retrying==1.3.3
s3transfer==0.1.13 s3transfer==0.1.13
six==1.11.0 six==1.11.0
snowballstemmer==1.2.1 # via sphinx snowballstemmer==1.2.1 # via sphinx
sphinx-rtd-theme==0.4.1 sphinx-rtd-theme==0.4.2
sphinx==1.8.0 sphinx==1.8.1
sphinxcontrib-httpdomain==1.7.0 sphinxcontrib-httpdomain==1.7.0
sphinxcontrib-websupport==1.1.0 # via sphinx sphinxcontrib-websupport==1.1.0 # via sphinx
sqlalchemy-utils==0.33.4 sqlalchemy-utils==0.33.4

View File

@ -8,13 +8,13 @@ asn1crypto==0.24.0 # via cryptography
atomicwrites==1.2.1 # via pytest atomicwrites==1.2.1 # via pytest
attrs==18.2.0 # via pytest attrs==18.2.0 # via pytest
aws-xray-sdk==0.95 # via moto aws-xray-sdk==0.95 # via moto
boto3==1.9.4 # via moto boto3==1.9.21 # via moto
boto==2.49.0 # via moto boto==2.49.0 # via moto
botocore==1.12.4 # via boto3, moto, s3transfer botocore==1.12.21 # via boto3, moto, s3transfer
certifi==2018.8.24 # via requests certifi==2018.8.24 # via requests
cffi==1.11.5 # via cryptography cffi==1.11.5 # via cryptography
chardet==3.0.4 # via requests chardet==3.0.4 # via requests
click==6.7 # via flask click==7.0 # via flask
cookies==2.2.1 # via moto, responses cookies==2.2.1 # via moto, responses
coverage==4.5.1 coverage==4.5.1
cryptography==2.3.1 # via moto cryptography==2.3.1 # via moto
@ -32,22 +32,22 @@ itsdangerous==0.24 # via flask
jinja2==2.10 # via flask, moto jinja2==2.10 # via flask, moto
jmespath==0.9.3 # via boto3, botocore jmespath==0.9.3 # via boto3, botocore
jsondiff==1.1.1 # via moto jsondiff==1.1.1 # via moto
jsonpickle==0.9.6 # via aws-xray-sdk jsonpickle==1.0 # via aws-xray-sdk
markupsafe==1.0 # via jinja2 markupsafe==1.0 # via jinja2
mock==2.0.0 # via moto mock==2.0.0 # via moto
more-itertools==4.3.0 # via pytest more-itertools==4.3.0 # via pytest
moto==1.3.4 moto==1.3.4
nose==1.3.7 nose==1.3.7
pbr==4.2.0 # via mock pbr==4.3.0 # via mock
pluggy==0.7.1 # via pytest pluggy==0.7.1 # via pytest
py==1.6.0 # via pytest py==1.6.0 # via pytest
pyaml==17.12.1 # via moto pyaml==17.12.1 # via moto
pycparser==2.18 # via cffi pycparser==2.19 # via cffi
pycryptodome==3.6.6 # via python-jose pycryptodome==3.6.6 # via python-jose
pyflakes==2.0.0 pyflakes==2.0.0
pytest-flask==0.12.0 pytest-flask==0.13.0
pytest-mock==1.10.0 pytest-mock==1.10.0
pytest==3.8.0 pytest==3.8.2
python-dateutil==2.7.3 # via botocore, faker, freezegun, moto python-dateutil==2.7.3 # via botocore, faker, freezegun, moto
python-jose==2.0.2 # via moto python-jose==2.0.2 # via moto
pytz==2018.5 # via moto pytz==2018.5 # via moto

View File

@ -21,7 +21,7 @@ celery[redis]==4.2.1
certifi==2018.8.24 certifi==2018.8.24
cffi==1.11.5 # via bcrypt, cryptography, pynacl cffi==1.11.5 # via bcrypt, cryptography, pynacl
chardet==3.0.4 # via requests chardet==3.0.4 # via requests
click==6.7 # via flask click==7.0 # via flask
cloudflare==2.1.0 cloudflare==2.1.0
cryptography==2.3.1 cryptography==2.3.1
dnspython3==1.15.0 dnspython3==1.15.0
@ -51,18 +51,18 @@ lockfile==0.12.2
mako==1.0.7 # via alembic mako==1.0.7 # via alembic
markupsafe==1.0 # via jinja2, mako markupsafe==1.0 # via jinja2, mako
marshmallow-sqlalchemy==0.14.1 marshmallow-sqlalchemy==0.14.1
marshmallow==2.15.5 marshmallow==2.16.0
mock==2.0.0 # via acme mock==2.0.0 # via acme
ndg-httpsclient==0.5.1 ndg-httpsclient==0.5.1
paramiko==2.4.1 paramiko==2.4.2
pbr==4.2.0 # via mock pbr==4.3.0 # via mock
pem==18.1.0 pem==18.2.0
psycopg2==2.7.5 psycopg2==2.7.5
pyasn1-modules==0.2.2 # via python-ldap pyasn1-modules==0.2.2 # via python-ldap
pyasn1==0.4.4 # via ndg-httpsclient, paramiko, pyasn1-modules, python-ldap pyasn1==0.4.4 # via ndg-httpsclient, paramiko, pyasn1-modules, python-ldap
pycparser==2.18 # via cffi pycparser==2.19 # via cffi
pyjwt==1.6.4 pyjwt==1.6.4
pynacl==1.2.1 # via paramiko pynacl==1.3.0 # via paramiko
pyopenssl==18.0.0 pyopenssl==18.0.0
pyrfc3339==1.1 # via acme pyrfc3339==1.1 # via acme
python-dateutil==2.7.3 # via alembic, arrow, botocore python-dateutil==2.7.3 # via alembic, arrow, botocore
@ -77,8 +77,8 @@ requests[security]==2.19.1
retrying==1.3.3 retrying==1.3.3
s3transfer==0.1.13 # via boto3 s3transfer==0.1.13 # via boto3
six==1.11.0 six==1.11.0
sqlalchemy-utils==0.33.4 sqlalchemy-utils==0.33.5
sqlalchemy==1.2.11 # via alembic, flask-sqlalchemy, marshmallow-sqlalchemy, sqlalchemy-utils sqlalchemy==1.2.12 # via alembic, flask-sqlalchemy, marshmallow-sqlalchemy, sqlalchemy-utils
tabulate==0.8.2 tabulate==0.8.2
urllib3==1.23 # via requests urllib3==1.23 # via requests
vine==1.1.4 # via amqp vine==1.1.4 # via amqp