feat(deploy/tls): self-signed CA + server cert generator
generate-certs.sh mints the bus CA and a NATS server certificate whose SANs cover the public IP (135.125.201.30), the WireGuard IP (10.42.0.1), the om hostname, and localhost/127.0.0.1 for on-host smoke tests (all overridable via env). Only the public ca.crt is committed; ca.key, server.key and server.crt are gitignored and distributed out of band. README documents generation, use and rotation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# Private keys and the deploy-specific server certificate never go to git.
|
||||
# Only the public CA certificate (ca.crt) is versioned, because clients embed it.
|
||||
*.key
|
||||
*.csr
|
||||
*.srl
|
||||
server.crt
|
||||
@@ -0,0 +1,56 @@
|
||||
# Bus TLS — self-signed CA and server certificate
|
||||
|
||||
The unibus data plane (NATS) is encrypted with TLS using the project's own
|
||||
self-signed CA. The bus is exposed publicly, protected by auth + TLS, so the CA
|
||||
is private (not Let's Encrypt) and every client we control embeds the public
|
||||
`ca.crt`; the server presents `server.crt`/`server.key`.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Secret? | Goes where |
|
||||
|---|---|---|
|
||||
| `ca.crt` | no (public) | versioned in git; embedded/distributed to every client |
|
||||
| `ca.key` | **yes** | stays on the machine that mints certs; gitignored |
|
||||
| `server.crt` | no | deployed to the bus host; gitignored (deploy-specific SANs) |
|
||||
| `server.key` | **yes** | deployed to the bus host over a secure channel; gitignored |
|
||||
|
||||
Only `ca.crt` is committed. `ca.key`, `server.key`, `server.crt`, and any
|
||||
`*.csr`/`*.srl` are gitignored — see `.gitignore`.
|
||||
|
||||
## Generate
|
||||
|
||||
```bash
|
||||
cd deploy/tls
|
||||
./generate-certs.sh # CA (if missing) + server cert with default SANs
|
||||
./generate-certs.sh --force # also regenerate the CA (invalidates pinned clients)
|
||||
```
|
||||
|
||||
The server certificate's SANs cover the public IP, the WireGuard IP, the om
|
||||
hostname, plus `localhost`/`127.0.0.1` for on-host smoke tests. Override the
|
||||
defaults via environment variables:
|
||||
|
||||
```bash
|
||||
UNIBUS_PUBLIC_IP=135.125.201.30 UNIBUS_WG_IP=10.42.0.1 UNIBUS_HOSTNAME=om ./generate-certs.sh
|
||||
```
|
||||
|
||||
Verify the SANs:
|
||||
|
||||
```bash
|
||||
openssl x509 -in server.crt -noout -text | grep -A1 'Subject Alternative Name'
|
||||
```
|
||||
|
||||
## Use
|
||||
|
||||
- **Server** (`membershipd`, phase 0001e): point it at `server.crt`/`server.key`
|
||||
so the embedded NATS presents the certificate and requires TLS. Built with
|
||||
`busauth.ServerTLSConfig(certPath, keyPath)`.
|
||||
- **Clients** (Go peers, mobile binding, gateway): pin `ca.crt` with
|
||||
`busauth.LoadCATLSConfig(caPath)` and pass the result as `client.Options.TLS`.
|
||||
|
||||
## Rotation
|
||||
|
||||
The CA is long-lived (10 years). Rotate the server certificate (825 days) by
|
||||
re-running `generate-certs.sh` (without `--force`) and redeploying
|
||||
`server.crt`/`server.key`; clients are unaffected because they pin the CA, not
|
||||
the server cert. Rotating the CA (`--force`) requires redistributing `ca.crt` to
|
||||
every client.
|
||||
@@ -0,0 +1,11 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBfTCCASOgAwIBAgIUW2HZJDDlixxw/DgNP/IDIrJ7MeMwCgYIKoZIzj0EAwIw
|
||||
FDESMBAGA1UEAwwJdW5pYnVzLWNhMB4XDTI2MDYwNzEwNDIyNloXDTM2MDYwNDEw
|
||||
NDIyNlowFDESMBAGA1UEAwwJdW5pYnVzLWNhMFkwEwYHKoZIzj0CAQYIKoZIzj0D
|
||||
AQcDQgAEe2by5l9dcEbqKB11yJtPIH9S/01XNhuFnBB/IpDevO2fWLLV+muqoB8C
|
||||
ADH1wKleq8jF5D0sSlK2DCuYrjAjPqNTMFEwHQYDVR0OBBYEFABX+UI7bXICRF4l
|
||||
WmmDR/rUtxnrMB8GA1UdIwQYMBaAFABX+UI7bXICRF4lWmmDR/rUtxnrMA8GA1Ud
|
||||
EwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIgCAeOYTKvA6SBB8xMdMdqNrp1
|
||||
20OPyi2BwFovW6vTCLMCIQC1qRi8SGRHTui8BVqIvp/DFJaZ/U8ocAg/qedLdy+R
|
||||
/w==
|
||||
-----END CERTIFICATE-----
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# generate-certs.sh — mint the unibus bus's self-signed CA and the NATS server
|
||||
# certificate. Run once on a trusted machine; distribute ca.crt to clients and
|
||||
# server.crt/server.key to the bus host (server.key by a secure channel, never
|
||||
# git). Re-running regenerates the server cert; pass --force to also regenerate
|
||||
# the CA (which invalidates every client that pinned the old ca.crt).
|
||||
#
|
||||
# SANs cover the public IP, the WireGuard IP, the om hostname, plus localhost so
|
||||
# the operator can smoke-test the TLS handshake on the box. Override via env:
|
||||
# UNIBUS_PUBLIC_IP (default 135.125.201.30)
|
||||
# UNIBUS_WG_IP (default 10.42.0.1)
|
||||
# UNIBUS_HOSTNAME (default om)
|
||||
#
|
||||
# Key material: EC P-256 (widely supported by Go's crypto/tls and nats-server).
|
||||
set -euo pipefail
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$DIR"
|
||||
|
||||
PUBLIC_IP="${UNIBUS_PUBLIC_IP:-135.125.201.30}"
|
||||
WG_IP="${UNIBUS_WG_IP:-10.42.0.1}"
|
||||
HOSTNAME_OM="${UNIBUS_HOSTNAME:-om}"
|
||||
DAYS_CA=3650
|
||||
DAYS_SRV=825
|
||||
|
||||
force=0
|
||||
[[ "${1:-}" == "--force" ]] && force=1
|
||||
|
||||
# --- CA (long-lived; only the cert is public) ---
|
||||
if [[ ! -f ca.crt || ! -f ca.key || $force -eq 1 ]]; then
|
||||
echo "==> generating CA"
|
||||
openssl ecparam -name prime256v1 -genkey -noout -out ca.key
|
||||
chmod 600 ca.key
|
||||
openssl req -x509 -new -key ca.key -sha256 -days "$DAYS_CA" \
|
||||
-subj "/CN=unibus-ca" -out ca.crt
|
||||
else
|
||||
echo "==> reusing existing CA (pass --force to regenerate)"
|
||||
fi
|
||||
|
||||
# --- server certificate, signed by the CA, with the bus SANs ---
|
||||
echo "==> generating server certificate (SAN: $PUBLIC_IP, $WG_IP, $HOSTNAME_OM, localhost, 127.0.0.1)"
|
||||
openssl ecparam -name prime256v1 -genkey -noout -out server.key
|
||||
chmod 600 server.key
|
||||
openssl req -new -key server.key -subj "/CN=unibus-bus" -out server.csr
|
||||
|
||||
cat > server.ext <<EOF
|
||||
subjectAltName=IP:${PUBLIC_IP},IP:${WG_IP},DNS:${HOSTNAME_OM},DNS:localhost,IP:127.0.0.1
|
||||
extendedKeyUsage=serverAuth
|
||||
keyUsage=digitalSignature,keyEncipherment
|
||||
EOF
|
||||
|
||||
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
|
||||
-sha256 -days "$DAYS_SRV" -extfile server.ext -out server.crt
|
||||
|
||||
rm -f server.csr server.ext ca.srl
|
||||
|
||||
echo "==> done:"
|
||||
echo " ca.crt -> embed/distribute to every client (public)"
|
||||
echo " server.crt -> deploy to the bus host"
|
||||
echo " server.key -> deploy to the bus host over a secure channel (NEVER git)"
|
||||
echo
|
||||
echo "verify SANs with:"
|
||||
echo " openssl x509 -in server.crt -noout -text | grep -A1 'Subject Alternative Name'"
|
||||
Reference in New Issue
Block a user