diff --git a/docker/Dockerfile-src b/docker/Dockerfile-src new file mode 100644 index 00000000..c23f249c --- /dev/null +++ b/docker/Dockerfile-src @@ -0,0 +1,64 @@ +FROM alpine:3.8 + +ARG VERSION +ENV VERSION master + +ENV uid 1337 +ENV gid 1337 +ENV user lemur +ENV group lemur + +RUN addgroup -S ${group} -g ${gid} && \ + adduser -D -S ${user} -G ${group} -u ${uid} && \ + apk --update add python3 libldap postgresql-client nginx supervisor curl tzdata openssl bash && \ + apk --update add --virtual build-dependencies \ + git \ + tar \ + curl \ + python3-dev \ + npm \ + bash \ + musl-dev \ + gcc \ + autoconf \ + automake \ + make \ + nasm \ + zlib-dev \ + postgresql-dev \ + libressl-dev \ + libffi-dev \ + cyrus-sasl-dev \ + openldap-dev && \ + pip3 install --upgrade pip && \ + pip3 install --upgrade setuptools && \ + mkdir -p /home/lemur/.lemur/ && \ + mkdir -p /run/nginx/ /etc/nginx/ssl/ + +COPY ./ /opt/lemur +WORKDIR /opt/lemur + +RUN chown -R $user:$group /opt/lemur/ /home/lemur/.lemur/ && \ + npm install --unsafe-perm && \ + pip3 install -e . && \ + node_modules/.bin/gulp build && \ + node_modules/.bin/gulp package --urlContextPath=$(urlContextPath) && \ + apk del build-dependencies + +COPY docker/entrypoint / +COPY docker/src/lemur.conf.py /home/lemur/.lemur/lemur.conf.py +COPY docker/supervisor.conf / +COPY docker/nginx/default.conf /etc/nginx/conf.d/ +COPY docker/nginx/default-ssl.conf /etc/nginx/conf.d/ + +RUN chmod +x /entrypoint +WORKDIR / + +HEALTHCHECK --interval=12s --timeout=12s --start-period=30s \ + CMD curl --fail http://localhost:80/api/1/healthcheck | grep -q ok || exit 1 + +USER root + +ENTRYPOINT ["/entrypoint"] + +CMD ["/usr/bin/supervisord","-c","supervisor.conf"] diff --git a/docker/entrypoint b/docker/entrypoint index 2a3a84e3..3f25951a 100644 --- a/docker/entrypoint +++ b/docker/entrypoint @@ -36,7 +36,7 @@ fi # fi echo " # Running init" -su lemur -s /bin/bash -c "cd /opt/lemur/lemur; python3 /opt/lemur/lemur/manage.py init -p ${LEMUR_ADMIN_PASSWORD}" +su lemur -s /bin/bash -c "cd /opt/lemur/lemur; lemur init -p ${LEMUR_ADMIN_PASSWORD}" echo " # Done" # echo "Creating user" @@ -47,11 +47,13 @@ echo " # Done" cron_notify="${CRON_NOTIFY:-"0 22 * * *"}" cron_sync="${CRON_SYNC:-"*/15 * * * *"}" cron_revoked="${CRON_CHECK_REVOKED:-"0 22 * * *"}" +cron_reissue="${CRON_REISSUE:-"0 23 * * *"}" echo " # Populating crontab" -echo "${cron_notify} lemur python3 /opt/lemur/lemur/manage.py notify expirations" > /etc/crontabs/lemur_notify -echo "${cron_sync} lemur python3 /opt/lemur/lemur/manage.py source sync -s all" > /etc/crontabs/lemur_sync -echo "${cron_revoked} lemur python3 /opt/lemur/lemur/manage.py certificate check_revoked" > /etc/crontabs/lemur_revoked +echo "${cron_notify} lemur notify expirations" > /etc/crontabs/lemur +echo "${cron_sync} lemur source sync -s all" >> /etc/crontabs/lemur +echo "${cron_revoked} lemur certificate check_revoked" >> /etc/crontabs/lemur +echo "${cron_reissue} lemur certificate reissue -c" >> /etc/crontabs/lemur echo " # Done" exec "$@" diff --git a/docker/src/lemur.conf.py b/docker/src/lemur.conf.py index 0f294b28..3cc51792 100644 --- a/docker/src/lemur.conf.py +++ b/docker/src/lemur.conf.py @@ -16,12 +16,16 @@ LEMUR_WHITELISTED_DOMAINS = [] LEMUR_EMAIL = '' LEMUR_SECURITY_TEAM_EMAIL = [] +ALLOW_CERT_DELETION = os.environ.get('ALLOW_CERT_DELETION') == "True" -LEMUR_DEFAULT_COUNTRY = repr(os.environ.get('LEMUR_DEFAULT_COUNTRY','')) -LEMUR_DEFAULT_STATE = repr(os.environ.get('LEMUR_DEFAULT_STATE','')) -LEMUR_DEFAULT_LOCATION = repr(os.environ.get('LEMUR_DEFAULT_LOCATION','')) -LEMUR_DEFAULT_ORGANIZATION = repr(os.environ.get('LEMUR_DEFAULT_ORGANIZATION','')) -LEMUR_DEFAULT_ORGANIZATIONAL_UNIT = repr(os.environ.get('LEMUR_DEFAULT_ORGANIZATIONAL_UNIT','')) +LEMUR_DEFAULT_COUNTRY = str(os.environ.get('LEMUR_DEFAULT_COUNTRY','')) +LEMUR_DEFAULT_STATE = str(os.environ.get('LEMUR_DEFAULT_STATE','')) +LEMUR_DEFAULT_LOCATION = str(os.environ.get('LEMUR_DEFAULT_LOCATION','')) +LEMUR_DEFAULT_ORGANIZATION = str(os.environ.get('LEMUR_DEFAULT_ORGANIZATION','')) +LEMUR_DEFAULT_ORGANIZATIONAL_UNIT = str(os.environ.get('LEMUR_DEFAULT_ORGANIZATIONAL_UNIT','')) + +LEMUR_DEFAULT_ISSUER_PLUGIN = str(os.environ.get('LEMUR_DEFAULT_ISSUER_PLUGIN','')) +LEMUR_DEFAULT_AUTHORITY = str(os.environ.get('LEMUR_DEFAULT_AUTHORITY','')) ACTIVE_PROVIDERS = [] diff --git a/docker/supervisor.conf b/docker/supervisor.conf index fed01581..ec4b221d 100644 --- a/docker/supervisor.conf +++ b/docker/supervisor.conf @@ -7,7 +7,7 @@ pidfile = /tmp/supervisord.pid [program:lemur] environment=LEMUR_CONF=/home/lemur/.lemur/lemur.conf.py -command=/usr/bin/python3 manage.py start -b 0.0.0.0:8000 +command=lemur start -b 0.0.0.0:8000 user=lemur directory=/opt/lemur/lemur stdout_logfile=/dev/stdout @@ -24,6 +24,7 @@ stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 [program:cron] +environment=LEMUR_CONF=/home/lemur/.lemur/lemur.conf.py command=/usr/sbin/crond -f user=root stdout_logfile=/dev/stdout diff --git a/lemur/certificates/schemas.py b/lemur/certificates/schemas.py index 8f15542d..42e444bc 100644 --- a/lemur/certificates/schemas.py +++ b/lemur/certificates/schemas.py @@ -146,7 +146,8 @@ class CertificateInputSchema(CertificateCreationSchema): data["extensions"]["subAltNames"] = {"names": []} elif not data["extensions"]["subAltNames"].get("names"): data["extensions"]["subAltNames"]["names"] = [] - data["extensions"]["subAltNames"]["names"] += csr_sans + + data["extensions"]["subAltNames"]["names"] = csr_sans return missing.convert_validity_years(data) diff --git a/lemur/plugins/lemur_vault_dest/plugin.py b/lemur/plugins/lemur_vault_dest/plugin.py index 41b9c252..3c5301f7 100755 --- a/lemur/plugins/lemur_vault_dest/plugin.py +++ b/lemur/plugins/lemur_vault_dest/plugin.py @@ -14,7 +14,7 @@ import re import hvac from flask import current_app -from lemur.common.defaults import common_name +from lemur.common.defaults import common_name, country, state, location, organizational_unit, organization from lemur.common.utils import parse_certificate from lemur.plugins.bases import DestinationPlugin from lemur.plugins.bases import SourcePlugin @@ -58,7 +58,7 @@ class VaultSourcePlugin(SourcePlugin): "helpMessage": "Authentication method to use", }, { - "name": "tokenFile/VaultRole", + "name": "tokenFileOrVaultRole", "type": "str", "required": True, "validation": "^([a-zA-Z0-9/._-]+/?)+$", @@ -94,7 +94,7 @@ class VaultSourcePlugin(SourcePlugin): body = "" url = self.get_option("vaultUrl", options) auth_method = self.get_option("authenticationMethod", options) - auth_key = self.get_option("tokenFile/vaultRole", options) + auth_key = self.get_option("tokenFileOrVaultRole", options) mount = self.get_option("vaultMount", options) path = self.get_option("vaultPath", options) obj_name = self.get_option("objectName", options) @@ -185,7 +185,7 @@ class VaultDestinationPlugin(DestinationPlugin): "helpMessage": "Authentication method to use", }, { - "name": "tokenFile/VaultRole", + "name": "tokenFileOrVaultRole", "type": "str", "required": True, "validation": "^([a-zA-Z0-9/._-]+/?)+$", @@ -202,15 +202,15 @@ class VaultDestinationPlugin(DestinationPlugin): "name": "vaultPath", "type": "str", "required": True, - "validation": "^([a-zA-Z0-9._-]+/?)+$", - "helpMessage": "Must be a valid Vault secrets path", + "validation": "^(([a-zA-Z0-9._-]+|{(CN|OU|O|L|S|C)})+/?)+$", + "helpMessage": "Must be a valid Vault secrets path. Support vars: {CN|OU|O|L|S|C}", }, { "name": "objectName", "type": "str", "required": False, - "validation": "[0-9a-zA-Z.:_-]+", - "helpMessage": "Name to bundle certs under, if blank use cn", + "validation": "^([0-9a-zA-Z.:_-]+|{(CN|OU|O|L|S|C)})+$", + "helpMessage": "Name to bundle certs under, if blank use {CN}. Support vars: {CN|OU|O|L|S|C}", }, { "name": "bundleChain", @@ -241,11 +241,12 @@ class VaultDestinationPlugin(DestinationPlugin): :param cert_chain: :return: """ - cname = common_name(parse_certificate(body)) + cert = parse_certificate(body) + cname = common_name(cert) url = self.get_option("vaultUrl", options) auth_method = self.get_option("authenticationMethod", options) - auth_key = self.get_option("tokenFile/vaultRole", options) + auth_key = self.get_option("tokenFileOrVaultRole", options) mount = self.get_option("vaultMount", options) path = self.get_option("vaultPath", options) bundle = self.get_option("bundleChain", options) @@ -285,10 +286,27 @@ class VaultDestinationPlugin(DestinationPlugin): client.secrets.kv.default_kv_version = api_version - if obj_name: - path = "{0}/{1}".format(path, obj_name) - else: - path = "{0}/{1}".format(path, cname) + t_path = path.format( + CN=cname, + OU=organizational_unit(cert), + O=organization(cert), # noqa: E741 + L=location(cert), + S=state(cert), + C=country(cert) + ) + if not obj_name: + obj_name = '{CN}' + + f_obj_name = obj_name.format( + CN=cname, + OU=organizational_unit(cert), + O=organization(cert), # noqa: E741 + L=location(cert), + S=state(cert), + C=country(cert) + ) + + path = "{0}/{1}".format(t_path, f_obj_name) secret = get_secret(client, mount, path) secret["data"][cname] = {}