| 1 | #!/usr/bin/env bash |
| 2 | # Grove - Generate self-signed TLS certificates for Mononoke |
| 3 | # Mononoke SLAPI and git_server require TLS. This generates a CA, server cert, |
| 4 | # and client cert (for sl push mTLS). |
| 5 | # |
| 6 | # Usage: ./generate-certs.sh [domain-or-ip] |
| 7 | # |
| 8 | # Examples: |
| 9 | # ./generate-certs.sh # localhost/dev only |
| 10 | # ./generate-certs.sh grove.host # production hub |
| 11 | # ./generate-certs.sh 203.0.113.5 # instance by IP |
| 12 | |
| 13 | set -euo pipefail |
| 14 | |
| 15 | DOMAIN_OR_IP="${1:-}" |
| 16 | TLS_DIR="${GROVE_TLS_DIR:-/data/grove/tls}" |
| 17 | |
| 18 | mkdir -p "$TLS_DIR" |
| 19 | |
| 20 | echo "Generating Grove TLS certificates in $TLS_DIR..." |
| 21 | |
| 22 | # ── Build SAN list ───────────────────────────────────────────────── |
| 23 | |
| 24 | SAN_DNS=("localhost" "mononoke-slapi" "mononoke-git") |
| 25 | SAN_IP=("127.0.0.1") |
| 26 | |
| 27 | if [[ -n "$DOMAIN_OR_IP" ]]; then |
| 28 | if [[ "$DOMAIN_OR_IP" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then |
| 29 | SAN_IP+=("$DOMAIN_OR_IP") |
| 30 | else |
| 31 | SAN_DNS+=("$DOMAIN_OR_IP") |
| 32 | fi |
| 33 | fi |
| 34 | |
| 35 | # Build the SAN config section |
| 36 | SAN_ENTRIES="" |
| 37 | for i in "${!SAN_DNS[@]}"; do |
| 38 | SAN_ENTRIES+="DNS.$((i + 1)) = ${SAN_DNS[$i]}"$'\n' |
| 39 | done |
| 40 | for i in "${!SAN_IP[@]}"; do |
| 41 | SAN_ENTRIES+="IP.$((i + 1)) = ${SAN_IP[$i]}"$'\n' |
| 42 | done |
| 43 | |
| 44 | # ── CA ───────────────────────────────────────────────────────────── |
| 45 | |
| 46 | openssl genrsa -out "$TLS_DIR/ca.key" 4096 2>/dev/null |
| 47 | openssl req -new -x509 -key "$TLS_DIR/ca.key" \ |
| 48 | -out "$TLS_DIR/ca.crt" -days 3650 \ |
| 49 | -subj "/CN=Grove CA/O=Letterpress Labs" 2>/dev/null |
| 50 | |
| 51 | # ── Server cert ──────────────────────────────────────────────────── |
| 52 | |
| 53 | openssl genrsa -out "$TLS_DIR/server.key" 4096 2>/dev/null |
| 54 | openssl req -new -key "$TLS_DIR/server.key" \ |
| 55 | -out "$TLS_DIR/server.csr" \ |
| 56 | -subj "/CN=${DOMAIN_OR_IP:-localhost}/O=Letterpress Labs" 2>/dev/null |
| 57 | |
| 58 | cat > "$TLS_DIR/ext.cnf" <<EOF |
| 59 | authorityKeyIdentifier=keyid,issuer |
| 60 | basicConstraints=CA:FALSE |
| 61 | keyUsage=digitalSignature,keyEncipherment |
| 62 | extendedKeyUsage=serverAuth,clientAuth |
| 63 | subjectAltName=@alt_names |
| 64 | |
| 65 | [alt_names] |
| 66 | ${SAN_ENTRIES} |
| 67 | EOF |
| 68 | |
| 69 | openssl x509 -req -in "$TLS_DIR/server.csr" \ |
| 70 | -CA "$TLS_DIR/ca.crt" -CAkey "$TLS_DIR/ca.key" \ |
| 71 | -CAcreateserial -out "$TLS_DIR/server.crt" \ |
| 72 | -days 3650 -extfile "$TLS_DIR/ext.cnf" 2>/dev/null |
| 73 | |
| 74 | # ── Client cert (for sl push mTLS) ──────────────────────────────── |
| 75 | |
| 76 | openssl genrsa -out "$TLS_DIR/client.key" 4096 2>/dev/null |
| 77 | openssl req -new -key "$TLS_DIR/client.key" \ |
| 78 | -out "$TLS_DIR/client.csr" \ |
| 79 | -subj "/CN=grove-client/O=Letterpress Labs" 2>/dev/null |
| 80 | |
| 81 | openssl x509 -req -in "$TLS_DIR/client.csr" \ |
| 82 | -CA "$TLS_DIR/ca.crt" -CAkey "$TLS_DIR/ca.key" \ |
| 83 | -CAcreateserial -out "$TLS_DIR/client.crt" \ |
| 84 | -days 3650 2>/dev/null |
| 85 | |
| 86 | # ── Cleanup temp files ───────────────────────────────────────────── |
| 87 | |
| 88 | rm -f "$TLS_DIR"/*.csr "$TLS_DIR"/*.cnf "$TLS_DIR"/*.srl |
| 89 | |
| 90 | echo "" |
| 91 | echo "Certificates generated in $TLS_DIR:" |
| 92 | echo " ca.crt - Certificate Authority certificate" |
| 93 | echo " ca.key - Certificate Authority private key" |
| 94 | echo " server.crt - Server certificate (Mononoke SLAPI + git_server)" |
| 95 | echo " server.key - Server private key" |
| 96 | echo " client.crt - Client certificate (sl push mTLS)" |
| 97 | echo " client.key - Client private key" |
| 98 | if [[ -n "$DOMAIN_OR_IP" ]]; then |
| 99 | echo "" |
| 100 | echo "SANs: ${SAN_DNS[*]} / ${SAN_IP[*]}" |
| 101 | fi |
| 102 | |