confluence_web/scripts/make-tls.sh
Artur Mukhamadiev c853fff995 deploy: optional Uvicorn TLS with a name-constrained private CA for IP-only networks
scripts/make-tls.sh creates a CA whose critical nameConstraints permit only the
listed IPv4 addresses (and the .invalid DNS subtree), plus an end-entity server
certificate; run-backend.sh passes CONFLUENCE_WEB_TLS_CERT/KEY to uvicorn.
2026-09-15 00:01:24 +03:00

107 lines
3.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# Create a name-constrained private CA and a server certificate for IP-only access.
#
# scripts/make-tls.sh <ip> [<ip>...] (default output dir: deploy/tls)
# TLS_DIR=/path scripts/make-tls.sh <ip>
#
# The CA carries a critical nameConstraints extension that permits only the listed
# IP addresses and no DNS names, so importing ca.crt as a trusted root lets a browser
# accept certificates from this CA for those addresses only. The server certificate
# is an end-entity certificate (CA:FALSE) with the addresses as subjectAltName.
#
# Distribute only ca.crt. Keep ca.key and server.key on the server host.
set -euo pipefail
if [[ $# -lt 1 ]]; then
echo "usage: $0 <ip> [<ip>...]" >&2
exit 2
fi
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TLS_DIR="${TLS_DIR:-$ROOT/deploy/tls}"
CA_DAYS="${CA_DAYS:-3650}"
LEAF_DAYS="${LEAF_DAYS:-825}" # Chrome and Safari reject server certificates valid for longer.
for ip in "$@"; do
if [[ ! "$ip" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]]; then
echo "only IPv4 addresses are supported: $ip" >&2
exit 2
fi
done
if [[ -e "$TLS_DIR/server.key" || -e "$TLS_DIR/server.crt" ]]; then
echo "refusing to overwrite $TLS_DIR/server.*; delete them to rotate the server certificate" >&2
exit 1
fi
if [[ -e "$TLS_DIR/ca.key" && ! -e "$TLS_DIR/ca.crt" ]] || [[ ! -e "$TLS_DIR/ca.key" && -e "$TLS_DIR/ca.crt" ]]; then
echo "incomplete CA in $TLS_DIR (need both ca.key and ca.crt, or neither)" >&2
exit 1
fi
umask 077
mkdir -p "$TLS_DIR"
permitted=""
san=""
for ip in "$@"; do
permitted+="permitted;IP:${ip}/255.255.255.255,"
san+="IP:${ip},"
done
permitted="${permitted%,}"
san="${san%,}"
# Only the listed addresses may appear in certificates from this CA. Any DNS name is
# forced to match the reserved .invalid domain, which no real host can, so this root
# can never vouch for a hostname.
cat > "$TLS_DIR/server.ext" <<EOF
basicConstraints=critical,CA:FALSE
keyUsage=critical,digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid
subjectAltName=${san}
EOF
if [[ -e "$TLS_DIR/ca.key" ]]; then
echo "reusing existing CA $TLS_DIR/ca.crt (server certificate rotation)"
else
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
-keyout "$TLS_DIR/ca.key" -out "$TLS_DIR/ca.crt" -days "$CA_DAYS" \
-subj "/CN=Confluence Research internal CA ($1)" \
-config <(printf '[req]\ndistinguished_name=dn\n[dn]\n') \
-addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
-addext "keyUsage=critical,keyCertSign,cRLSign" \
-addext "nameConstraints=critical,${permitted},permitted;DNS:.invalid" >/dev/null 2>&1
fi
openssl req -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
-keyout "$TLS_DIR/server.key" -out "$TLS_DIR/server.csr" -subj "/CN=Confluence Research server ($1)" \
-config <(printf '[req]\ndistinguished_name=dn\n[dn]\n') >/dev/null 2>&1
openssl x509 -req -in "$TLS_DIR/server.csr" -CA "$TLS_DIR/ca.crt" -CAkey "$TLS_DIR/ca.key" \
-CAcreateserial -out "$TLS_DIR/server.crt" -days "$LEAF_DAYS" \
-extfile "$TLS_DIR/server.ext" >/dev/null 2>&1
rm -f "$TLS_DIR/server.csr" "$TLS_DIR/ca.srl" "$TLS_DIR/server.ext"
chmod 0644 "$TLS_DIR/ca.crt" "$TLS_DIR/server.crt"
# Fails if an existing CA does not permit one of the requested addresses.
if ! openssl verify -CAfile "$TLS_DIR/ca.crt" "$TLS_DIR/server.crt" >/dev/null; then
echo "server certificate does not verify under $TLS_DIR/ca.crt (CA name constraints do not cover $*?)" >&2
rm -f "$TLS_DIR/server.key" "$TLS_DIR/server.crt"
exit 1
fi
cat <<EOF
Created in $TLS_DIR:
ca.crt distribute this one file; import as a trusted root (constrained to: $*)
ca.key keep private; needed only to issue a new server.crt
server.crt server certificate for: $*
server.key server private key
Enable in the env file:
CONFLUENCE_WEB_BIND_HOST=$1
CONFLUENCE_WEB_TLS_CERT=$TLS_DIR/server.crt
CONFLUENCE_WEB_TLS_KEY=$TLS_DIR/server.key
EOF