Skip to content

Commit

Permalink
Merge branch 'master' into backend-nullable
Browse files Browse the repository at this point in the history
  • Loading branch information
johnthagen committed Oct 24, 2020
2 parents b40766b + e9ec5d4 commit 8c6ceea
Show file tree
Hide file tree
Showing 15 changed files with 321 additions and 51 deletions.
14 changes: 9 additions & 5 deletions .gitattributes
@@ -1,12 +1,16 @@
src/resources/dictionaries/* linguist-detectable=false

*.js eol=lf
*.jsx eol=lf
*.json eol=lf

# Auto detect text files and perform LF normalization
* text=auto

# Force LF for shell scripts.
# Without this, these can fail when passed from Windows into Docker.
# Without this, these can fail when copied from Windows into Docker.
*.sh text eol=lf
*.py text eol=lf

# Force LF for JavaScript/TypeScript files to conform with standards and not conflict with Prettier.
*.js eol=lf
*.jsx eol=lf
*.ts eol=lf
*.tsx eol=lf
*.json eol=lf
42 changes: 35 additions & 7 deletions certmgr/Dockerfile
@@ -1,10 +1,38 @@
FROM certbot/certbot:v1.8.0
# Dockerfile to build a Certificate Manager container. The certificate manager
# shall generate self-signed certificates and/or it shall use certbot to get
# certificates from letsencrypt.
#
# The certmgr may also be configured to push some of its certificates to an
# Amazon Web Services S3 bucket for use by devices that need a certificate when
# an internet connection is not always available.
#
# The container's entrypoint function and the modules that it calls are written
# in Python. The Python modules use the following modules that are not explicitly
# installed since they are currently installed with certbot:
# - requests
# - openssl

RUN apk update && \
apk upgrade && \
apk add --no-cache bash && \
apk add --no-cache curl

COPY scripts/*.sh /usr/bin/
FROM debian:buster

ENTRYPOINT ["entrypoint.sh"]
RUN apt-get update && \
apt-get install -y apt-utils curl certbot zip && \
apt-get autoremove && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
curl -sL https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip -o awscliv2.zip && \
unzip awscliv2.zip && \
aws/install && \
rm -rf awscliv2.zip \
aws \
/usr/local/aws-cli/v2/*/dist/aws_completer \
/usr/local/aws-cli/v2/*/dist/awscli/data/ac.index \
/usr/local/aws-cli/v2/*/dist/awscli/examples

RUN mkdir /scripts

WORKDIR /scripts

