diff --git a/.travis.yml b/.travis.yml index f1abf3f3..f38555a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,8 @@ cache: env: global: - PIP_DOWNLOAD_CACHE=".pip_download_cache" + # The following line is a temporary workaround for this issue: https://github.com/pypa/setuptools/issues/2230 + - SETUPTOOLS_USE_DISTUTILS=stdlib # do not load /etc/boto.cfg with Python 3 incompatible plugin # https://github.com/travis-ci/travis-ci/issues/5246#issuecomment-166460882 - BOTO_CONFIG=/doesnotexist diff --git a/Makefile b/Makefile index 069eb29b..3312a41d 100644 --- a/Makefile +++ b/Makefile @@ -50,8 +50,10 @@ reset-db: setup-git: @echo "--> Installing git hooks" - git config branch.autosetuprebase always - cd .git/hooks && ln -sf ../../hooks/* ./ + if [ -d .git/hooks ]; then \ + git config branch.autosetuprebase always; \ + cd .git/hooks && ln -sf ../../hooks/* ./; \ + fi @echo "" clean: diff --git a/lemur/authorities/schemas.py b/lemur/authorities/schemas.py index 0700c15b..7f9f57d4 100644 --- a/lemur/authorities/schemas.py +++ b/lemur/authorities/schemas.py @@ -23,6 +23,7 @@ from lemur.common.schema import LemurInputSchema, LemurOutputSchema from lemur.common import validators, missing from lemur.common.fields import ArrowDateTime +from lemur.constants import CERTIFICATE_KEY_TYPES class AuthorityInputSchema(LemurInputSchema): @@ -56,11 +57,12 @@ class AuthorityInputSchema(LemurInputSchema): type = fields.String(validate=validate.OneOf(["root", "subca"]), missing="root") parent = fields.Nested(AssociatedAuthoritySchema) signing_algorithm = fields.String( - validate=validate.OneOf(["sha256WithRSA", "sha1WithRSA"]), + validate=validate.OneOf(["sha256WithRSA", "sha1WithRSA", + "sha256WithECDSA", "SHA384withECDSA", "SHA512withECDSA"]), missing="sha256WithRSA", ) key_type = fields.String( - validate=validate.OneOf(["RSA2048", "RSA4096"]), missing="RSA2048" + validate=validate.OneOf(CERTIFICATE_KEY_TYPES), missing="RSA2048" ) key_name = fields.String() sensitivity = fields.String( diff --git a/lemur/common/validators.py b/lemur/common/validators.py index 74095255..e1dfe3c1 100644 --- a/lemur/common/validators.py +++ b/lemur/common/validators.py @@ -152,18 +152,6 @@ def dates(data): data["authority"].authority_certificate.not_after ) ) - # Allow no more than PUBLIC_CA_MAX_VALIDITY_DAYS (Default: 397) days of validity - # for certs issued by public CA - # The list of public issuers can be managed through a config named PUBLIC_CA - public_CA = current_app.config.get("PUBLIC_CA_AUTHORITY_NAMES", []) - if data["authority"].name.lower() in [ca.lower() for ca in public_CA]: - max_validity_days = current_app.config.get("PUBLIC_CA_MAX_VALIDITY_DAYS", 397) - if ( - (data.get("validity_end").date() - data.get("validity_start").date()).days - > max_validity_days - ): - raise ValidationError("Certificate cannot be valid for more than " + - str(max_validity_days) + " days") return data diff --git a/lemur/static/app/angular/authorities/authority/options.tpl.html b/lemur/static/app/angular/authorities/authority/options.tpl.html index dbc4f40a..bf1ad70c 100644 --- a/lemur/static/app/angular/authorities/authority/options.tpl.html +++ b/lemur/static/app/angular/authorities/authority/options.tpl.html @@ -4,7 +4,7 @@ Signing Algorithm
- +
diff --git a/lemur/tests/test_authorities.py b/lemur/tests/test_authorities.py index 9649e949..fade39e8 100644 --- a/lemur/tests/test_authorities.py +++ b/lemur/tests/test_authorities.py @@ -34,6 +34,29 @@ def test_authority_input_schema(client, role, issuer_plugin, logged_in_user): assert not errors +def test_authority_input_schema_ecc(client, role, issuer_plugin, logged_in_user): + from lemur.authorities.schemas import AuthorityInputSchema + + input_data = { + "name": "Example Authority", + "owner": "jim@example.com", + "description": "An example authority.", + "commonName": "An Example Authority", + "plugin": { + "slug": "test-issuer", + "plugin_options": [{"name": "test", "value": "blah"}], + }, + "type": "root", + "signingAlgorithm": "sha256WithECDSA", + "keyType": "ECCPRIME256V1", + "sensitivity": "medium", + } + + data, errors = AuthorityInputSchema().load(input_data) + + assert not errors + + def test_user_authority(session, client, authority, role, user, issuer_plugin): u = user["user"] u.roles.append(role) diff --git a/requirements-dev.txt b/requirements-dev.txt index 2299848e..166722e8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -11,7 +11,7 @@ cffi==1.14.0 # via cryptography cfgv==3.1.0 # via pre-commit chardet==3.0.4 # via requests colorama==0.4.3 # via twine -cryptography==3.0 # via secretstorage +cryptography==3.1 # via secretstorage distlib==0.3.0 # via virtualenv docutils==0.16 # via readme-renderer filelock==3.0.12 # via virtualenv @@ -22,9 +22,9 @@ invoke==1.4.1 # via -r requirements-dev.in jeepney==0.4.3 # via keyring, secretstorage keyring==21.2.0 # via twine mccabe==0.6.1 # via flake8 -nodeenv==1.4.0 # via -r requirements-dev.in, pre-commit +nodeenv==1.5.0 # via -r requirements-dev.in, pre-commit pkginfo==1.5.0.1 # via twine -pre-commit==2.6.0 # via -r requirements-dev.in +pre-commit==2.7.1 # via -r requirements-dev.in pycodestyle==2.3.1 # via flake8 pycparser==2.20 # via cffi pyflakes==1.6.0 # via flake8 diff --git a/requirements-docs.txt b/requirements-docs.txt index 7e187213..37d50804 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -4,35 +4,35 @@ # # pip-compile --no-index --output-file=requirements-docs.txt requirements-docs.in # -acme==1.6.0 # via -r requirements.txt +acme==1.7.0 # via -r requirements.txt alabaster==0.7.12 # via sphinx alembic-autogenerate-enums==0.0.2 # via -r requirements.txt alembic==1.4.2 # via -r requirements.txt, flask-migrate amqp==2.5.2 # via -r requirements.txt, kombu aniso8601==8.0.0 # via -r requirements.txt, flask-restful -arrow==0.15.8 # via -r requirements.txt +arrow==0.16.0 # via -r requirements.txt asyncpool==1.0 # via -r requirements.txt babel==2.8.0 # via sphinx bcrypt==3.1.7 # via -r requirements.txt, flask-bcrypt, paramiko beautifulsoup4==4.9.1 # via -r requirements.txt, cloudflare billiard==3.6.3.0 # via -r requirements.txt, celery blinker==1.4 # via -r requirements.txt, flask-mail, flask-principal, raven -boto3==1.14.33 # via -r requirements.txt -botocore==1.17.33 # via -r requirements.txt, boto3, s3transfer +boto3==1.14.56 # via -r requirements.txt +botocore==1.17.56 # via -r requirements.txt, boto3, s3transfer celery[redis]==4.4.2 # via -r requirements.txt certifi==2020.6.20 # via -r requirements.txt, requests certsrv==2.1.1 # via -r requirements.txt cffi==1.14.0 # via -r requirements.txt, bcrypt, cryptography, pynacl chardet==3.0.4 # via -r requirements.txt, requests click==7.1.1 # via -r requirements.txt, flask -cloudflare==2.8.9 # via -r requirements.txt -cryptography==3.0 # via -r requirements.txt, acme, josepy, paramiko, pyopenssl, requests +cloudflare==2.8.13 # via -r requirements.txt +cryptography==3.1 # via -r requirements.txt, acme, josepy, paramiko, pyopenssl, requests dnspython3==1.15.0 # via -r requirements.txt dnspython==1.15.0 # via -r requirements.txt, dnspython3 docutils==0.15.2 # via -r requirements.txt, botocore, sphinx dyn==1.8.1 # via -r requirements.txt flask-bcrypt==0.7.1 # via -r requirements.txt -flask-cors==3.0.8 # via -r requirements.txt +flask-cors==3.0.9 # via -r requirements.txt flask-mail==0.9.1 # via -r requirements.txt flask-migrate==2.5.3 # via -r requirements.txt flask-principal==0.4.0 # via -r requirements.txt @@ -46,7 +46,7 @@ gunicorn==20.0.4 # via -r requirements.txt hvac==0.10.5 # via -r requirements.txt idna==2.9 # via -r requirements.txt, requests imagesize==1.2.0 # via sphinx -inflection==0.5.0 # via -r requirements.txt +inflection==0.5.1 # via -r requirements.txt itsdangerous==1.1.0 # via -r requirements.txt, flask javaobj-py3==0.4.0.1 # via -r requirements.txt, pyjks jinja2==2.11.2 # via -r requirements.txt, flask, sphinx @@ -62,9 +62,9 @@ marshmallow-sqlalchemy==0.23.1 # via -r requirements.txt marshmallow==2.20.4 # via -r requirements.txt, marshmallow-sqlalchemy ndg-httpsclient==0.5.1 # via -r requirements.txt packaging==20.3 # via sphinx -paramiko==2.7.1 # via -r requirements.txt +paramiko==2.7.2 # via -r requirements.txt pem==20.1.0 # via -r requirements.txt -psycopg2==2.8.5 # via -r requirements.txt +psycopg2==2.8.6 # via -r requirements.txt pyasn1-modules==0.2.8 # via -r requirements.txt, pyjks, python-ldap pyasn1==0.4.8 # via -r requirements.txt, ndg-httpsclient, pyasn1-modules, pyjks, python-ldap pycparser==2.20 # via -r requirements.txt, cffi @@ -92,7 +92,7 @@ six==1.15.0 # via -r requirements.txt, acme, bcrypt, cryptography, snowballstemmer==2.0.0 # via sphinx soupsieve==2.0.1 # via -r requirements.txt, beautifulsoup4 sphinx-rtd-theme==0.5.0 # via -r requirements-docs.in -sphinx==3.2.0 # via -r requirements-docs.in, sphinx-rtd-theme, sphinxcontrib-httpdomain +sphinx==3.2.1 # via -r requirements-docs.in, sphinx-rtd-theme, sphinxcontrib-httpdomain sphinxcontrib-applehelp==1.0.2 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx sphinxcontrib-htmlhelp==1.0.3 # via sphinx diff --git a/requirements-tests.txt b/requirements-tests.txt index 7fd13f76..e9106767 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -5,30 +5,30 @@ # pip-compile --no-index --output-file=requirements-tests.txt requirements-tests.in # appdirs==1.4.3 # via black -attrs==19.3.0 # via black, jsonschema, pytest +attrs==19.3.0 # via jsonschema, pytest aws-sam-translator==1.22.0 # via cfn-lint aws-xray-sdk==2.5.0 # via moto bandit==1.6.2 # via -r requirements-tests.in -black==19.10b0 # via -r requirements-tests.in -boto3==1.14.33 # via aws-sam-translator, moto +black==20.8b1 # via -r requirements-tests.in +boto3==1.14.56 # via aws-sam-translator, moto boto==2.49.0 # via moto -botocore==1.17.33 # via aws-xray-sdk, boto3, moto, s3transfer +botocore==1.17.56 # via aws-xray-sdk, boto3, moto, s3transfer certifi==2020.6.20 # via requests cffi==1.14.0 # via cryptography cfn-lint==0.29.5 # via moto chardet==3.0.4 # via requests -click==7.1.1 # via black, flask +click==7.1.2 # via black, flask coverage==5.2.1 # via -r requirements-tests.in -cryptography==3.0 # via moto, sshpubkeys +cryptography==3.1 # via moto, sshpubkeys decorator==4.4.2 # via networkx docker==4.2.0 # via moto docutils==0.15.2 # via botocore ecdsa==0.15 # via python-jose, sshpubkeys -factory-boy==2.12.0 # via -r requirements-tests.in -faker==4.1.1 # via -r requirements-tests.in, factory-boy -fakeredis==1.4.1 # via -r requirements-tests.in +factory-boy==3.0.1 # via -r requirements-tests.in +faker==4.1.2 # via -r requirements-tests.in, factory-boy +fakeredis==1.4.3 # via -r requirements-tests.in flask==1.1.2 # via pytest-flask -freezegun==0.3.15 # via -r requirements-tests.in +freezegun==1.0.0 # via -r requirements-tests.in future==0.18.2 # via aws-xray-sdk gitdb==4.0.4 # via gitpython gitpython==3.1.1 # via bandit @@ -47,6 +47,7 @@ markupsafe==1.1.1 # via jinja2 mock==4.0.2 # via moto more-itertools==8.2.0 # via pytest moto==1.3.14 # via -r requirements-tests.in +mypy-extensions==0.4.3 # via black networkx==2.4 # via cfn-lint nose==1.3.7 # via -r requirements-tests.in packaging==20.3 # via pytest @@ -60,7 +61,7 @@ pyflakes==2.2.0 # via -r requirements-tests.in pyparsing==2.4.7 # via packaging pyrsistent==0.16.0 # via jsonschema pytest-flask==1.0.0 # via -r requirements-tests.in -pytest-mock==3.2.0 # via -r requirements-tests.in +pytest-mock==3.3.1 # via -r requirements-tests.in pytest==6.0.1 # via -r requirements-tests.in, pytest-flask, pytest-mock python-dateutil==2.8.1 # via botocore, faker, freezegun, moto python-jose==3.1.0 # via moto @@ -73,14 +74,15 @@ requests==2.24.0 # via docker, moto, requests-mock, responses responses==0.10.12 # via moto rsa==4.0 # via python-jose s3transfer==0.3.3 # via boto3 -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 +six==1.15.0 # via aws-sam-translator, bandit, cfn-lint, cryptography, docker, ecdsa, fakeredis, jsonschema, moto, packaging, pyrsistent, python-dateutil, python-jose, requests-mock, responses, stevedore, websocket-client smmap==3.0.2 # via gitdb sortedcontainers==2.1.0 # via fakeredis sshpubkeys==3.1.0 # via moto stevedore==1.32.0 # via bandit text-unidecode==1.3 # via faker -toml==0.10.0 # via black, pytest +toml==0.10.1 # via black, pytest typed-ast==1.4.1 # via black +typing-extensions==3.7.4.3 # via black urllib3==1.25.8 # via botocore, requests websocket-client==0.57.0 # via docker werkzeug==1.0.1 # via flask, moto, pytest-flask diff --git a/requirements.txt b/requirements.txt index d9e86d97..64e41b3c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,33 +4,33 @@ # # pip-compile --no-index --output-file=requirements.txt requirements.in # -acme==1.6.0 # via -r requirements.in +acme==1.7.0 # via -r requirements.in alembic-autogenerate-enums==0.0.2 # via -r requirements.in alembic==1.4.2 # via flask-migrate amqp==2.5.2 # via kombu aniso8601==8.0.0 # via flask-restful -arrow==0.15.8 # via -r requirements.in +arrow==0.16.0 # via -r requirements.in asyncpool==1.0 # via -r requirements.in bcrypt==3.1.7 # via flask-bcrypt, paramiko beautifulsoup4==4.9.1 # via cloudflare billiard==3.6.3.0 # via celery blinker==1.4 # via flask-mail, flask-principal, raven -boto3==1.14.33 # via -r requirements.in -botocore==1.17.33 # via -r requirements.in, boto3, s3transfer +boto3==1.14.56 # via -r requirements.in +botocore==1.17.56 # via -r requirements.in, boto3, s3transfer celery[redis]==4.4.2 # via -r requirements.in certifi==2020.6.20 # via -r requirements.in, requests certsrv==2.1.1 # via -r requirements.in cffi==1.14.0 # via bcrypt, cryptography, pynacl chardet==3.0.4 # via requests click==7.1.1 # via flask -cloudflare==2.8.9 # via -r requirements.in -cryptography==3.0 # via -r requirements.in, acme, josepy, paramiko, pyopenssl, requests +cloudflare==2.8.13 # via -r requirements.in +cryptography==3.1 # via -r requirements.in, acme, josepy, paramiko, pyopenssl, requests dnspython3==1.15.0 # via -r requirements.in dnspython==1.15.0 # via dnspython3 docutils==0.15.2 # via botocore dyn==1.8.1 # via -r requirements.in flask-bcrypt==0.7.1 # via -r requirements.in -flask-cors==3.0.8 # via -r requirements.in +flask-cors==3.0.9 # via -r requirements.in flask-mail==0.9.1 # via -r requirements.in flask-migrate==2.5.3 # via -r requirements.in flask-principal==0.4.0 # via -r requirements.in @@ -43,7 +43,7 @@ future==0.18.2 # via -r requirements.in gunicorn==20.0.4 # via -r requirements.in hvac==0.10.5 # via -r requirements.in idna==2.9 # via requests -inflection==0.5.0 # via -r requirements.in +inflection==0.5.1 # via -r requirements.in itsdangerous==1.1.0 # via flask javaobj-py3==0.4.0.1 # via pyjks jinja2==2.11.2 # via -r requirements.in, flask @@ -58,9 +58,9 @@ markupsafe==1.1.1 # via jinja2, mako marshmallow-sqlalchemy==0.23.1 # via -r requirements.in marshmallow==2.20.4 # via -r requirements.in, marshmallow-sqlalchemy ndg-httpsclient==0.5.1 # via -r requirements.in -paramiko==2.7.1 # via -r requirements.in +paramiko==2.7.2 # via -r requirements.in pem==20.1.0 # via -r requirements.in -psycopg2==2.8.5 # via -r requirements.in +psycopg2==2.8.6 # via -r requirements.in pyasn1-modules==0.2.8 # via pyjks, python-ldap pyasn1==0.4.8 # via ndg-httpsclient, pyasn1-modules, pyjks, python-ldap pycparser==2.20 # via cffi diff --git a/setup.py b/setup.py index 4ce03d70..a612cd18 100644 --- a/setup.py +++ b/setup.py @@ -9,30 +9,18 @@ Is a TLS management and orchestration tool. """ from __future__ import absolute_import -import sys -import json -import os.path import datetime +import json +import logging +import os.path +import sys +from subprocess import check_output -from distutils import log -from distutils.core import Command +from setuptools import Command +from setuptools import setup, find_packages from setuptools.command.develop import develop from setuptools.command.install import install from setuptools.command.sdist import sdist -from setuptools import setup, find_packages -from subprocess import check_output - -import pip -if tuple(map(int, pip.__version__.split('.'))) >= (19, 3, 0): - from pip._internal.network.session import PipSession - from pip._internal.req import parse_requirements - -elif tuple(map(int, pip.__version__.split('.'))) >= (10, 0, 0): - from pip._internal.download import PipSession - from pip._internal.req import parse_requirements -else: - from pip.download import PipSession - from pip.req import parse_requirements ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__))) @@ -44,21 +32,18 @@ about = {} with open(os.path.join(ROOT, 'lemur', '__about__.py')) as f: exec(f.read(), about) # nosec: about file is benign -install_requires_g = parse_requirements("requirements.txt", session=PipSession()) -tests_require_g = parse_requirements("requirements-tests.txt", session=PipSession()) -docs_require_g = parse_requirements("requirements-docs.txt", session=PipSession()) -dev_requires_g = parse_requirements("requirements-dev.txt", session=PipSession()) +# Parse requirements files +with open('requirements.txt') as f: + install_requirements = f.read().splitlines() -if tuple(map(int, pip.__version__.split('.'))) >= (20, 1): - install_requires = [str(ir.requirement) for ir in install_requires_g] - tests_require = [str(ir.requirement) for ir in tests_require_g] - docs_require = [str(ir.requirement) for ir in docs_require_g] - dev_requires = [str(ir.requirement) for ir in dev_requires_g] -else: - install_requires = [str(ir.req) for ir in install_requires_g] - tests_require = [str(ir.req) for ir in tests_require_g] - docs_require = [str(ir.req) for ir in docs_require_g] - dev_requires = [str(ir.req) for ir in dev_requires_g] +with open('requirements-tests.txt') as f: + tests_requirements = f.read().splitlines() + +with open('requirements-docs.txt') as f: + docs_requirements = f.read().splitlines() + +with open('requirements-dev.txt') as f: + dev_requirements = f.read().splitlines() class SmartInstall(install): @@ -67,6 +52,7 @@ class SmartInstall(install): If the package indicator is missing, this will also force a run of `build_static` which is required for JavaScript assets and other things. """ + def _needs_static(self): return not os.path.exists(os.path.join(ROOT, 'lemur/static/dist')) @@ -105,16 +91,16 @@ class BuildStatic(Command): pass def run(self): - log.info("running [npm install --quiet] in {0}".format(ROOT)) + logging.info("running [npm install --quiet] in {0}".format(ROOT)) try: check_output(['npm', 'install', '--quiet'], cwd=ROOT) - log.info("running [gulp build]") + logging.info("running [gulp build]") check_output([os.path.join(ROOT, 'node_modules', '.bin', 'gulp'), 'build'], cwd=ROOT) - log.info("running [gulp package]") + logging.info("running [gulp package]") check_output([os.path.join(ROOT, 'node_modules', '.bin', 'gulp'), 'package'], cwd=ROOT) except Exception as e: - log.warn("Unable to build static content") + logging.warn("Unable to build static content") setup( @@ -128,11 +114,11 @@ setup( packages=find_packages(), include_package_data=True, zip_safe=False, - install_requires=install_requires, + install_requires=install_requirements, extras_require={ - 'tests': tests_require, - 'docs': docs_require, - 'dev': dev_requires, + 'tests': tests_requirements, + 'docs': docs_requirements, + 'dev': dev_requirements, }, cmdclass={ 'build_static': BuildStatic,