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.
107 lines
3.9 KiB
Bash
Executable File
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
|