COPY scripts/*.py .

ENTRYPOINT ["./entrypoint.py"]
1 change: 0 additions & 1 deletion certmgr/README.md
Expand Up @@ -31,7 +31,6 @@ behavior:
| CERT_EMAIL | Set to e-mail address for certificate expiration notices first |
| CERT_STAGING | Primary domain for the certificate - used to specify location of certificate |
| CERT_DOMAINS | A space separated list of domains for the certificate. |
| CERT_VERBOSE | Set to 1 for verbose output to aid in debugging; 0 otherwise. |
| SERVER_NAME | Name of the server - used to specify the directory where the certificates are stored. |
| MAX_CONNECT_TRIES | Number of times to check if the webserver is up before attempting to get a certificate from letsencrypt. |

Expand Down
11 changes: 11 additions & 0 deletions certmgr/scripts/base_cert.py
@@ -0,0 +1,11 @@
from abc import ABC, abstractmethod


class BaseCert(ABC):
@abstractmethod
def create(self, force: bool = False) -> None:
pass

@abstractmethod
def renew(self) -> None:
pass
36 changes: 36 additions & 0 deletions certmgr/scripts/entrypoint.py
@@ -0,0 +1,36 @@
#!/usr/bin/env python3

import os
import sys
import time
from typing import Dict, Optional

from base_cert import BaseCert
from func import lookup_env
from letsencrypt_cert import LetsEncryptCert
from self_signed_cert import SelfSignedCert

if __name__ == "__main__":

mode_choices: Dict[str, Optional[BaseCert]] = {
"self-signed": SelfSignedCert(),
"letsencrypt": LetsEncryptCert(),
}

cert_store = lookup_env("CERT_STORE")
for subdir in ("nginx", "selfsigned"):
os.makedirs(f"{cert_store}/{subdir}", 0o755, True)

cert_mode = lookup_env("CERT_MODE")
print(f"Running in {cert_mode} mode")
cert_obj = mode_choices.get(cert_mode, None)

if cert_obj is not None:
cert_obj.create()
while True:
# sleep for 12 hours before checking for renewal
time.sleep(12 * 3600)
cert_obj.renew()
else:
print(f"Cannot run {cert_mode} mode")
sys.exit(99)
51 changes: 51 additions & 0 deletions certmgr/scripts/func.py
@@ -0,0 +1,51 @@
import os
from pathlib import Path
from typing import Dict, Optional, Union

env_defaults: Dict[str, Union[str, int]] = {
"CERT_MODE": "self-signed",
"CERT_STORE": "/etc/cert_store",
"CERT_EMAIL": "",
"CERT_STAGING": 0,
"MAX_CONNECT_TRIES": 15,
"CERT_DOMAINS": "",
"SERVER_NAME": "",
}


def lookup_env(env_var: str) -> Optional[Union[str, int]]:
"""
Look up environment variable
Look up an environment variable and return its value or its
default value. It the variable is not set and is not listed
in the defaults, then None is returned
"""
if env_var in os.environ:
return os.environ[env_var]
elif env_var in env_defaults:
return env_defaults[env_var]
else:
return None


def update_link(src: Path, dest: Path) -> None:
"""
Create/move a symbolic link at 'dest' to point to 'src'
If dest already exists and is not a link, it is deleted
first.
"""
print(f"linking {src} to {dest}")
if dest.exists():
if dest.is_symlink():
link_target: str = dest.readlink()
if link_target != src:
dest.unlink()
else:
# src already points to the dest
return
else:
print(f"{dest} exists and is not a link")
dest.unlink()
dest.symlink_to(src)
29 changes: 2 additions & 27 deletions certmgr/scripts/func.sh
Expand Up @@ -20,11 +20,6 @@ init_vars() {
fi
# create $cert_domains as an array of domain names
IFS=" " read -r -a cert_domains <<< "${CERT_DOMAINS}"
debug_log "Self-signed Certificates stored in ${CERT_STORE}"
debug_log "Certificate name: ${SERVER_NAME}"
debug_log "Domains: ${cert_domains[*]}"
debug_log "Certificates expire in ${SELF_SIGNED_EXPIRE}"
debug_log "Minimum days before renewal: ${CERT_MIN_DAYS_TO_EXPIRE}"

CERTBOT_DIR="/etc/letsencrypt/live/${SERVER_NAME}"
# Path where the self-signed certificates are stored
Expand All @@ -39,37 +34,24 @@ init_cert_store() {
done
}

debug_log() {
if [ "${CERT_VERBOSE}" = "1" ] ; then
echo $*
fi
}

update_link() {
src=$1
target=$2

debug_log "linking ${src} to ${target}"
echo "linking ${src} to ${target}"
link_target=`readlink ${target}`
if [ "${link_target}" != "${src}" ] ; then
if [ -L "${target}" ]; then
rm "${target}"
debug_log " Old link removed"
fi
ln -s ${src} ${target}
else
debug_log " ${target} already points to ${src}"
fi

}

create_selfsigned_cert() {
debug_log "Self-signed certificates are stored in ${SELF_SIGNED_PATH}"
mkdir -p ${SELF_SIGNED_PATH}
openssl req -x509 -nodes -newkey rsa:4096 -days ${SELF_SIGNED_EXPIRE} -keyout "${SELF_SIGNED_PATH}/privkey.pem" -out "${SELF_SIGNED_PATH}/fullchain.pem" -subj '/CN=localhost'
debug_log "Created certificate in ${CERT_STORE} for ${cert_domains}"
debug_log "Expires: "`openssl x509 -in "${SELF_SIGNED_PATH}/fullchain.pem" -noout -enddate`

# Update Nginx link
update_link "${SELF_SIGNED_PATH}" "${NGINX_CERT_PATH}"
}
Expand All @@ -78,7 +60,6 @@ create_selfsigned_cert() {
renew_selfsigned_cert()
{
CERT_FILE="${SELF_SIGNED_PATH}/fullchain.pem"
debug_log "Checking for renewal of ${CERT_FILE}"

CERT_MIN_SEC_TO_EXPIRE=$(( ${CERT_MIN_DAYS_TO_EXPIRE} * 3600 * 24 ))
if [ -f ${CERT_FILE} ] ; then
Expand All @@ -94,7 +75,6 @@ renew_selfsigned_cert()
}

create_certbot_cert() {
debug_log "### Requesting Let's Encrypt certificate for ${cert_domains} ..."

# Select appropriate email arg
if [ -z "${CERT_EMAIL}" ] ; then
Expand All @@ -106,7 +86,6 @@ create_certbot_cert() {
# Enable staging mode if needed
if [ ${CERT_STAGING} != "0" ]; then staging_arg="--staging"; fi

debug_log `pwd`
cert_cmd="certbot certonly --webroot -w /var/www/certbot \
${staging_arg} \
${email_arg} \
Expand All @@ -115,20 +94,16 @@ create_certbot_cert() {
--agree-tos \
--non-interactive \
--force-renewal"
debug_log "$cert_cmd"
if $cert_cmd ; then
update_link "/etc/letsencrypt/live/${SERVER_NAME}" "${NGINX_CERT_PATH}"
fi
}

wait_for_webserver() {
debug_log "Attempting connection to ${cert_domains}"
debug_log "Max attempts = ${MAX_CONNECT_TRIES}"
count=0;
while [ ${count} -lt ${MAX_CONNECT_TRIES} ] ; do
debug_log "Connection attempt ${count}"
if curl -I "http://${cert_domains}" 2>&1 | grep -w "200\|301" ; then
debug_log "${cert_domains} is up."
echo "${cert_domains} is up."
return 0;
fi
let "count+=1"
Expand Down
5 changes: 2 additions & 3 deletions certmgr/scripts/letsencrypt.sh
Expand Up @@ -18,16 +18,15 @@ fi
CERT_ISSUER=""
if [ -f "${NGINX_CERT_PATH}/fullchain.pem" ] ; then
CERT_ISSUER=`openssl x509 -in "${NGINX_CERT_PATH}/fullchain.pem" -noout -issuer | sed 's/issuer=CN *= *//'`
debug_log "Issuer for existing certificate is: ${CERT_ISSUER}"
fi

# If it is a self-signed cert, wait for the webserver to come up
# and replace it with a cert from letsencrypt
if [ -z "${CERT_ISSUER}" ] || [ "${CERT_ISSUER}" = "localhost" ] ; then
echo "Waiting for webserver to come up"
if ! wait_for_webserver ; then
debug_log "Could not connect to webserver"
#exit 1
echo "Could not connect to webserver"
exit 1
fi

echo "Request certificate from Let's Encrypt"
Expand Down

0 comments on commit 8c6ceea

Please sign in to comment.