Merge branch 'master' into fix-http-proxy-security-alert
This commit is contained in:
commit
2b40d2743c
@ -20,6 +20,8 @@ cache:
|
|||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- PIP_DOWNLOAD_CACHE=".pip_download_cache"
|
- 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
|
# do not load /etc/boto.cfg with Python 3 incompatible plugin
|
||||||
# https://github.com/travis-ci/travis-ci/issues/5246#issuecomment-166460882
|
# https://github.com/travis-ci/travis-ci/issues/5246#issuecomment-166460882
|
||||||
- BOTO_CONFIG=/doesnotexist
|
- BOTO_CONFIG=/doesnotexist
|
||||||
|
@ -4,6 +4,7 @@ RUN apt-get install -y make software-properties-common curl
|
|||||||
RUN curl -sL https://deb.nodesource.com/setup_7.x | bash -
|
RUN curl -sL https://deb.nodesource.com/setup_7.x | bash -
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get install -y npm libldap2-dev libsasl2-dev libldap2-dev libssl-dev
|
RUN apt-get install -y npm libldap2-dev libsasl2-dev libldap2-dev libssl-dev
|
||||||
|
RUN pip install pip==20.0.2
|
||||||
RUN pip install -U setuptools
|
RUN pip install -U setuptools
|
||||||
RUN pip install coveralls bandit
|
RUN pip install coveralls bandit
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
6
Makefile
6
Makefile
@ -50,8 +50,10 @@ reset-db:
|
|||||||
|
|
||||||
setup-git:
|
setup-git:
|
||||||
@echo "--> Installing git hooks"
|
@echo "--> Installing git hooks"
|
||||||
git config branch.autosetuprebase always
|
if [ -d .git/hooks ]; then \
|
||||||
cd .git/hooks && ln -sf ../../hooks/* ./
|
git config branch.autosetuprebase always; \
|
||||||
|
cd .git/hooks && ln -sf ../../hooks/* ./; \
|
||||||
|
fi
|
||||||
@echo ""
|
@echo ""
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
@ -66,7 +66,7 @@ Basic Configuration
|
|||||||
|
|
||||||
|
|
||||||
.. data:: SQLALCHEMY_POOL_SIZE
|
.. data:: SQLALCHEMY_POOL_SIZE
|
||||||
:noindex:
|
:noindex:
|
||||||
|
|
||||||
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.
|
||||||
@ -80,7 +80,7 @@ Basic Configuration
|
|||||||
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:
|
||||||
|
|
||||||
This setting allows to create connections in addition to specified number of connections in pool size. By default, sqlalchemy
|
This setting allows to create connections in addition to specified number of connections in pool size. By default, sqlalchemy
|
||||||
allows 10 connections to create in addition to the pool size. This is also an optional setting. If `SQLALCHEMY_POOL_SIZE` and
|
allows 10 connections to create in addition to the pool size. This is also an optional setting. If `SQLALCHEMY_POOL_SIZE` and
|
||||||
@ -155,6 +155,34 @@ Specifying the `SQLALCHEMY_MAX_OVERFLOW` to 0 will enforce limit to not create c
|
|||||||
|
|
||||||
LEMUR_ENCRYPTION_KEYS = ['1YeftooSbxCiX2zo8m1lXtpvQjy27smZcUUaGmffhMY=', 'LAfQt6yrkLqOK5lwpvQcT4jf2zdeTQJV1uYeh9coT5s=']
|
LEMUR_ENCRYPTION_KEYS = ['1YeftooSbxCiX2zo8m1lXtpvQjy27smZcUUaGmffhMY=', 'LAfQt6yrkLqOK5lwpvQcT4jf2zdeTQJV1uYeh9coT5s=']
|
||||||
|
|
||||||
|
.. data:: PUBLIC_CA_AUTHORITY_NAMES
|
||||||
|
:noindex:
|
||||||
|
A list of public issuers which would be checked against to determine whether limit of max validity of 397 days
|
||||||
|
should be applied to the certificate. Configure public CA authority names in this list to enforce validity check.
|
||||||
|
This is an optional setting. Using this will allow the sanity check as mentioned. The name check is a case-insensitive
|
||||||
|
string comparision.
|
||||||
|
|
||||||
|
.. data:: PUBLIC_CA_MAX_VALIDITY_DAYS
|
||||||
|
:noindex:
|
||||||
|
Use this config to override the limit of 397 days of validity for certificates issued by public issuers configured
|
||||||
|
using PUBLIC_CA_AUTHORITY_NAMES. Below example overrides the default validity of 397 days and sets it to 365 days.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
PUBLIC_CA_MAX_VALIDITY_DAYS = 365
|
||||||
|
|
||||||
|
|
||||||
|
.. data:: DEFAULT_VALIDITY_DAYS
|
||||||
|
:noindex:
|
||||||
|
Use this config to override the default validity of 365 days for certificates offered through Lemur UI. Any CA which
|
||||||
|
is not listed in PUBLIC_CA_AUTHORITY_NAMES 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. Below example overrides the default validity
|
||||||
|
of 365 days and sets it to 1095 days (3 years).
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
DEFAULT_VALIDITY_DAYS = 1095
|
||||||
|
|
||||||
|
|
||||||
.. data:: DEBUG_DUMP
|
.. data:: DEBUG_DUMP
|
||||||
:noindex:
|
:noindex:
|
||||||
@ -213,7 +241,7 @@ and are used when Lemur creates the CSR for your certificates.
|
|||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
LEMUR_DEFAULT_ORGANIZATIONAL_UNIT = "Operations"
|
LEMUR_DEFAULT_ORGANIZATIONAL_UNIT = ""
|
||||||
|
|
||||||
|
|
||||||
.. data:: LEMUR_DEFAULT_ISSUER_PLUGIN
|
.. data:: LEMUR_DEFAULT_ISSUER_PLUGIN
|
||||||
@ -625,13 +653,20 @@ Active Directory Certificate Services Plugin
|
|||||||
:noindex:
|
:noindex:
|
||||||
|
|
||||||
Template to be used for certificate issuing. Usually display name w/o spaces
|
Template to be used for certificate issuing. Usually display name w/o spaces
|
||||||
|
|
||||||
|
.. data:: ADCS_TEMPLATE_<upper(authority.name)>
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
If there is a config variable ADCS_TEMPLATE_<upper(authority.name)> take the value as Cert template else default to ADCS_TEMPLATE to be compatible with former versions. Template to be used for certificate issuing. Usually display name w/o spaces
|
||||||
|
|
||||||
.. 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
|
||||||
|
|
||||||
.. data:: ADCS_STOP
|
.. data:: ADCS_STOP
|
||||||
:noindex:
|
:noindex:
|
||||||
|
Used for ADCS-Sourceplugin. Maximum id of the certificates returned.
|
||||||
|
|
||||||
|
|
||||||
.. data:: ADCS_ISSUING
|
.. data:: ADCS_ISSUING
|
||||||
:noindex:
|
:noindex:
|
||||||
@ -644,6 +679,68 @@ Active Directory Certificate Services Plugin
|
|||||||
|
|
||||||
Contains the root cert of the CA
|
Contains the root cert of the CA
|
||||||
|
|
||||||
|
Entrust Plugin
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Enables the creation of Entrust certificates. You need to set the API access up with Entrust support. Check the information in the Entrust Portal as well.
|
||||||
|
Certificates are created as "SERVER_AND_CLIENT_AUTH".
|
||||||
|
Caution: Sometimes the entrust API does not respond in a timely manner. This error is handled and reported by the plugin. Should this happen you just have to hit the create button again after to create a valid certificate.
|
||||||
|
The following parameters have to be set in the configuration files.
|
||||||
|
|
||||||
|
.. data:: ENTRUST_URL
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
This is the url for the Entrust API. Refer to the API documentation.
|
||||||
|
|
||||||
|
.. data:: ENTRUST_API_CERT
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
Path to the certificate file in PEM format. This certificate is created in the onboarding process. Refer to the API documentation.
|
||||||
|
|
||||||
|
.. data:: ENTRUST_API_KEY
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
Path to the key file in RSA format. This certificate is created in the onboarding process. Refer to the API documentation. Caution: the request library cannot handle encrypted keys. The keyfile therefore has to contain the unencrypted key. Please put this in a secure location on the server.
|
||||||
|
|
||||||
|
.. data:: ENTRUST_API_USER
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
String with the API user. This user is created in the onboarding process. Refer to the API documentation.
|
||||||
|
|
||||||
|
.. data:: ENTRUST_API_PASS
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
String with the password for the API user. This password is created in the onboarding process. Refer to the API documentation.
|
||||||
|
|
||||||
|
.. data:: ENTRUST_NAME
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
String with the name that should appear as certificate owner in the Entrust portal. Refer to the API documentation.
|
||||||
|
|
||||||
|
.. data:: ENTRUST_EMAIL
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
String with the email address that should appear as certificate contact email in the Entrust portal. Refer to the API documentation.
|
||||||
|
|
||||||
|
.. data:: ENTRUST_PHONE
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
String with the phone number that should appear as certificate contact in the Entrust portal. Refer to the API documentation.
|
||||||
|
|
||||||
|
.. data:: ENTRUST_ISSUING
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
Contains the issuing cert of the CA
|
||||||
|
|
||||||
|
.. data:: ENTRUST_ROOT
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
Contains the root cert of the CA
|
||||||
|
|
||||||
|
.. data:: ENTRUST_PRODUCT_<upper(authority.name)>
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
If there is a config variable ENTRUST_PRODUCT_<upper(authority.name)> take the value as cert product name else default to "STANDARD_SSL". Refer to the API documentation for valid products names.
|
||||||
|
|
||||||
Verisign Issuer Plugin
|
Verisign Issuer Plugin
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
@ -729,16 +826,16 @@ The following configuration properties are required to use the Digicert issuer p
|
|||||||
This is the root to be used for your CA chain
|
This is the root to be used for your CA chain
|
||||||
|
|
||||||
|
|
||||||
.. data:: DIGICERT_DEFAULT_VALIDITY
|
.. data:: DIGICERT_DEFAULT_VALIDITY_DAYS
|
||||||
:noindex:
|
:noindex:
|
||||||
|
|
||||||
This is the default validity (in years), if no end date is specified. (Default: 1)
|
This is the default validity (in days), if no end date is specified. (Default: 397)
|
||||||
|
|
||||||
|
|
||||||
.. data:: DIGICERT_MAX_VALIDITY
|
.. data:: DIGICERT_MAX_VALIDITY_DAYS
|
||||||
:noindex:
|
:noindex:
|
||||||
|
|
||||||
This is the maximum validity (in years). (Default: value of DIGICERT_DEFAULT_VALIDITY)
|
This is the maximum validity (in days). (Default: value of DIGICERT_DEFAULT_VALIDITY_DAYS)
|
||||||
|
|
||||||
|
|
||||||
.. data:: DIGICERT_PRIVATE
|
.. data:: DIGICERT_PRIVATE
|
||||||
|
@ -451,3 +451,53 @@ LetsEncrypt flow to function. However, Lemur will attempt to automatically deter
|
|||||||
possible. To enable this functionality, periodically (or through Cron/Celery) run `lemur dns_providers get_all_zones`.
|
possible. To enable this functionality, periodically (or through Cron/Celery) run `lemur dns_providers get_all_zones`.
|
||||||
This command will traverse all DNS providers, determine which zones they control, and upload this list of zones to
|
This command will traverse all DNS providers, determine which zones they control, and upload this list of zones to
|
||||||
Lemur's database (in the dns_providers table). Alternatively, you can manually input this data.
|
Lemur's database (in the dns_providers table). Alternatively, you can manually input this data.
|
||||||
|
|
||||||
|
|
||||||
|
LetsEncrypt: pinning to cross-signed ICA
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
Let's Encrypt has been using a `cross-signed <https://letsencrypt.org/certificates/>`_ intermediate CA by DST Root CA X3,
|
||||||
|
which is included in many older devices' TrustStore.
|
||||||
|
|
||||||
|
|
||||||
|
Let's Encrypt is `transitioning <https://letsencrypt.org/2019/04/15/transitioning-to-isrg-root.html>`_ to use
|
||||||
|
the intermediate CA issued by their own root (ISRG X1) starting from September 29th 2020.
|
||||||
|
This is in preparation of concluding the initial bootstrapping of their CA, by having it cross-signed by an older CA.
|
||||||
|
|
||||||
|
|
||||||
|
Lemur can temporarily pin to the cross-signed intermediate CA (same public/private key pair as the ICA signed by ISRG X1).
|
||||||
|
This will prolong support for incompatible devices.
|
||||||
|
|
||||||
|
The following must be added to the config file to activate the pinning (the pinning will be removed by September 2021)::
|
||||||
|
|
||||||
|
# remove or update after Mar 17 16:40:46 2021 GMT
|
||||||
|
IDENTRUST_CROSS_SIGNED_LE_ICA_EXPIRATION_DATE = "17/03/21"
|
||||||
|
IDENTRUST_CROSS_SIGNED_LE_ICA = """
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
|
||||||
|
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
|
||||||
|
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
|
||||||
|
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
|
||||||
|
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
|
||||||
|
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
|
||||||
|
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
|
||||||
|
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
|
||||||
|
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
|
||||||
|
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
|
||||||
|
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
|
||||||
|
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
|
||||||
|
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
|
||||||
|
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
|
||||||
|
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
|
||||||
|
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
|
||||||
|
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
|
||||||
|
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
|
||||||
|
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
|
||||||
|
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
|
||||||
|
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
|
||||||
|
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
|
||||||
|
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
|
||||||
|
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
|
||||||
|
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
"""
|
||||||
|
@ -23,6 +23,7 @@ from lemur.common.schema import LemurInputSchema, LemurOutputSchema
|
|||||||
from lemur.common import validators, missing
|
from lemur.common import validators, missing
|
||||||
|
|
||||||
from lemur.common.fields import ArrowDateTime
|
from lemur.common.fields import ArrowDateTime
|
||||||
|
from lemur.constants import CERTIFICATE_KEY_TYPES
|
||||||
|
|
||||||
|
|
||||||
class AuthorityInputSchema(LemurInputSchema):
|
class AuthorityInputSchema(LemurInputSchema):
|
||||||
@ -56,11 +57,12 @@ class AuthorityInputSchema(LemurInputSchema):
|
|||||||
type = fields.String(validate=validate.OneOf(["root", "subca"]), missing="root")
|
type = fields.String(validate=validate.OneOf(["root", "subca"]), missing="root")
|
||||||
parent = fields.Nested(AssociatedAuthoritySchema)
|
parent = fields.Nested(AssociatedAuthoritySchema)
|
||||||
signing_algorithm = fields.String(
|
signing_algorithm = fields.String(
|
||||||
validate=validate.OneOf(["sha256WithRSA", "sha1WithRSA"]),
|
validate=validate.OneOf(["sha256WithRSA", "sha1WithRSA",
|
||||||
|
"sha256WithECDSA", "SHA384withECDSA", "SHA512withECDSA"]),
|
||||||
missing="sha256WithRSA",
|
missing="sha256WithRSA",
|
||||||
)
|
)
|
||||||
key_type = fields.String(
|
key_type = fields.String(
|
||||||
validate=validate.OneOf(["RSA2048", "RSA4096"]), missing="RSA2048"
|
validate=validate.OneOf(CERTIFICATE_KEY_TYPES), missing="RSA2048"
|
||||||
)
|
)
|
||||||
key_name = fields.String()
|
key_name = fields.String()
|
||||||
sensitivity = fields.String(
|
sensitivity = fields.String(
|
||||||
@ -109,6 +111,8 @@ class RootAuthorityCertificateOutputSchema(LemurOutputSchema):
|
|||||||
cn = fields.String()
|
cn = fields.String()
|
||||||
not_after = fields.DateTime()
|
not_after = fields.DateTime()
|
||||||
not_before = fields.DateTime()
|
not_before = fields.DateTime()
|
||||||
|
max_issuance_days = fields.Integer()
|
||||||
|
default_validity_days = fields.Integer()
|
||||||
owner = fields.Email()
|
owner = fields.Email()
|
||||||
status = fields.Boolean()
|
status = fields.Boolean()
|
||||||
user = fields.Nested(UserNestedOutputSchema)
|
user = fields.Nested(UserNestedOutputSchema)
|
||||||
@ -134,6 +138,7 @@ class AuthorityNestedOutputSchema(LemurOutputSchema):
|
|||||||
owner = fields.Email()
|
owner = fields.Email()
|
||||||
plugin = fields.Nested(PluginOutputSchema)
|
plugin = fields.Nested(PluginOutputSchema)
|
||||||
active = fields.Boolean()
|
active = fields.Boolean()
|
||||||
|
authority_certificate = fields.Nested(RootAuthorityCertificateOutputSchema, only=["max_issuance_days", "default_validity_days"])
|
||||||
|
|
||||||
|
|
||||||
authority_update_schema = AuthorityUpdateSchema()
|
authority_update_schema = AuthorityUpdateSchema()
|
||||||
|
@ -9,9 +9,10 @@ from datetime import timedelta
|
|||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
from cryptography.hazmat.primitives.asymmetric import rsa, ec
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from idna.core import InvalidCodepoint
|
from idna.core import InvalidCodepoint
|
||||||
|
from lemur.common.utils import get_key_type_from_ec_curve
|
||||||
from sqlalchemy import (
|
from sqlalchemy import (
|
||||||
event,
|
event,
|
||||||
Integer,
|
Integer,
|
||||||
@ -302,6 +303,8 @@ class Certificate(db.Model):
|
|||||||
return "RSA{key_size}".format(
|
return "RSA{key_size}".format(
|
||||||
key_size=self.parsed_cert.public_key().key_size
|
key_size=self.parsed_cert.public_key().key_size
|
||||||
)
|
)
|
||||||
|
elif isinstance(self.parsed_cert.public_key(), ec.EllipticCurvePublicKey):
|
||||||
|
return get_key_type_from_ec_curve(self.parsed_cert.public_key().curve.name)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def validity_remaining(self):
|
def validity_remaining(self):
|
||||||
@ -311,6 +314,20 @@ class Certificate(db.Model):
|
|||||||
def validity_range(self):
|
def validity_range(self):
|
||||||
return self.not_after - self.not_before
|
return self.not_after - self.not_before
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_issuance_days(self):
|
||||||
|
public_CA = current_app.config.get("PUBLIC_CA_AUTHORITY_NAMES", [])
|
||||||
|
if self.name.lower() in [ca.lower() for ca in public_CA]:
|
||||||
|
return current_app.config.get("PUBLIC_CA_MAX_VALIDITY_DAYS", 397)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default_validity_days(self):
|
||||||
|
public_CA = current_app.config.get("PUBLIC_CA_AUTHORITY_NAMES", [])
|
||||||
|
if self.name.lower() in [ca.lower() for ca in public_CA]:
|
||||||
|
return current_app.config.get("PUBLIC_CA_MAX_VALIDITY_DAYS", 397)
|
||||||
|
|
||||||
|
return current_app.config.get("DEFAULT_VALIDITY_DAYS", 365) # 1 year default
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def subject(self):
|
def subject(self):
|
||||||
return self.parsed_cert.subject
|
return self.parsed_cert.subject
|
||||||
|
@ -148,6 +148,13 @@ class CertificateInputSchema(CertificateCreationSchema):
|
|||||||
data["extensions"]["subAltNames"]["names"] = []
|
data["extensions"]["subAltNames"]["names"] = []
|
||||||
|
|
||||||
data["extensions"]["subAltNames"]["names"] = csr_sans
|
data["extensions"]["subAltNames"]["names"] = csr_sans
|
||||||
|
|
||||||
|
common_name = cert_utils.get_cn_from_csr(data["csr"])
|
||||||
|
if common_name:
|
||||||
|
data["common_name"] = common_name
|
||||||
|
key_type = cert_utils.get_key_type_from_csr(data["csr"])
|
||||||
|
if key_type:
|
||||||
|
data["key_type"] = key_type
|
||||||
return missing.convert_validity_years(data)
|
return missing.convert_validity_years(data)
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,6 +12,8 @@ Utils to parse certificate data.
|
|||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
from marshmallow.exceptions import ValidationError
|
from marshmallow.exceptions import ValidationError
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import rsa, ec
|
||||||
|
from lemur.common.utils import get_key_type_from_ec_curve
|
||||||
|
|
||||||
|
|
||||||
def get_sans_from_csr(data):
|
def get_sans_from_csr(data):
|
||||||
@ -39,3 +41,45 @@ def get_sans_from_csr(data):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
return sub_alt_names
|
return sub_alt_names
|
||||||
|
|
||||||
|
|
||||||
|
def get_cn_from_csr(data):
|
||||||
|
"""
|
||||||
|
Fetches common name (CN) from CSR.
|
||||||
|
Works with any kind of SubjectAlternativeName
|
||||||
|
:param data: PEM-encoded string with CSR
|
||||||
|
:return: the common name
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
request = x509.load_pem_x509_csr(data.encode("utf-8"), default_backend())
|
||||||
|
except Exception:
|
||||||
|
raise ValidationError("CSR presented is not valid.")
|
||||||
|
|
||||||
|
common_name = request.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)
|
||||||
|
return common_name[0].value
|
||||||
|
|
||||||
|
|
||||||
|
def get_key_type_from_csr(data):
|
||||||
|
"""
|
||||||
|
Fetches key_type from CSR.
|
||||||
|
Works with any kind of SubjectAlternativeName
|
||||||
|
:param data: PEM-encoded string with CSR
|
||||||
|
:return: key_type
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
request = x509.load_pem_x509_csr(data.encode("utf-8"), default_backend())
|
||||||
|
except Exception:
|
||||||
|
raise ValidationError("CSR presented is not valid.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
if isinstance(request.public_key(), rsa.RSAPublicKey):
|
||||||
|
return "RSA{key_size}".format(
|
||||||
|
key_size=request.public_key().key_size
|
||||||
|
)
|
||||||
|
elif isinstance(request.public_key(), ec.EllipticCurvePublicKey):
|
||||||
|
return get_key_type_from_ec_curve(request.public_key().curve.name)
|
||||||
|
else:
|
||||||
|
raise Exception("Unsupported key type")
|
||||||
|
|
||||||
|
except NotImplemented:
|
||||||
|
raise NotImplemented()
|
||||||
|
@ -114,6 +114,39 @@ def get_authority_key(body):
|
|||||||
return authority_key.hex()
|
return authority_key.hex()
|
||||||
|
|
||||||
|
|
||||||
|
def get_key_type_from_ec_curve(curve_name):
|
||||||
|
"""
|
||||||
|
Give an EC curve name, return the matching key_type.
|
||||||
|
|
||||||
|
:param: curve_name
|
||||||
|
:return: key_type
|
||||||
|
"""
|
||||||
|
|
||||||
|
_CURVE_TYPES = {
|
||||||
|
ec.SECP192R1().name: "ECCPRIME192V1",
|
||||||
|
ec.SECP256R1().name: "ECCPRIME256V1",
|
||||||
|
ec.SECP224R1().name: "ECCSECP224R1",
|
||||||
|
ec.SECP384R1().name: "ECCSECP384R1",
|
||||||
|
ec.SECP521R1().name: "ECCSECP521R1",
|
||||||
|
ec.SECP256K1().name: "ECCSECP256K1",
|
||||||
|
ec.SECT163K1().name: "ECCSECT163K1",
|
||||||
|
ec.SECT233K1().name: "ECCSECT233K1",
|
||||||
|
ec.SECT283K1().name: "ECCSECT283K1",
|
||||||
|
ec.SECT409K1().name: "ECCSECT409K1",
|
||||||
|
ec.SECT571K1().name: "ECCSECT571K1",
|
||||||
|
ec.SECT163R2().name: "ECCSECT163R2",
|
||||||
|
ec.SECT233R1().name: "ECCSECT233R1",
|
||||||
|
ec.SECT283R1().name: "ECCSECT283R1",
|
||||||
|
ec.SECT409R1().name: "ECCSECT409R1",
|
||||||
|
ec.SECT571R1().name: "ECCSECT571R2",
|
||||||
|
}
|
||||||
|
|
||||||
|
if curve_name in _CURVE_TYPES.keys():
|
||||||
|
return _CURVE_TYPES[curve_name]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def generate_private_key(key_type):
|
def generate_private_key(key_type):
|
||||||
"""
|
"""
|
||||||
Generates a new private key based on key_type.
|
Generates a new private key based on key_type.
|
||||||
@ -128,11 +161,11 @@ def generate_private_key(key_type):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
_CURVE_TYPES = {
|
_CURVE_TYPES = {
|
||||||
"ECCPRIME192V1": ec.SECP192R1(),
|
"ECCPRIME192V1": ec.SECP192R1(), # duplicate
|
||||||
"ECCPRIME256V1": ec.SECP256R1(),
|
"ECCPRIME256V1": ec.SECP256R1(), # duplicate
|
||||||
"ECCSECP192R1": ec.SECP192R1(),
|
"ECCSECP192R1": ec.SECP192R1(), # duplicate
|
||||||
"ECCSECP224R1": ec.SECP224R1(),
|
"ECCSECP224R1": ec.SECP224R1(),
|
||||||
"ECCSECP256R1": ec.SECP256R1(),
|
"ECCSECP256R1": ec.SECP256R1(), # duplicate
|
||||||
"ECCSECP384R1": ec.SECP384R1(),
|
"ECCSECP384R1": ec.SECP384R1(),
|
||||||
"ECCSECP521R1": ec.SECP521R1(),
|
"ECCSECP521R1": ec.SECP521R1(),
|
||||||
"ECCSECP256K1": ec.SECP256K1(),
|
"ECCSECP256K1": ec.SECP256K1(),
|
||||||
|
@ -205,9 +205,15 @@ class AcmeHandler(object):
|
|||||||
OpenSSL.crypto.FILETYPE_PEM, orderr.fullchain_pem
|
OpenSSL.crypto.FILETYPE_PEM, orderr.fullchain_pem
|
||||||
),
|
),
|
||||||
).decode()
|
).decode()
|
||||||
pem_certificate_chain = orderr.fullchain_pem[
|
|
||||||
len(pem_certificate) : # noqa
|
if current_app.config.get("IDENTRUST_CROSS_SIGNED_LE_ICA", False) \
|
||||||
].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 = orderr.fullchain_pem[
|
||||||
|
len(pem_certificate) : # noqa
|
||||||
|
].lstrip()
|
||||||
|
|
||||||
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))
|
||||||
|
@ -156,6 +156,7 @@ class TestAcme(unittest.TestCase):
|
|||||||
mock_acme.fetch_chain = Mock(return_value="mock_chain")
|
mock_acme.fetch_chain = Mock(return_value="mock_chain")
|
||||||
mock_crypto.dump_certificate = Mock(return_value=b"chain")
|
mock_crypto.dump_certificate = Mock(return_value=b"chain")
|
||||||
mock_order = Mock()
|
mock_order = Mock()
|
||||||
|
mock_current_app.config = {}
|
||||||
self.acme.request_certificate(mock_acme, [], mock_order)
|
self.acme.request_certificate(mock_acme, [], mock_order)
|
||||||
|
|
||||||
def test_setup_acme_client_fail(self):
|
def test_setup_acme_client_fail(self):
|
||||||
|
@ -40,7 +40,10 @@ class ADCSIssuerPlugin(IssuerPlugin):
|
|||||||
adcs_user = current_app.config.get("ADCS_USER")
|
adcs_user = current_app.config.get("ADCS_USER")
|
||||||
adcs_pwd = current_app.config.get("ADCS_PWD")
|
adcs_pwd = current_app.config.get("ADCS_PWD")
|
||||||
adcs_auth_method = current_app.config.get("ADCS_AUTH_METHOD")
|
adcs_auth_method = current_app.config.get("ADCS_AUTH_METHOD")
|
||||||
adcs_template = current_app.config.get("ADCS_TEMPLATE")
|
# if there is a config variable ADCS_TEMPLATE_<upper(authority.name)> take the value as Cert template
|
||||||
|
# else default to ADCS_TEMPLATE to be compatible with former versions
|
||||||
|
authority = issuer_options.get("authority").name.upper()
|
||||||
|
adcs_template = current_app.config.get("ADCS_TEMPLATE_{0}".format(authority), current_app.config.get("ADCS_TEMPLATE"))
|
||||||
ca_server = Certsrv(
|
ca_server = Certsrv(
|
||||||
adcs_server, adcs_user, adcs_pwd, auth_method=adcs_auth_method
|
adcs_server, adcs_user, adcs_pwd, auth_method=adcs_auth_method
|
||||||
)
|
)
|
||||||
|
@ -18,8 +18,9 @@ import json
|
|||||||
import arrow
|
import arrow
|
||||||
import pem
|
import pem
|
||||||
import requests
|
import requests
|
||||||
|
import sys
|
||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
from flask import current_app
|
from flask import current_app, g
|
||||||
from lemur.common.utils import validate_conf
|
from lemur.common.utils import validate_conf
|
||||||
from lemur.extensions import metrics
|
from lemur.extensions import metrics
|
||||||
from lemur.plugins import lemur_digicert as digicert
|
from lemur.plugins import lemur_digicert as digicert
|
||||||
@ -61,18 +62,16 @@ def signature_hash(signing_algorithm):
|
|||||||
|
|
||||||
|
|
||||||
def determine_validity_years(years):
|
def determine_validity_years(years):
|
||||||
"""Given an end date determine how many years into the future that date is.
|
|
||||||
:param years:
|
|
||||||
:return: validity in years
|
|
||||||
"""
|
"""
|
||||||
default_years = current_app.config.get("DIGICERT_DEFAULT_VALIDITY", 1)
|
Considering maximum allowed certificate validity period of 397 days, this method should not return
|
||||||
max_years = current_app.config.get("DIGICERT_MAX_VALIDITY", default_years)
|
more than 1 year of validity. Thus changing it to always return 1.
|
||||||
|
Lemur will change this method in future to handle validity in months (determine_validity_months)
|
||||||
|
instead of years. This will allow flexibility to handle short-lived certificates.
|
||||||
|
|
||||||
if years > max_years:
|
:param years:
|
||||||
return max_years
|
:return: 1
|
||||||
if years not in [1, 2, 3]:
|
"""
|
||||||
return default_years
|
return 1
|
||||||
return years
|
|
||||||
|
|
||||||
|
|
||||||
def determine_end_date(end_date):
|
def determine_end_date(end_date):
|
||||||
@ -82,11 +81,11 @@ def determine_end_date(end_date):
|
|||||||
:param end_date:
|
:param end_date:
|
||||||
:return: validity_end
|
:return: validity_end
|
||||||
"""
|
"""
|
||||||
default_years = current_app.config.get("DIGICERT_DEFAULT_VALIDITY", 1)
|
default_days = current_app.config.get("DIGICERT_DEFAULT_VALIDITY_DAYS", 397)
|
||||||
max_validity_end = arrow.utcnow().shift(years=current_app.config.get("DIGICERT_MAX_VALIDITY", default_years))
|
max_validity_end = arrow.utcnow().shift(days=current_app.config.get("DIGICERT_MAX_VALIDITY_DAYS", default_days))
|
||||||
|
|
||||||
if not end_date:
|
if not end_date:
|
||||||
end_date = arrow.utcnow().shift(years=default_years)
|
end_date = arrow.utcnow().shift(days=default_days)
|
||||||
|
|
||||||
if end_date > max_validity_end:
|
if end_date > max_validity_end:
|
||||||
end_date = max_validity_end
|
end_date = max_validity_end
|
||||||
@ -131,6 +130,9 @@ def map_fields(options, csr):
|
|||||||
data["validity_years"] = determine_validity_years(options.get("validity_years"))
|
data["validity_years"] = determine_validity_years(options.get("validity_years"))
|
||||||
elif options.get("validity_end"):
|
elif options.get("validity_end"):
|
||||||
data["custom_expiration_date"] = determine_end_date(options.get("validity_end")).format("YYYY-MM-DD")
|
data["custom_expiration_date"] = determine_end_date(options.get("validity_end")).format("YYYY-MM-DD")
|
||||||
|
# check if validity got truncated. If resultant validity is not equal to requested validity, it just got truncated
|
||||||
|
if data["custom_expiration_date"] != options.get("validity_end").format("YYYY-MM-DD"):
|
||||||
|
log_validity_truncation(options, f"{__name__}.{sys._getframe().f_code.co_name}")
|
||||||
else:
|
else:
|
||||||
data["validity_years"] = determine_validity_years(0)
|
data["validity_years"] = determine_validity_years(0)
|
||||||
|
|
||||||
@ -156,6 +158,9 @@ def map_cis_fields(options, csr):
|
|||||||
validity_end = determine_end_date(arrow.utcnow().shift(years=options["validity_years"]))
|
validity_end = determine_end_date(arrow.utcnow().shift(years=options["validity_years"]))
|
||||||
elif options.get("validity_end"):
|
elif options.get("validity_end"):
|
||||||
validity_end = determine_end_date(options.get("validity_end"))
|
validity_end = determine_end_date(options.get("validity_end"))
|
||||||
|
# check if validity got truncated. If resultant validity is not equal to requested validity, it just got truncated
|
||||||
|
if validity_end != options.get("validity_end"):
|
||||||
|
log_validity_truncation(options, f"{__name__}.{sys._getframe().f_code.co_name}")
|
||||||
else:
|
else:
|
||||||
validity_end = determine_end_date(False)
|
validity_end = determine_end_date(False)
|
||||||
|
|
||||||
@ -181,6 +186,18 @@ def map_cis_fields(options, csr):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def log_validity_truncation(options, function):
|
||||||
|
log_data = {
|
||||||
|
"cn": options["common_name"],
|
||||||
|
"creator": g.user.username
|
||||||
|
}
|
||||||
|
metrics.send("digicert_validity_truncated", "counter", 1, metric_tags=log_data)
|
||||||
|
|
||||||
|
log_data["function"] = function
|
||||||
|
log_data["message"] = "Digicert Plugin truncated the validity of certificate"
|
||||||
|
current_app.logger.info(log_data)
|
||||||
|
|
||||||
|
|
||||||
def handle_response(response):
|
def handle_response(response):
|
||||||
"""
|
"""
|
||||||
Handle the DigiCert API response and any errors it might have experienced.
|
Handle the DigiCert API response and any errors it might have experienced.
|
||||||
|
@ -14,8 +14,6 @@ def config_mock(*args):
|
|||||||
"DIGICERT_ORG_ID": 111111,
|
"DIGICERT_ORG_ID": 111111,
|
||||||
"DIGICERT_PRIVATE": False,
|
"DIGICERT_PRIVATE": False,
|
||||||
"DIGICERT_DEFAULT_SIGNING_ALGORITHM": "sha256",
|
"DIGICERT_DEFAULT_SIGNING_ALGORITHM": "sha256",
|
||||||
"DIGICERT_DEFAULT_VALIDITY": 1,
|
|
||||||
"DIGICERT_MAX_VALIDITY": 2,
|
|
||||||
"DIGICERT_CIS_PROFILE_NAMES": {"digicert": 'digicert'},
|
"DIGICERT_CIS_PROFILE_NAMES": {"digicert": 'digicert'},
|
||||||
"DIGICERT_CIS_SIGNING_ALGORITHMS": {"digicert": 'digicert'},
|
"DIGICERT_CIS_SIGNING_ALGORITHMS": {"digicert": 'digicert'},
|
||||||
}
|
}
|
||||||
@ -24,19 +22,18 @@ def config_mock(*args):
|
|||||||
|
|
||||||
@patch("lemur.plugins.lemur_digicert.plugin.current_app")
|
@patch("lemur.plugins.lemur_digicert.plugin.current_app")
|
||||||
def test_determine_validity_years(mock_current_app):
|
def test_determine_validity_years(mock_current_app):
|
||||||
mock_current_app.config.get = Mock(return_value=2)
|
|
||||||
assert plugin.determine_validity_years(1) == 1
|
assert plugin.determine_validity_years(1) == 1
|
||||||
assert plugin.determine_validity_years(0) == 2
|
assert plugin.determine_validity_years(0) == 1
|
||||||
assert plugin.determine_validity_years(3) == 2
|
assert plugin.determine_validity_years(3) == 1
|
||||||
|
|
||||||
|
|
||||||
@patch("lemur.plugins.lemur_digicert.plugin.current_app")
|
@patch("lemur.plugins.lemur_digicert.plugin.current_app")
|
||||||
def test_determine_end_date(mock_current_app):
|
def test_determine_end_date(mock_current_app):
|
||||||
mock_current_app.config.get = Mock(return_value=2)
|
mock_current_app.config.get = Mock(return_value=397) # 397 days validity
|
||||||
with freeze_time(time_to_freeze=arrow.get(2016, 11, 3).datetime):
|
with freeze_time(time_to_freeze=arrow.get(2016, 11, 3).datetime):
|
||||||
assert arrow.get(2018, 11, 3) == plugin.determine_end_date(0)
|
assert arrow.get(2017, 12, 5) == plugin.determine_end_date(0) # 397 days from (2016, 11, 3)
|
||||||
assert arrow.get(2018, 5, 7) == plugin.determine_end_date(arrow.get(2018, 5, 7))
|
assert arrow.get(2017, 12, 5) == plugin.determine_end_date(arrow.get(2017, 12, 5))
|
||||||
assert arrow.get(2018, 11, 3) == plugin.determine_end_date(arrow.get(2020, 5, 7))
|
assert arrow.get(2017, 12, 5) == plugin.determine_end_date(arrow.get(2020, 5, 7))
|
||||||
|
|
||||||
|
|
||||||
@patch("lemur.plugins.lemur_digicert.plugin.current_app")
|
@patch("lemur.plugins.lemur_digicert.plugin.current_app")
|
||||||
@ -52,7 +49,7 @@ def test_map_fields_with_validity_years(mock_current_app):
|
|||||||
"owner": "bob@example.com",
|
"owner": "bob@example.com",
|
||||||
"description": "test certificate",
|
"description": "test certificate",
|
||||||
"extensions": {"sub_alt_names": {"names": [x509.DNSName(x) for x in names]}},
|
"extensions": {"sub_alt_names": {"names": [x509.DNSName(x) for x in names]}},
|
||||||
"validity_years": 2
|
"validity_years": 1
|
||||||
}
|
}
|
||||||
expected = {
|
expected = {
|
||||||
"certificate": {
|
"certificate": {
|
||||||
@ -62,7 +59,7 @@ def test_map_fields_with_validity_years(mock_current_app):
|
|||||||
"signature_hash": "sha256",
|
"signature_hash": "sha256",
|
||||||
},
|
},
|
||||||
"organization": {"id": 111111},
|
"organization": {"id": 111111},
|
||||||
"validity_years": 2,
|
"validity_years": 1,
|
||||||
}
|
}
|
||||||
assert expected == plugin.map_fields(options, CSR_STR)
|
assert expected == plugin.map_fields(options, CSR_STR)
|
||||||
|
|
||||||
|
5
lemur/plugins/lemur_entrust/__init__.py
Normal file
5
lemur/plugins/lemur_entrust/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
"""Set the version information."""
|
||||||
|
try:
|
||||||
|
VERSION = __import__("pkg_resources").get_distribution(__name__).version
|
||||||
|
except Exception as e:
|
||||||
|
VERSION = "unknown"
|
228
lemur/plugins/lemur_entrust/plugin.py
Normal file
228
lemur/plugins/lemur_entrust/plugin.py
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
from lemur.plugins.bases import IssuerPlugin, SourcePlugin
|
||||||
|
import arrow
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
from lemur.plugins import lemur_entrust as ENTRUST
|
||||||
|
from flask import current_app
|
||||||
|
from lemur.extensions import metrics
|
||||||
|
from lemur.common.utils import validate_conf
|
||||||
|
|
||||||
|
|
||||||
|
def log_status_code(r, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Is a request hook that logs all status codes to the ENTRUST api.
|
||||||
|
|
||||||
|
:param r:
|
||||||
|
:param args:
|
||||||
|
:param kwargs:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
metrics.send("ENTRUST_status_code_{}".format(r.status_code), "counter", 1)
|
||||||
|
|
||||||
|
|
||||||
|
def determine_end_date(end_date):
|
||||||
|
"""
|
||||||
|
Determine appropriate end date
|
||||||
|
:param end_date:
|
||||||
|
:return: validity_end
|
||||||
|
"""
|
||||||
|
# ENTRUST only allows 13 months of max certificate duration
|
||||||
|
max_validity_end = arrow.utcnow().shift(years=1, months=+1).format('YYYY-MM-DD')
|
||||||
|
|
||||||
|
if not end_date:
|
||||||
|
end_date = max_validity_end
|
||||||
|
|
||||||
|
if end_date > max_validity_end:
|
||||||
|
end_date = max_validity_end
|
||||||
|
return end_date
|
||||||
|
|
||||||
|
|
||||||
|
def process_options(options):
|
||||||
|
"""
|
||||||
|
Processes and maps the incoming issuer options to fields/options that
|
||||||
|
Entrust understands
|
||||||
|
|
||||||
|
:param options:
|
||||||
|
:return: dict of valid entrust options
|
||||||
|
"""
|
||||||
|
# if there is a config variable ENTRUST_PRODUCT_<upper(authority.name)>
|
||||||
|
# take the value as Cert product-type
|
||||||
|
# else default to "STANDARD_SSL"
|
||||||
|
authority = options.get("authority").name.upper()
|
||||||
|
product_type = current_app.config.get("ENTRUST_PRODUCT_{0}".format(authority), "STANDARD_SSL")
|
||||||
|
|
||||||
|
if options.get("validity_end"):
|
||||||
|
validity_end = determine_end_date(options.get("validity_end"))
|
||||||
|
else:
|
||||||
|
validity_end = determine_end_date(False)
|
||||||
|
|
||||||
|
tracking_data = {
|
||||||
|
"requesterName": current_app.config.get("ENTRUST_NAME"),
|
||||||
|
"requesterEmail": current_app.config.get("ENTRUST_EMAIL"),
|
||||||
|
"requesterPhone": current_app.config.get("ENTRUST_PHONE")
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"signingAlg": "SHA-2",
|
||||||
|
"eku": "SERVER_AND_CLIENT_AUTH",
|
||||||
|
"certType": product_type,
|
||||||
|
"certExpiryDate": validity_end,
|
||||||
|
"tracking": tracking_data
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def handle_response(my_response):
|
||||||
|
"""
|
||||||
|
Helper function for parsing responses from the Entrust API.
|
||||||
|
:param content:
|
||||||
|
:return: :raise Exception:
|
||||||
|
"""
|
||||||
|
msg = {
|
||||||
|
200: "The request had the validateOnly flag set to true and validation was successful.",
|
||||||
|
201: "Certificate created",
|
||||||
|
202: "Request accepted and queued for approval",
|
||||||
|
400: "Invalid request parameters",
|
||||||
|
404: "Unknown jobId",
|
||||||
|
429: "Too many requests"
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
d = json.loads(my_response.content)
|
||||||
|
except Exception as e:
|
||||||
|
# catch an empty jason object here
|
||||||
|
d = {'errors': 'No detailled message'}
|
||||||
|
s = my_response.status_code
|
||||||
|
if s > 399:
|
||||||
|
raise Exception("ENTRUST error: {0}\n{1}".format(msg.get(s, s), d['errors']))
|
||||||
|
current_app.logger.info("Response: {0}, {1} ".format(s, d))
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
class EntrustIssuerPlugin(IssuerPlugin):
|
||||||
|
title = "ENTRUST"
|
||||||
|
slug = "entrust-issuer"
|
||||||
|
description = "Enables the creation of certificates by ENTRUST"
|
||||||
|
version = ENTRUST.VERSION
|
||||||
|
|
||||||
|
author = "sirferl"
|
||||||
|
author_url = "https://github.com/sirferl/lemur"
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""Initialize the issuer with the appropriate details."""
|
||||||
|
required_vars = [
|
||||||
|
"ENTRUST_API_CERT",
|
||||||
|
"ENTRUST_API_KEY",
|
||||||
|
"ENTRUST_API_USER",
|
||||||
|
"ENTRUST_API_PASS",
|
||||||
|
"ENTRUST_URL",
|
||||||
|
"ENTRUST_ROOT",
|
||||||
|
"ENTRUST_NAME",
|
||||||
|
"ENTRUST_EMAIL",
|
||||||
|
"ENTRUST_PHONE",
|
||||||
|
"ENTRUST_ISSUING",
|
||||||
|
]
|
||||||
|
validate_conf(current_app, required_vars)
|
||||||
|
|
||||||
|
self.session = requests.Session()
|
||||||
|
cert_file = current_app.config.get("ENTRUST_API_CERT")
|
||||||
|
key_file = current_app.config.get("ENTRUST_API_KEY")
|
||||||
|
user = current_app.config.get("ENTRUST_API_USER")
|
||||||
|
password = current_app.config.get("ENTRUST_API_PASS")
|
||||||
|
self.session.cert = (cert_file, key_file)
|
||||||
|
self.session.auth = (user, password)
|
||||||
|
self.session.hooks = dict(response=log_status_code)
|
||||||
|
# self.session.config['keep_alive'] = False
|
||||||
|
super(EntrustIssuerPlugin, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def create_certificate(self, csr, issuer_options):
|
||||||
|
"""
|
||||||
|
Creates an Entrust certificate.
|
||||||
|
|
||||||
|
:param csr:
|
||||||
|
:param issuer_options:
|
||||||
|
:return: :raise Exception:
|
||||||
|
"""
|
||||||
|
current_app.logger.info(
|
||||||
|
"Requesting options: {0}".format(issuer_options)
|
||||||
|
)
|
||||||
|
|
||||||
|
url = current_app.config.get("ENTRUST_URL") + "/certificates"
|
||||||
|
|
||||||
|
data = process_options(issuer_options)
|
||||||
|
data["csr"] = csr
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = self.session.post(url, json=data, timeout=(15, 40))
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
raise Exception("Timeout for POST")
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
raise Exception("Error for POST {0}".format(e))
|
||||||
|
|
||||||
|
response_dict = handle_response(response)
|
||||||
|
external_id = response_dict['trackingId']
|
||||||
|
cert = response_dict['endEntityCert']
|
||||||
|
chain = response_dict['chainCerts'][1]
|
||||||
|
current_app.logger.info(
|
||||||
|
"Received Chain: {0}".format(chain)
|
||||||
|
)
|
||||||
|
|
||||||
|
return cert, chain, external_id
|
||||||
|
|
||||||
|
def revoke_certificate(self, certificate, comments):
|
||||||
|
"""Revoke a Digicert certificate."""
|
||||||
|
base_url = current_app.config.get("ENTRUST_URL")
|
||||||
|
|
||||||
|
# make certificate revoke request
|
||||||
|
revoke_url = "{0}/certificates/{1}/revocations".format(
|
||||||
|
base_url, certificate.external_id
|
||||||
|
)
|
||||||
|
metrics.send("entrust_revoke_certificate", "counter", 1)
|
||||||
|
if comments == '' or not comments:
|
||||||
|
comments = "revoked via API"
|
||||||
|
data = {
|
||||||
|
"crlReason": "superseded",
|
||||||
|
"revocationComment": comments
|
||||||
|
}
|
||||||
|
response = self.session.post(revoke_url, json=data)
|
||||||
|
|
||||||
|
data = handle_response(response)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_authority(options):
|
||||||
|
"""Create an authority.
|
||||||
|
Creates an authority, this authority is then used by Lemur to
|
||||||
|
allow a user to specify which Certificate Authority they want
|
||||||
|
to sign their certificate.
|
||||||
|
|
||||||
|
:param options:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
entrust_root = current_app.config.get("ENTRUST_ROOT")
|
||||||
|
entrust_issuing = current_app.config.get("ENTRUST_ISSUING")
|
||||||
|
role = {"username": "", "password": "", "name": "entrust"}
|
||||||
|
current_app.logger.info("Creating Auth: {0} {1}".format(options, entrust_issuing))
|
||||||
|
return entrust_root, "", [role]
|
||||||
|
|
||||||
|
def get_ordered_certificate(self, order_id):
|
||||||
|
raise NotImplementedError("Not implemented\n", self, order_id)
|
||||||
|
|
||||||
|
def canceled_ordered_certificate(self, pending_cert, **kwargs):
|
||||||
|
raise NotImplementedError("Not implemented\n", self, pending_cert, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class EntrustSourcePlugin(SourcePlugin):
|
||||||
|
title = "ENTRUST"
|
||||||
|
slug = "entrust-source"
|
||||||
|
description = "Enables the collecion of certificates"
|
||||||
|
version = ENTRUST.VERSION
|
||||||
|
|
||||||
|
author = "sirferl"
|
||||||
|
author_url = "https://github.com/sirferl/lemur"
|
||||||
|
|
||||||
|
def get_certificates(self, options, **kwargs):
|
||||||
|
# Not needed for ENTRUST
|
||||||
|
raise NotImplementedError("Not implemented\n", self, options, **kwargs)
|
||||||
|
|
||||||
|
def get_endpoints(self, options, **kwargs):
|
||||||
|
# There are no endpoints in ENTRUST
|
||||||
|
raise NotImplementedError("Not implemented\n", self, options, **kwargs)
|
@ -46,8 +46,7 @@
|
|||||||
Organizational Unit
|
Organizational Unit
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input name="organizationalUnit" ng-model="authority.organizationalUnit" placeholder="Organizational Unit" class="form-control" required/>
|
<input name="organizationalUnit" ng-model="authority.organizationalUnit" placeholder="Organizational Unit" class="form-control"/>
|
||||||
<p ng-show="dnForm.organization.$invalid && !dnForm.organizationalUnit.$pristine" class="help-block">You must enter a organizational unit</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
Signing Algorithm
|
Signing Algorithm
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<select class="form-control" ng-model="authority.signingAlgorithm" ng-options="option for option in ['sha1WithRSA', 'sha256WithRSA']" ng-init="authority.signingAlgorithm = 'sha256WithRSA'"></select>
|
<select class="form-control" ng-model="authority.signingAlgorithm" ng-options="option for option in ['sha1WithRSA', 'sha256WithRSA', 'sha256WithECDSA', 'SHA384withECDSA', 'SHA512withECDSA']" ng-init="authority.signingAlgorithm = 'sha256WithRSA'"></select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -107,7 +107,6 @@ angular.module('lemur')
|
|||||||
startingDay: 1
|
startingDay: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
$scope.open1 = function() {
|
$scope.open1 = function() {
|
||||||
$scope.popup1.opened = true;
|
$scope.popup1.opened = true;
|
||||||
};
|
};
|
||||||
@ -140,6 +139,14 @@ angular.module('lemur')
|
|||||||
);
|
);
|
||||||
|
|
||||||
$scope.create = function (certificate) {
|
$scope.create = function (certificate) {
|
||||||
|
if(certificate.validityType === 'customDates' &&
|
||||||
|
(!certificate.validityStart || !certificate.validityEnd)) { // these are not mandatory fields in schema, thus handling validation in js
|
||||||
|
return showMissingDateError();
|
||||||
|
}
|
||||||
|
if(certificate.validityType === 'defaultDays') {
|
||||||
|
populateValidityDateAsPerDefault(certificate);
|
||||||
|
}
|
||||||
|
|
||||||
WizardHandler.wizard().context.loading = true;
|
WizardHandler.wizard().context.loading = true;
|
||||||
CertificateService.create(certificate).then(
|
CertificateService.create(certificate).then(
|
||||||
function () {
|
function () {
|
||||||
@ -164,6 +171,30 @@ angular.module('lemur')
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function showMissingDateError() {
|
||||||
|
let error = {};
|
||||||
|
error.message = '';
|
||||||
|
error.reasons = {};
|
||||||
|
error.reasons.validityRange = 'Valid start and end dates are needed, else select Default option';
|
||||||
|
|
||||||
|
toaster.pop({
|
||||||
|
type: 'error',
|
||||||
|
title: 'Validation Error',
|
||||||
|
body: 'lemur-bad-request',
|
||||||
|
bodyOutputType: 'directive',
|
||||||
|
directiveData: error,
|
||||||
|
timeout: 100000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateValidityDateAsPerDefault(certificate) {
|
||||||
|
// calculate start and end date as per default validity
|
||||||
|
let startDate = new Date(), endDate = new Date();
|
||||||
|
endDate.setDate(startDate.getDate() + certificate.authority.authorityCertificate.defaultValidityDays);
|
||||||
|
certificate.validityStart = startDate;
|
||||||
|
certificate.validityEnd = endDate;
|
||||||
|
}
|
||||||
|
|
||||||
$scope.templates = [
|
$scope.templates = [
|
||||||
{
|
{
|
||||||
'name': 'Client Certificate',
|
'name': 'Client Certificate',
|
||||||
@ -212,12 +243,18 @@ angular.module('lemur')
|
|||||||
})
|
})
|
||||||
|
|
||||||
.controller('CertificateCloneController', function ($scope, $uibModalInstance, CertificateApi, CertificateService, DestinationService, AuthorityService, AuthorityApi, PluginService, MomentService, WizardHandler, LemurRestangular, NotificationService, toaster, editId) {
|
.controller('CertificateCloneController', function ($scope, $uibModalInstance, CertificateApi, CertificateService, DestinationService, AuthorityService, AuthorityApi, PluginService, MomentService, WizardHandler, LemurRestangular, NotificationService, toaster, editId) {
|
||||||
|
$scope.certificate = LemurRestangular.restangularizeElement(null, {}, 'certificates');
|
||||||
CertificateApi.get(editId).then(function (certificate) {
|
CertificateApi.get(editId).then(function (certificate) {
|
||||||
$scope.certificate = certificate;
|
$scope.certificate = certificate;
|
||||||
|
// prepare the certificate for cloning
|
||||||
$scope.certificate.name = ''; // we should prefer the generated name
|
$scope.certificate.name = ''; // we should prefer the generated name
|
||||||
$scope.certificate.csr = null; // should not clone CSR in case other settings are changed in clone
|
$scope.certificate.csr = null; // should not clone CSR in case other settings are changed in clone
|
||||||
$scope.certificate.validityStart = null;
|
$scope.certificate.validityStart = null;
|
||||||
$scope.certificate.validityEnd = null;
|
$scope.certificate.validityEnd = null;
|
||||||
|
$scope.certificate.keyType = 'RSA2048'; // default algo to show during clone
|
||||||
|
$scope.certificate.description = 'Cloning from cert ID ' + editId;
|
||||||
|
$scope.certificate.replacedBy = []; // should not clone 'replaced by' info
|
||||||
|
$scope.certificate.removeReplaces(); // should not clone 'replacement cert' info
|
||||||
CertificateService.getDefaults($scope.certificate);
|
CertificateService.getDefaults($scope.certificate);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -271,6 +308,14 @@ angular.module('lemur')
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.create = function (certificate) {
|
$scope.create = function (certificate) {
|
||||||
|
if(certificate.validityType === 'customDates' &&
|
||||||
|
(!certificate.validityStart || !certificate.validityEnd)) { // these are not mandatory fields in schema, thus handling validation in js
|
||||||
|
return showMissingDateError();
|
||||||
|
}
|
||||||
|
if(certificate.validityType === 'defaultDays') {
|
||||||
|
populateValidityDateAsPerDefault(certificate);
|
||||||
|
}
|
||||||
|
|
||||||
WizardHandler.wizard().context.loading = true;
|
WizardHandler.wizard().context.loading = true;
|
||||||
CertificateService.create(certificate).then(
|
CertificateService.create(certificate).then(
|
||||||
function () {
|
function () {
|
||||||
@ -295,6 +340,30 @@ angular.module('lemur')
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function showMissingDateError() {
|
||||||
|
let error = {};
|
||||||
|
error.message = '';
|
||||||
|
error.reasons = {};
|
||||||
|
error.reasons.validityRange = 'Valid start and end dates are needed, else select Default option';
|
||||||
|
|
||||||
|
toaster.pop({
|
||||||
|
type: 'error',
|
||||||
|
title: 'Validation Error',
|
||||||
|
body: 'lemur-bad-request',
|
||||||
|
bodyOutputType: 'directive',
|
||||||
|
directiveData: error,
|
||||||
|
timeout: 100000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateValidityDateAsPerDefault(certificate) {
|
||||||
|
// calculate start and end date as per default validity
|
||||||
|
let startDate = new Date(), endDate = new Date();
|
||||||
|
endDate.setDate(startDate.getDate() + certificate.authority.authorityCertificate.defaultValidityDays);
|
||||||
|
certificate.validityStart = startDate;
|
||||||
|
certificate.validityEnd = endDate;
|
||||||
|
}
|
||||||
|
|
||||||
$scope.templates = [
|
$scope.templates = [
|
||||||
{
|
{
|
||||||
'name': 'Client Certificate',
|
'name': 'Client Certificate',
|
||||||
|
@ -62,9 +62,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input name="organizationalUnit" ng-model="certificate.organizationalUnit" placeholder="Organizational Unit"
|
<input name="organizationalUnit" ng-model="certificate.organizationalUnit" placeholder="Organizational Unit"
|
||||||
class="form-control" required/>
|
class="form-control"/>
|
||||||
<p ng-show="dnForm.organization.$invalid && !dnForm.organizationalUnit.$pristine" class="help-block">You must
|
|
||||||
enter a organizational unit</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
name="certificate signing request"
|
name="certificate signing request"
|
||||||
ng-model="certificate.csr"
|
ng-model="certificate.csr"
|
||||||
placeholder="PEM encoded string..." class="form-control"
|
placeholder="PEM encoded string..." class="form-control"
|
||||||
ng-pattern="/^-----BEGIN CERTIFICATE REQUEST-----/"></textarea>
|
ng-pattern="/(^-----BEGIN CERTIFICATE REQUEST-----[\S\s]*-----END CERTIFICATE REQUEST-----)|(^-----BEGIN NEW CERTIFICATE REQUEST-----[\S\s]*-----END NEW CERTIFICATE REQUEST-----)/"></textarea>
|
||||||
|
|
||||||
<p ng-show="trackingForm.csr.$invalid && !trackingForm.csr.$pristine"
|
<p ng-show="trackingForm.csr.$invalid && !trackingForm.csr.$pristine"
|
||||||
class="help-block">Enter a valid certificate signing request.</p>
|
class="help-block">Enter a valid certificate signing request.</p>
|
||||||
|
@ -96,7 +96,7 @@
|
|||||||
Certificate Authority
|
Certificate Authority
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<ui-select class="input-md" ng-model="certificate.authority" theme="bootstrap" title="choose an authority">
|
<ui-select class="input-md" ng-model="certificate.authority" theme="bootstrap" title="choose an authority" ng-change="clearDates()">
|
||||||
<ui-select-match placeholder="select an authority...">{{$select.selected.name}}</ui-select-match>
|
<ui-select-match placeholder="select an authority...">{{$select.selected.name}}</ui-select-match>
|
||||||
<ui-select-choices class="form-control" repeat="authority in authorities"
|
<ui-select-choices class="form-control" repeat="authority in authorities"
|
||||||
refresh="getAuthoritiesByName($select.search)"
|
refresh="getAuthoritiesByName($select.search)"
|
||||||
@ -133,24 +133,23 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-hide="certificate.authority.plugin.slug == 'acme-issuer'">
|
<div class="form-group" ng-hide="certificate.authority.plugin.slug == 'acme-issuer'">
|
||||||
<label class="control-label col-sm-2"
|
<label class="control-label col-sm-2"
|
||||||
uib-tooltip="If no date is selected Lemur attempts to issue a 1 year certificate">
|
uib-tooltip="You can select custom date range; however, we recommend continuing with default validity.">
|
||||||
Validity Range <span class="glyphicon glyphicon-question-sign"></span>
|
Validity Range <span class="glyphicon glyphicon-question-sign"></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-2">
|
<div class="col-sm-4">
|
||||||
<select ng-model="certificate.validityYears" class="form-control">
|
<div class="btn-group btn-group-toggle" data-toggle="buttons">
|
||||||
<option value="">-</option>
|
<label class="btn btn-info" ng-model="certificate.validityType" uib-btn-radio="'defaultDays'" ng-click="clearDates()">
|
||||||
<option value="1">1 year</option>
|
Default ({{certificate.authority.authorityCertificate.defaultValidityDays}} days)</label>
|
||||||
</select>
|
<label class="btn btn-info" ng-model="certificate.validityType" uib-btn-radio="'customDates'" ng-change="clearDates()">Custom</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span style="padding-top: 15px" class="text-center col-sm-1">
|
<div class="col-sm-3" ng-if="certificate.validityType==='customDates'">
|
||||||
<strong>or</strong>
|
|
||||||
</span>
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" class="form-control"
|
<input type="text" class="form-control"
|
||||||
uib-tooltip="yyyy/MM/dd"
|
uib-tooltip="Start Date (yyyy/MM/dd)"
|
||||||
uib-datepicker-popup="yyyy/MM/dd"
|
uib-datepicker-popup="yyyy/MM/dd"
|
||||||
ng-model="certificate.validityStart"
|
ng-model="certificate.validityStart"
|
||||||
|
ng-change="certificate.setValidityEndDateRange(certificate.validityStart)"
|
||||||
is-open="popup1.opened"
|
is-open="popup1.opened"
|
||||||
datepicker-options="dateOptions"
|
datepicker-options="dateOptions"
|
||||||
close-text="Close"
|
close-text="Close"
|
||||||
@ -158,6 +157,7 @@
|
|||||||
min-date="certificate.authority.authorityCertificate.notBefore"
|
min-date="certificate.authority.authorityCertificate.notBefore"
|
||||||
alt-input-formats="altInputFormats"
|
alt-input-formats="altInputFormats"
|
||||||
placeholder="Start Date"
|
placeholder="Start Date"
|
||||||
|
readonly="true"
|
||||||
/>
|
/>
|
||||||
<span class="input-group-btn">
|
<span class="input-group-btn">
|
||||||
<button type="button" class="btn btn-default" ng-click="open1()"><i
|
<button type="button" class="btn btn-default" ng-click="open1()"><i
|
||||||
@ -165,19 +165,20 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-3">
|
<div class="col-sm-3" ng-if="certificate.validityType==='customDates'">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" class="form-control"
|
<input type="text" class="form-control"
|
||||||
uib-tooltip="yyyy/MM/dd"
|
uib-tooltip="End Date (yyyy/MM/dd)"
|
||||||
uib-datepicker-popup="yyyy/MM/dd"
|
uib-datepicker-popup="yyyy/MM/dd"
|
||||||
ng-model="certificate.validityEnd"
|
ng-model="certificate.validityEnd"
|
||||||
is-open="popup2.opened"
|
is-open="popup2.opened"
|
||||||
datepicker-options="dateOptions"
|
datepicker-options="dateOptions"
|
||||||
close-text="Close"
|
close-text="Close"
|
||||||
max-date="certificate.authority.authorityCertificate.notAfter"
|
max-date="certificate.authority.authorityCertificate.maxValidityEnd"
|
||||||
min-date="certificate.authority.authorityCertificate.notBefore"
|
min-date="certificate.authority.authorityCertificate.minValidityEnd"
|
||||||
alt-input-formats="altInputFormats"
|
alt-input-formats="altInputFormats"
|
||||||
placeholder="End Date"
|
placeholder="End Date"
|
||||||
|
readonly="true"
|
||||||
/>
|
/>
|
||||||
<span class="input-group-btn">
|
<span class="input-group-btn">
|
||||||
<button type="button" class="btn btn-default" ng-click="open2()"><i
|
<button type="button" class="btn btn-default" ng-click="open2()"><i
|
||||||
@ -185,10 +186,6 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-1">
|
|
||||||
<button uib-tooltip="Clear Validity" ng-click="clearDates()" class="btn btn-default"><i
|
|
||||||
class="glyphicon glyphicon-remove"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="certificate.authority.plugin.slug == 'acme-issuer'">
|
<div class="form-group" ng-show="certificate.authority.plugin.slug == 'acme-issuer'">
|
||||||
<label class="control-label col-sm-2">
|
<label class="control-label col-sm-2">
|
||||||
|
@ -164,6 +164,22 @@ angular.module('lemur')
|
|||||||
this.extensions.keyUsage.useDecipherOnly = true;
|
this.extensions.keyUsage.useDecipherOnly = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
setValidityEndDateRange: function (value) {
|
||||||
|
// clear selected validity end date as we are about to calculate new range
|
||||||
|
this.validityEnd = '';
|
||||||
|
|
||||||
|
// Minimum end date will be same as selected start date
|
||||||
|
this.authority.authorityCertificate.minValidityEnd = value;
|
||||||
|
|
||||||
|
if(!this.authority.authorityCertificate || !this.authority.authorityCertificate.maxIssuanceDays) {
|
||||||
|
this.authority.authorityCertificate.maxValidityEnd = this.authority.authorityCertificate.notAfter;
|
||||||
|
} else {
|
||||||
|
// Move max end date by maxIssuanceDays
|
||||||
|
let endDate = new Date(value);
|
||||||
|
endDate.setDate(endDate.getDate() + this.authority.authorityCertificate.maxIssuanceDays);
|
||||||
|
this.authority.authorityCertificate.maxValidityEnd = endDate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -181,7 +197,7 @@ angular.module('lemur')
|
|||||||
CertificateService.create = function (certificate) {
|
CertificateService.create = function (certificate) {
|
||||||
certificate.attachSubAltName();
|
certificate.attachSubAltName();
|
||||||
certificate.attachCustom();
|
certificate.attachCustom();
|
||||||
if (certificate.validityYears === '') { // if a user de-selects validity years we ignore it
|
if (certificate.validityYears === '') { // if a user de-selects validity years we ignore it - might not be needed anymore
|
||||||
delete certificate.validityYears;
|
delete certificate.validityYears;
|
||||||
}
|
}
|
||||||
return CertificateApi.post(certificate);
|
return CertificateApi.post(certificate);
|
||||||
@ -264,6 +280,12 @@ angular.module('lemur')
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
certificate.authority.authorityCertificate.minValidityEnd = defaults.authority.authorityCertificate.notBefore;
|
||||||
|
certificate.authority.authorityCertificate.maxValidityEnd = defaults.authority.authorityCertificate.notAfter;
|
||||||
|
|
||||||
|
// pre-select validity type radio button to default days
|
||||||
|
certificate.validityType = 'defaultDays';
|
||||||
|
|
||||||
if (certificate.dnsProviderId) {
|
if (certificate.dnsProviderId) {
|
||||||
certificate.dnsProvider = {id: certificate.dnsProviderId};
|
certificate.dnsProvider = {id: certificate.dnsProviderId};
|
||||||
}
|
}
|
||||||
@ -292,3 +314,4 @@ angular.module('lemur')
|
|||||||
|
|
||||||
return CertificateService;
|
return CertificateService;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -144,6 +144,22 @@ angular.module('lemur')
|
|||||||
this.extensions.keyUsage.useDecipherOnly = true;
|
this.extensions.keyUsage.useDecipherOnly = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
setValidityEndDateRange: function (value) {
|
||||||
|
// clear selected validity end date as we are about to calculate new range
|
||||||
|
this.validityEnd = '';
|
||||||
|
|
||||||
|
// Minimum end date will be same as selected start date
|
||||||
|
this.authority.authorityCertificate.minValidityEnd = value;
|
||||||
|
|
||||||
|
if(!this.authority.authorityCertificate || !this.authority.authorityCertificate.maxIssuanceDays) {
|
||||||
|
this.authority.authorityCertificate.maxValidityEnd = this.authority.authorityCertificate.notAfter;
|
||||||
|
} else {
|
||||||
|
// Move max end date by maxIssuanceDays
|
||||||
|
let endDate = new Date(value);
|
||||||
|
endDate.setDate(endDate.getDate() + this.authority.authorityCertificate.maxIssuanceDays);
|
||||||
|
this.authority.authorityCertificate.maxValidityEnd = endDate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -230,6 +246,9 @@ angular.module('lemur')
|
|||||||
certificate.authority = defaults.authority;
|
certificate.authority = defaults.authority;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
certificate.authority.authorityCertificate.minValidityEnd = defaults.authority.authorityCertificate.notBefore;
|
||||||
|
certificate.authority.authorityCertificate.maxValidityEnd = defaults.authority.authorityCertificate.notAfter;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -34,6 +34,29 @@ def test_authority_input_schema(client, role, issuer_plugin, logged_in_user):
|
|||||||
assert not errors
|
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):
|
def test_user_authority(session, client, authority, role, user, issuer_plugin):
|
||||||
u = user["user"]
|
u = user["user"]
|
||||||
u.roles.append(role)
|
u.roles.append(role)
|
||||||
|
@ -11,6 +11,12 @@ from lemur.tests.vectors import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_key_type_from_ec_curve():
|
||||||
|
from lemur.common.utils import get_key_type_from_ec_curve
|
||||||
|
|
||||||
|
assert get_key_type_from_ec_curve("secp256r1") == "ECCPRIME256V1"
|
||||||
|
|
||||||
|
|
||||||
def test_generate_private_key():
|
def test_generate_private_key():
|
||||||
from lemur.common.utils import generate_private_key
|
from lemur.common.utils import generate_private_key
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ cffi==1.14.0 # via cryptography
|
|||||||
cfgv==3.1.0 # via pre-commit
|
cfgv==3.1.0 # via pre-commit
|
||||||
chardet==3.0.4 # via requests
|
chardet==3.0.4 # via requests
|
||||||
colorama==0.4.3 # via twine
|
colorama==0.4.3 # via twine
|
||||||
cryptography==2.9.2 # via secretstorage
|
cryptography==3.1 # via secretstorage
|
||||||
distlib==0.3.0 # via virtualenv
|
distlib==0.3.0 # via virtualenv
|
||||||
docutils==0.16 # via readme-renderer
|
docutils==0.16 # via readme-renderer
|
||||||
filelock==3.0.12 # via virtualenv
|
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
|
jeepney==0.4.3 # via keyring, secretstorage
|
||||||
keyring==21.2.0 # via twine
|
keyring==21.2.0 # via twine
|
||||||
mccabe==0.6.1 # via flake8
|
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
|
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
|
pycodestyle==2.3.1 # via flake8
|
||||||
pycparser==2.20 # via cffi
|
pycparser==2.20 # via cffi
|
||||||
pyflakes==1.6.0 # via flake8
|
pyflakes==1.6.0 # via flake8
|
||||||
|
@ -4,35 +4,35 @@
|
|||||||
#
|
#
|
||||||
# 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.6.0 # via -r requirements.txt
|
acme==1.8.0 # via -r requirements.txt
|
||||||
alabaster==0.7.12 # via sphinx
|
alabaster==0.7.12 # via sphinx
|
||||||
alembic-autogenerate-enums==0.0.2 # via -r requirements.txt
|
alembic-autogenerate-enums==0.0.2 # via -r requirements.txt
|
||||||
alembic==1.4.2 # via -r requirements.txt, flask-migrate
|
alembic==1.4.2 # via -r requirements.txt, flask-migrate
|
||||||
amqp==2.5.2 # via -r requirements.txt, kombu
|
amqp==2.5.2 # via -r requirements.txt, kombu
|
||||||
aniso8601==8.0.0 # via -r requirements.txt, flask-restful
|
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
|
asyncpool==1.0 # via -r requirements.txt
|
||||||
babel==2.8.0 # via sphinx
|
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
|
||||||
beautifulsoup4==4.9.1 # via -r requirements.txt, cloudflare
|
beautifulsoup4==4.9.1 # via -r requirements.txt, cloudflare
|
||||||
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.14.28 # via -r requirements.txt
|
boto3==1.14.61 # via -r requirements.txt
|
||||||
botocore==1.17.28 # via -r requirements.txt, boto3, s3transfer
|
botocore==1.17.61 # 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.6.20 # via -r requirements.txt, requests
|
certifi==2020.6.20 # via -r requirements.txt, requests
|
||||||
certsrv==2.1.1 # via -r requirements.txt
|
certsrv==2.1.1 # via -r requirements.txt
|
||||||
cffi==1.14.0 # via -r requirements.txt, bcrypt, cryptography, pynacl
|
cffi==1.14.0 # via -r requirements.txt, bcrypt, cryptography, pynacl
|
||||||
chardet==3.0.4 # via -r requirements.txt, requests
|
chardet==3.0.4 # via -r requirements.txt, requests
|
||||||
click==7.1.1 # via -r requirements.txt, flask
|
click==7.1.1 # via -r requirements.txt, flask
|
||||||
cloudflare==2.8.8 # via -r requirements.txt
|
cloudflare==2.8.13 # via -r requirements.txt
|
||||||
cryptography==2.9.2 # via -r requirements.txt, acme, josepy, paramiko, pyopenssl, requests
|
cryptography==3.1 # via -r requirements.txt, acme, josepy, paramiko, pyopenssl, requests
|
||||||
dnspython3==1.15.0 # via -r requirements.txt
|
dnspython3==1.15.0 # via -r requirements.txt
|
||||||
dnspython==1.15.0 # via -r requirements.txt, dnspython3
|
dnspython==1.15.0 # via -r requirements.txt, dnspython3
|
||||||
docutils==0.15.2 # via -r requirements.txt, botocore, sphinx
|
docutils==0.15.2 # via -r requirements.txt, botocore, sphinx
|
||||||
dyn==1.8.1 # via -r requirements.txt
|
dyn==1.8.1 # via -r requirements.txt
|
||||||
flask-bcrypt==0.7.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-mail==0.9.1 # via -r requirements.txt
|
||||||
flask-migrate==2.5.3 # via -r requirements.txt
|
flask-migrate==2.5.3 # via -r requirements.txt
|
||||||
flask-principal==0.4.0 # 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
|
hvac==0.10.5 # 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.5.0 # via -r requirements.txt
|
inflection==0.5.1 # via -r requirements.txt
|
||||||
itsdangerous==1.1.0 # via -r requirements.txt, flask
|
itsdangerous==1.1.0 # via -r requirements.txt, flask
|
||||||
javaobj-py3==0.4.0.1 # via -r requirements.txt, pyjks
|
javaobj-py3==0.4.0.1 # via -r requirements.txt, pyjks
|
||||||
jinja2==2.11.2 # via -r requirements.txt, flask, sphinx
|
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
|
marshmallow==2.20.4 # via -r requirements.txt, marshmallow-sqlalchemy
|
||||||
ndg-httpsclient==0.5.1 # via -r requirements.txt
|
ndg-httpsclient==0.5.1 # via -r requirements.txt
|
||||||
packaging==20.3 # via sphinx
|
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
|
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-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
|
pyasn1==0.4.8 # via -r requirements.txt, ndg-httpsclient, pyasn1-modules, pyjks, python-ldap
|
||||||
pycparser==2.20 # via -r requirements.txt, cffi
|
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
|
snowballstemmer==2.0.0 # via sphinx
|
||||||
soupsieve==2.0.1 # via -r requirements.txt, beautifulsoup4
|
soupsieve==2.0.1 # via -r requirements.txt, beautifulsoup4
|
||||||
sphinx-rtd-theme==0.5.0 # via -r requirements-docs.in
|
sphinx-rtd-theme==0.5.0 # via -r requirements-docs.in
|
||||||
sphinx==3.1.2 # 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-applehelp==1.0.2 # via sphinx
|
||||||
sphinxcontrib-devhelp==1.0.2 # via sphinx
|
sphinxcontrib-devhelp==1.0.2 # via sphinx
|
||||||
sphinxcontrib-htmlhelp==1.0.3 # via sphinx
|
sphinxcontrib-htmlhelp==1.0.3 # via sphinx
|
||||||
|
@ -5,35 +5,36 @@
|
|||||||
# pip-compile --no-index --output-file=requirements-tests.txt requirements-tests.in
|
# pip-compile --no-index --output-file=requirements-tests.txt requirements-tests.in
|
||||||
#
|
#
|
||||||
appdirs==1.4.3 # via black
|
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-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==20.8b1 # via -r requirements-tests.in
|
||||||
boto3==1.14.28 # via aws-sam-translator, moto
|
boto3==1.14.61 # via aws-sam-translator, moto
|
||||||
boto==2.49.0 # via moto
|
boto==2.49.0 # via moto
|
||||||
botocore==1.17.28 # via aws-xray-sdk, boto3, moto, s3transfer
|
botocore==1.17.61 # via aws-xray-sdk, boto3, moto, s3transfer
|
||||||
certifi==2020.6.20 # via requests
|
certifi==2020.6.20 # 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
|
||||||
chardet==3.0.4 # via requests
|
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
|
coverage==5.3 # via -r requirements-tests.in
|
||||||
cryptography==2.9.2 # via moto, sshpubkeys
|
cryptography==3.1 # via moto, python-jose, sshpubkeys
|
||||||
decorator==4.4.2 # via networkx
|
decorator==4.4.2 # via networkx
|
||||||
docker==4.2.0 # via moto
|
docker==4.2.0 # via moto
|
||||||
docutils==0.15.2 # via botocore
|
docutils==0.15.2 # via botocore
|
||||||
ecdsa==0.15 # via python-jose, sshpubkeys
|
ecdsa==0.14.1 # via moto, python-jose, sshpubkeys
|
||||||
factory-boy==2.12.0 # via -r requirements-tests.in
|
factory-boy==3.0.1 # via -r requirements-tests.in
|
||||||
faker==4.1.1 # via -r requirements-tests.in, factory-boy
|
faker==4.1.3 # via -r requirements-tests.in, factory-boy
|
||||||
fakeredis==1.4.1 # via -r requirements-tests.in
|
fakeredis==1.4.3 # via -r requirements-tests.in
|
||||||
flask==1.1.2 # via pytest-flask
|
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
|
future==0.18.2 # via aws-xray-sdk
|
||||||
gitdb==4.0.4 # via gitpython
|
gitdb==4.0.4 # via gitpython
|
||||||
gitpython==3.1.1 # via bandit
|
gitpython==3.1.1 # via bandit
|
||||||
idna==2.8 # via moto, requests
|
idna==2.8 # via moto, requests
|
||||||
importlib-metadata==1.6.0 # via jsonpickle
|
importlib-metadata==1.6.0 # via jsonpickle
|
||||||
|
iniconfig==1.0.1 # via pytest
|
||||||
itsdangerous==1.1.0 # via flask
|
itsdangerous==1.1.0 # via flask
|
||||||
jinja2==2.11.2 # via flask, moto
|
jinja2==2.11.2 # via flask, moto
|
||||||
jmespath==0.9.5 # via boto3, botocore
|
jmespath==0.9.5 # via boto3, botocore
|
||||||
@ -42,27 +43,28 @@ jsonpatch==1.25 # via cfn-lint
|
|||||||
jsonpickle==1.4 # via aws-xray-sdk
|
jsonpickle==1.4 # via aws-xray-sdk
|
||||||
jsonpointer==2.0 # via jsonpatch
|
jsonpointer==2.0 # via jsonpatch
|
||||||
jsonschema==3.2.0 # via aws-sam-translator, cfn-lint
|
jsonschema==3.2.0 # via aws-sam-translator, cfn-lint
|
||||||
markupsafe==1.1.1 # via jinja2
|
markupsafe==1.1.1 # via jinja2, moto
|
||||||
mock==4.0.2 # via moto
|
mock==4.0.2 # via moto
|
||||||
more-itertools==8.2.0 # via pytest
|
more-itertools==8.2.0 # via moto, pytest
|
||||||
moto==1.3.14 # via -r requirements-tests.in
|
moto==1.3.16 # via -r requirements-tests.in
|
||||||
|
mypy-extensions==0.4.3 # via black
|
||||||
networkx==2.4 # via cfn-lint
|
networkx==2.4 # via cfn-lint
|
||||||
nose==1.3.7 # via -r requirements-tests.in
|
nose==1.3.7 # via -r requirements-tests.in
|
||||||
packaging==20.3 # via pytest
|
packaging==20.3 # via pytest
|
||||||
pathspec==0.8.0 # via black
|
pathspec==0.8.0 # via black
|
||||||
pbr==5.4.5 # via stevedore
|
pbr==5.4.5 # via stevedore
|
||||||
pluggy==0.13.1 # via pytest
|
pluggy==0.13.1 # via pytest
|
||||||
py==1.8.1 # via pytest
|
py==1.9.0 # via pytest
|
||||||
pyasn1==0.4.8 # via python-jose, rsa
|
pyasn1==0.4.8 # via python-jose, rsa
|
||||||
pycparser==2.20 # via cffi
|
pycparser==2.20 # via cffi
|
||||||
pyflakes==2.2.0 # via -r requirements-tests.in
|
pyflakes==2.2.0 # via -r requirements-tests.in
|
||||||
pyparsing==2.4.7 # via packaging
|
pyparsing==2.4.7 # via packaging
|
||||||
pyrsistent==0.16.0 # via jsonschema
|
pyrsistent==0.16.0 # via jsonschema
|
||||||
pytest-flask==1.0.0 # via -r requirements-tests.in
|
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==5.4.3 # via -r requirements-tests.in, pytest-flask, pytest-mock
|
pytest==6.0.2 # via -r requirements-tests.in, pytest-flask, pytest-mock
|
||||||
python-dateutil==2.8.1 # via botocore, faker, freezegun, moto
|
python-dateutil==2.8.1 # via botocore, faker, freezegun, moto
|
||||||
python-jose==3.1.0 # via moto
|
python-jose[cryptography]==3.1.0 # via moto
|
||||||
pytz==2019.3 # via moto
|
pytz==2019.3 # via moto
|
||||||
pyyaml==5.3.1 # via -r requirements-tests.in, bandit, cfn-lint, moto
|
pyyaml==5.3.1 # via -r requirements-tests.in, bandit, cfn-lint, moto
|
||||||
redis==3.5.3 # via fakeredis
|
redis==3.5.3 # via fakeredis
|
||||||
@ -72,21 +74,21 @@ requests==2.24.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.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
|
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
|
||||||
stevedore==1.32.0 # via bandit
|
stevedore==1.32.0 # via bandit
|
||||||
text-unidecode==1.3 # via faker
|
text-unidecode==1.3 # via faker
|
||||||
toml==0.10.0 # via black
|
toml==0.10.1 # via black, pytest
|
||||||
typed-ast==1.4.1 # via black
|
typed-ast==1.4.1 # via black
|
||||||
|
typing-extensions==3.7.4.3 # via black
|
||||||
urllib3==1.25.8 # via botocore, requests
|
urllib3==1.25.8 # via botocore, requests
|
||||||
wcwidth==0.1.9 # via pytest
|
|
||||||
websocket-client==0.57.0 # via docker
|
websocket-client==0.57.0 # via docker
|
||||||
werkzeug==1.0.1 # via flask, moto, pytest-flask
|
werkzeug==1.0.1 # via flask, moto, pytest-flask
|
||||||
wrapt==1.12.1 # via aws-xray-sdk
|
wrapt==1.12.1 # via aws-xray-sdk
|
||||||
xmltodict==0.12.0 # via moto
|
xmltodict==0.12.0 # via moto
|
||||||
zipp==3.1.0 # via importlib-metadata
|
zipp==3.1.0 # via importlib-metadata, moto
|
||||||
|
|
||||||
# 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
|
||||||
|
@ -4,33 +4,33 @@
|
|||||||
#
|
#
|
||||||
# pip-compile --no-index --output-file=requirements.txt requirements.in
|
# pip-compile --no-index --output-file=requirements.txt requirements.in
|
||||||
#
|
#
|
||||||
acme==1.6.0 # via -r requirements.in
|
acme==1.8.0 # via -r requirements.in
|
||||||
alembic-autogenerate-enums==0.0.2 # via -r requirements.in
|
alembic-autogenerate-enums==0.0.2 # via -r requirements.in
|
||||||
alembic==1.4.2 # via flask-migrate
|
alembic==1.4.2 # via flask-migrate
|
||||||
amqp==2.5.2 # via kombu
|
amqp==2.5.2 # via kombu
|
||||||
aniso8601==8.0.0 # via flask-restful
|
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
|
asyncpool==1.0 # via -r requirements.in
|
||||||
bcrypt==3.1.7 # via flask-bcrypt, paramiko
|
bcrypt==3.1.7 # via flask-bcrypt, paramiko
|
||||||
beautifulsoup4==4.9.1 # via cloudflare
|
beautifulsoup4==4.9.1 # via cloudflare
|
||||||
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.14.28 # via -r requirements.in
|
boto3==1.14.61 # via -r requirements.in
|
||||||
botocore==1.17.28 # via -r requirements.in, boto3, s3transfer
|
botocore==1.17.61 # 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.6.20 # via -r requirements.in, requests
|
certifi==2020.6.20 # via -r requirements.in, requests
|
||||||
certsrv==2.1.1 # via -r requirements.in
|
certsrv==2.1.1 # via -r requirements.in
|
||||||
cffi==1.14.0 # via bcrypt, cryptography, pynacl
|
cffi==1.14.0 # via bcrypt, cryptography, pynacl
|
||||||
chardet==3.0.4 # via requests
|
chardet==3.0.4 # via requests
|
||||||
click==7.1.1 # via flask
|
click==7.1.1 # via flask
|
||||||
cloudflare==2.8.8 # via -r requirements.in
|
cloudflare==2.8.13 # via -r requirements.in
|
||||||
cryptography==2.9.2 # via -r requirements.in, acme, josepy, paramiko, pyopenssl, requests
|
cryptography==3.1 # via -r requirements.in, acme, josepy, paramiko, pyopenssl, requests
|
||||||
dnspython3==1.15.0 # via -r requirements.in
|
dnspython3==1.15.0 # via -r requirements.in
|
||||||
dnspython==1.15.0 # via dnspython3
|
dnspython==1.15.0 # via dnspython3
|
||||||
docutils==0.15.2 # via botocore
|
docutils==0.15.2 # via botocore
|
||||||
dyn==1.8.1 # via -r requirements.in
|
dyn==1.8.1 # via -r requirements.in
|
||||||
flask-bcrypt==0.7.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-mail==0.9.1 # via -r requirements.in
|
||||||
flask-migrate==2.5.3 # via -r requirements.in
|
flask-migrate==2.5.3 # via -r requirements.in
|
||||||
flask-principal==0.4.0 # 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
|
gunicorn==20.0.4 # via -r requirements.in
|
||||||
hvac==0.10.5 # via -r requirements.in
|
hvac==0.10.5 # via -r requirements.in
|
||||||
idna==2.9 # via requests
|
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
|
itsdangerous==1.1.0 # via flask
|
||||||
javaobj-py3==0.4.0.1 # via pyjks
|
javaobj-py3==0.4.0.1 # via pyjks
|
||||||
jinja2==2.11.2 # via -r requirements.in, flask
|
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-sqlalchemy==0.23.1 # via -r requirements.in
|
||||||
marshmallow==2.20.4 # via -r requirements.in, marshmallow-sqlalchemy
|
marshmallow==2.20.4 # via -r requirements.in, marshmallow-sqlalchemy
|
||||||
ndg-httpsclient==0.5.1 # via -r requirements.in
|
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
|
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-modules==0.2.8 # via pyjks, python-ldap
|
||||||
pyasn1==0.4.8 # via ndg-httpsclient, pyasn1-modules, pyjks, python-ldap
|
pyasn1==0.4.8 # via ndg-httpsclient, pyasn1-modules, pyjks, python-ldap
|
||||||
pycparser==2.20 # via cffi
|
pycparser==2.20 # via cffi
|
||||||
|
72
setup.py
72
setup.py
@ -9,30 +9,18 @@ Is a TLS management and orchestration tool.
|
|||||||
"""
|
"""
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
import os.path
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os.path
|
||||||
|
import sys
|
||||||
|
from subprocess import check_output
|
||||||
|
|
||||||
from distutils import log
|
from setuptools import Command
|
||||||
from distutils.core import Command
|
from setuptools import setup, find_packages
|
||||||
from setuptools.command.develop import develop
|
from setuptools.command.develop import develop
|
||||||
from setuptools.command.install import install
|
from setuptools.command.install import install
|
||||||
from setuptools.command.sdist import sdist
|
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__)))
|
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:
|
with open(os.path.join(ROOT, 'lemur', '__about__.py')) as f:
|
||||||
exec(f.read(), about) # nosec: about file is benign
|
exec(f.read(), about) # nosec: about file is benign
|
||||||
|
|
||||||
install_requires_g = parse_requirements("requirements.txt", session=PipSession())
|
# Parse requirements files
|
||||||
tests_require_g = parse_requirements("requirements-tests.txt", session=PipSession())
|
with open('requirements.txt') as f:
|
||||||
docs_require_g = parse_requirements("requirements-docs.txt", session=PipSession())
|
install_requirements = f.read().splitlines()
|
||||||
dev_requires_g = parse_requirements("requirements-dev.txt", session=PipSession())
|
|
||||||
|
|
||||||
if tuple(map(int, pip.__version__.split('.'))) >= (20, 1):
|
with open('requirements-tests.txt') as f:
|
||||||
install_requires = [str(ir.requirement) for ir in install_requires_g]
|
tests_requirements = f.read().splitlines()
|
||||||
tests_require = [str(ir.requirement) for ir in tests_require_g]
|
|
||||||
docs_require = [str(ir.requirement) for ir in docs_require_g]
|
with open('requirements-docs.txt') as f:
|
||||||
dev_requires = [str(ir.requirement) for ir in dev_requires_g]
|
docs_requirements = f.read().splitlines()
|
||||||
else:
|
|
||||||
install_requires = [str(ir.req) for ir in install_requires_g]
|
with open('requirements-dev.txt') as f:
|
||||||
tests_require = [str(ir.req) for ir in tests_require_g]
|
dev_requirements = f.read().splitlines()
|
||||||
docs_require = [str(ir.req) for ir in docs_require_g]
|
|
||||||
dev_requires = [str(ir.req) for ir in dev_requires_g]
|
|
||||||
|
|
||||||
|
|
||||||
class SmartInstall(install):
|
class SmartInstall(install):
|
||||||
@ -67,6 +52,7 @@ class SmartInstall(install):
|
|||||||
If the package indicator is missing, this will also force a run of
|
If the package indicator is missing, this will also force a run of
|
||||||
`build_static` which is required for JavaScript assets and other things.
|
`build_static` which is required for JavaScript assets and other things.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _needs_static(self):
|
def _needs_static(self):
|
||||||
return not os.path.exists(os.path.join(ROOT, 'lemur/static/dist'))
|
return not os.path.exists(os.path.join(ROOT, 'lemur/static/dist'))
|
||||||
|
|
||||||
@ -105,16 +91,16 @@ class BuildStatic(Command):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
log.info("running [npm install --quiet] in {0}".format(ROOT))
|
logging.info("running [npm install --quiet] in {0}".format(ROOT))
|
||||||
try:
|
try:
|
||||||
check_output(['npm', 'install', '--quiet'], cwd=ROOT)
|
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)
|
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)
|
check_output([os.path.join(ROOT, 'node_modules', '.bin', 'gulp'), 'package'], cwd=ROOT)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warn("Unable to build static content")
|
logging.warn("Unable to build static content")
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
@ -128,11 +114,11 @@ setup(
|
|||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
install_requires=install_requires,
|
install_requires=install_requirements,
|
||||||
extras_require={
|
extras_require={
|
||||||
'tests': tests_require,
|
'tests': tests_requirements,
|
||||||
'docs': docs_require,
|
'docs': docs_requirements,
|
||||||
'dev': dev_requires,
|
'dev': dev_requirements,
|
||||||
},
|
},
|
||||||
cmdclass={
|
cmdclass={
|
||||||
'build_static': BuildStatic,
|
'build_static': BuildStatic,
|
||||||
@ -167,7 +153,9 @@ setup(
|
|||||||
'vault_source = lemur.plugins.lemur_vault_dest.plugin:VaultSourcePlugin',
|
'vault_source = lemur.plugins.lemur_vault_dest.plugin:VaultSourcePlugin',
|
||||||
'vault_desination = lemur.plugins.lemur_vault_dest.plugin:VaultDestinationPlugin',
|
'vault_desination = lemur.plugins.lemur_vault_dest.plugin:VaultDestinationPlugin',
|
||||||
'adcs_issuer = lemur.plugins.lemur_adcs.plugin:ADCSIssuerPlugin',
|
'adcs_issuer = lemur.plugins.lemur_adcs.plugin:ADCSIssuerPlugin',
|
||||||
'adcs_source = lemur.plugins.lemur_adcs.plugin:ADCSSourcePlugin'
|
'adcs_source = lemur.plugins.lemur_adcs.plugin:ADCSSourcePlugin',
|
||||||
|
'entrust_issuer = lemur.plugins.lemur_entrust.plugin:EntrustIssuerPlugin',
|
||||||
|
'entrust_source = lemur.plugins.lemur_entrust.plugin:EntrustSourcePlugin'
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
classifiers=[
|
classifiers=[
|
||||||
|
Loading…
x
Reference in New Issue
Block a user