From b98159d5b90fe0fd609a694257bb25a4fa579b0e Mon Sep 17 00:00:00 2001 From: Kevin Morris Date: Sat, 27 Nov 2021 16:43:29 -0800 Subject: [PATCH] change(docker): use step-ca for CA + cert generation Signed-off-by: Kevin Morris --- Dockerfile | 3 +- docker-compose.aur-dev.yml | 3 +- docker-compose.override.yml | 14 --- docker-compose.yml | 14 ++- docker/ca-entrypoint.sh | 163 +++++++++++++++++++++--------- docker/health/ca.sh | 2 + docker/nginx-entrypoint.sh | 2 +- docker/scripts/install-deps.sh | 2 +- docker/scripts/run-ca.sh | 7 ++ docker/scripts/update-step-config | 19 ++++ 10 files changed, 160 insertions(+), 69 deletions(-) create mode 100755 docker/health/ca.sh create mode 100755 docker/scripts/run-ca.sh create mode 100755 docker/scripts/update-step-config diff --git a/Dockerfile b/Dockerfile index 3c12cbf8..9af78c3e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,8 @@ RUN /install-deps.sh # Copy Docker scripts COPY ./docker /docker -COPY ./docker/scripts/*.sh /usr/local/bin/ +COPY ./docker/scripts/* /usr/local/bin/ + # Copy over all aurweb files. COPY . /aurweb diff --git a/docker-compose.aur-dev.yml b/docker-compose.aur-dev.yml index 4b522e56..f27b2b19 100644 --- a/docker-compose.aur-dev.yml +++ b/docker-compose.aur-dev.yml @@ -70,9 +70,8 @@ services: nginx: restart: always volumes: - - ${GIT_DATA_DIR}:/aurweb/aur.git - data:/data - - logs:/var/log/nginx + - archives:/var/lib/aurweb/archives - smartgit_run:/var/run/smartgit volumes: diff --git a/docker-compose.override.yml b/docker-compose.override.yml index eae12a92..8c74f947 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -6,10 +6,6 @@ services: mariadb: condition: service_healthy - ca: - volumes: - - ./data:/data - git: volumes: - git_data:/aurweb/aur.git @@ -45,13 +41,3 @@ services: - ./web/template:/aurweb/web/template - ./web/lib:/aurweb/web/lib - ./templates:/aurweb/templates - - nginx: - volumes: - - git_data:/aurweb/aur.git - - ./data:/data - - ./logs:/var/log/nginx - - ./web/html:/aurweb/web/html - - ./web/template:/aurweb/web/template - - ./web/lib:/aurweb/web/lib - - smartgit_run:/var/run/smartgit diff --git a/docker-compose.yml b/docker-compose.yml index acb5dd65..c1f93319 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,7 +29,16 @@ services: image: aurweb:latest init: true entrypoint: /docker/ca-entrypoint.sh - command: echo + command: /docker/scripts/run-ca.sh + healthcheck: + test: "bash /docker/health/run-ca.sh" + interval: 3s + tmpfs: + - /tmp + volumes: + - ./docker:/docker + - ./data:/data + - step:/root/.step memcached: image: aurweb:latest @@ -261,7 +270,9 @@ services: php-fpm: condition: service_healthy volumes: + - ./data:/data - archives:/var/lib/aurweb/archives + - smartgit_run:/var/run/smartgit sharness: image: aurweb:latest @@ -347,3 +358,4 @@ volumes: git_data: {} # Share aurweb/aur.git smartgit_run: {} archives: {} + step: {} diff --git a/docker/ca-entrypoint.sh b/docker/ca-entrypoint.sh index 42d8bd14..d03efbbc 100755 --- a/docker/ca-entrypoint.sh +++ b/docker/ca-entrypoint.sh @@ -1,58 +1,123 @@ #!/bin/bash +# Initialize step-ca and request certificates from it. +# +# Certificates created by this service are meant to be used in +# aurweb Docker's nginx service. +# +# If ./data/root_ca.crt is present, CA generation is skipped. +# If ./data/${host}.{cert,key}.pem is available, host certificate +# generation is skipped. +# set -eou pipefail -if [ -f /data/ca.root.pem ]; then - echo "Already have certs, skipping." - exit 0 +# /data-based variables. +DATA_DIR="/data" +DATA_ROOT_CA="$DATA_DIR/root_ca.crt" +DATA_CERT="$DATA_DIR/localhost.cert.pem" +DATA_CERT_KEY="$DATA_DIR/localhost.key.pem" + +# Host certificates requested from the CA (separated by spaces). +DATA_CERT_HOSTS='localhost' + +# Local step paths and CA configuration values. +STEP_DIR="$(step-cli path)" +STEP_CA_CONFIG="$STEP_DIR/config/ca.json" +STEP_CA_ADDR='127.0.0.1:8443' +STEP_CA_URL='https://localhost:8443' +STEP_CA_PROVISIONER='admin@localhost' + +# Password file used for both --password-file and --provisioner-password-file. +STEP_PASSWD_FILE="$STEP_DIR/password.txt" + +# Hostnames supported by the CA. +STEP_CA_NAME='aurweb' +STEP_CA_DNS='localhost' + +make_password() { + # Create a random 20-length password and write it to $1. + openssl rand -hex 20 > $1 +} + +setup_step_ca() { + # Cleanup and setup step ca configuration. + rm -rf $STEP_DIR/* + + # Initialize `step` + make_password "$STEP_PASSWD_FILE" + step-cli ca init \ + --name="$STEP_CA_NAME" \ + --dns="$STEP_CA_DNS" \ + --address="$STEP_CA_ADDR" \ + --password-file="$STEP_PASSWD_FILE" \ + --provisioner="$STEP_CA_PROVISIONER" \ + --provisioner-password-file="$STEP_PASSWD_FILE" \ + --with-ca-url="$STEP_CA_URL" + + # Update ca.json max TLS certificate duration to a year. + update-step-config "$STEP_CA_CONFIG" + + # Install root_ca.crt as read/writable to /data/root_ca.crt. + install -m666 "$STEP_DIR/certs/root_ca.crt" "$DATA_ROOT_CA" +} + +start_step_ca() { + # Start the step-ca web server. + step-ca "$STEP_CA_CONFIG" \ + --password-file="$STEP_PASSWD_FILE" & + until printf "" 2>>/dev/null >>/dev/tcp/127.0.0.1/8443; do + sleep 1 + done +} + +kill_step_ca() { + # Stop the step-ca web server. + killall step-ca >/dev/null 2>&1 || /bin/true +} + +install_step_ca() { + # Install step-ca certificate authority to the system. + step-cli certificate install "$STEP_DIR/certs/root_ca.crt" +} + +step_cert_request() { + # Request a certificate from the step ca. + step-cli ca certificate \ + --not-after=8800h \ + --provisioner="$STEP_CA_PROVISIONER" \ + --provisioner-password-file="$STEP_PASSWD_FILE" \ + $1 $2 $3 + chmod 666 /data/${1}.*.pem +} + +if [ ! -f $DATA_ROOT_CA ]; then + setup_step_ca + install_step_ca fi -# Generate a new 2048-bit RSA key for the Root CA. -openssl genrsa -des3 -out /data/ca.key -passout pass:devca 2048 +# For all hosts separated by spaces in $DATA_CERT_HOSTS, perform a check +# for their existence in /data and react accordingly. +for host in $DATA_CERT_HOSTS; do + if [ -f /data/${host}.cert.pem ] && [ -f /data/${host}.key.pem ]; then + # Found an override. Move on to running the service after + # printing a notification to the user. + echo "Found '${host}.{cert,key}.pem' override, skipping..." + echo -n "Note: If you need to regenerate certificates, run " + echo '`rm -f data/*.{cert,key}.pem` before starting this service.' + exec "$@" + else + # Otherwise, we had a missing cert or key, so remove both. + rm -f /data/${host}.cert.pem + rm -f /data/${host}.key.pem + fi +done -# Request and self-sign a new Root CA certificate, using -# the RSA key. Output Root CA PEM-format certificate and key: -# /data/ca.root.pem and /data/ca.key.pem -openssl req -x509 -new -nodes -sha256 -days 1825 \ - -passin pass:devca \ - -subj "/C=US/ST=California/L=Authority/O=aurweb/CN=localhost" \ - -in /data/ca.key -out /data/ca.root.pem -keyout /data/ca.key.pem +start_step_ca +for host in $DATA_CERT_HOSTS; do + step_cert_request $host /data/${host}.cert.pem /data/${host}.key.pem +done +kill_step_ca -# Generate a new 2048-bit RSA key for a localhost server. -openssl genrsa -out /data/localhost.key 2048 - -# Generate a Certificate Signing Request (CSR) for the localhost server -# using the RSA key we generated above. -openssl req -new -key /data/localhost.key -passout pass:devca \ - -subj "/C=US/ST=California/L=Server/O=aurweb/CN=localhost" \ - -out /data/localhost.csr - -# Get our CSR signed by our Root CA PEM-formatted certificate and key -# to produce a fresh /data/localhost.cert.pem PEM-formatted certificate. -openssl x509 -req -in /data/localhost.csr \ - -CA /data/ca.root.pem -CAkey /data/ca.key.pem \ - -CAcreateserial \ - -out /data/localhost.cert.pem \ - -days 825 -sha256 \ - -passin pass:devca \ - -extfile /docker/localhost.ext - -# Convert RSA key to a PEM-formatted key: /data/localhost.key.pem -openssl rsa -in /data/localhost.key -text > /data/localhost.key.pem - -# At the end here, our notable certificates and keys are: -# - /data/ca.root.pem -# - /data/ca.key.pem -# - /data/localhost.key.pem -# - /data/localhost.cert.pem -# -# When running a server which uses the localhost certificate, a chain -# should be used, starting with localhost.cert.pem: -# - cat /data/localhost.cert.pem /data/ca.root.pem > localhost.chain.pem -# -# The Root CA (ca.root.pem) should be imported into browsers or -# ca-certificates on machines wishing to verify localhost. -# - -chmod 666 /data/* +# Set permissions to /data to rwx for everybody. +chmod 777 /data exec "$@" diff --git a/docker/health/ca.sh b/docker/health/ca.sh new file mode 100755 index 00000000..3e4bbe8e --- /dev/null +++ b/docker/health/ca.sh @@ -0,0 +1,2 @@ + +exec printf "" 2>>/dev/null >>/dev/tcp/127.0.0.1/8443 diff --git a/docker/nginx-entrypoint.sh b/docker/nginx-entrypoint.sh index 6b9a6954..1527cda7 100755 --- a/docker/nginx-entrypoint.sh +++ b/docker/nginx-entrypoint.sh @@ -15,7 +15,7 @@ if [ -f "$CERT" ]; then cp -vf "$CERT" "$DEST_CERT" cp -vf "$KEY" "$DEST_KEY" else - cat /data/localhost.cert.pem /data/ca.root.pem > "$DEST_CERT" + cat /data/localhost.cert.pem /data/root_ca.crt > "$DEST_CERT" cp -vf /data/localhost.key.pem "$DEST_KEY" fi diff --git a/docker/scripts/install-deps.sh b/docker/scripts/install-deps.sh index ad0157f8..372b6e0c 100755 --- a/docker/scripts/install-deps.sh +++ b/docker/scripts/install-deps.sh @@ -9,6 +9,6 @@ pacman -Syu --noconfirm --noprogressbar \ mariadb mariadb-libs cgit-aurweb uwsgi uwsgi-plugin-cgi \ php php-fpm memcached php-memcached python-pip pyalpm \ python-srcinfo curl libeatmydata cronie python-poetry \ - python-poetry-core + python-poetry-core step-cli step-ca exec "$@" diff --git a/docker/scripts/run-ca.sh b/docker/scripts/run-ca.sh new file mode 100755 index 00000000..1ef45ef7 --- /dev/null +++ b/docker/scripts/run-ca.sh @@ -0,0 +1,7 @@ +#!/bin/bash +STEP_DIR="$(step-cli path)" +STEP_PASSWD_FILE="$STEP_DIR/password.txt" +STEP_CA_CONFIG="$STEP_DIR/config/ca.json" + +# Start the step-ca https server. +exec step-ca "$STEP_CA_CONFIG" --password-file="$STEP_PASSWD_FILE" diff --git a/docker/scripts/update-step-config b/docker/scripts/update-step-config new file mode 100755 index 00000000..bbdb2680 --- /dev/null +++ b/docker/scripts/update-step-config @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +import json +import sys + +CA_CONFIG = sys.argv[1] + +with open(CA_CONFIG) as f: + data = json.load(f) + +if "authority" not in data: + data["authority"] = dict() +if "claims" not in data["authority"]: + data["authority"]["claims"] = dict() + +# One year of certificate duration. +data["authority"]["claims"] = {"maxTLSCertDuration": "8800h"} + +with open(CA_CONFIG, "w") as f: + json.dump(data, f)