Files
ModelForge-Public/docs/operations/VALIDATION_TLS_TEST_PORT.md

64 lines
3.5 KiB
Markdown

# Candidate HTTPS on the canonical hostname
Port 443 is reserved for the existing production stack. A retained, isolated
candidate uses `https://modelforge.example.com:18444`. The two ports never share
a proxy route. Before configuring port 443, verify that the existing production
Console uses the HTTPS API origin and matching CSP through a separately authorized
production release.
The candidate's `tls` service belongs to its own Compose project and only joins that
project's `ingress` network. It sends `/api/*` and `/v1/*` to that project's API and
everything else to that project's Web service.
Use a valid Nginx Proxy Manager certificate for the canonical hostname. Its key
must not be mounted directly from NPM or copied into the source tree or image
build context. For each retained
candidate, stage only `fullchain.pem` and `privkey.pem` under a root-owned `tls`
directory inside that candidate's private root but outside its `source` directory.
After choosing an exact candidate root and certificate directory:
```sh
candidate_root=/path/to/private/modelforge-validation/EXACT_PROJECT
npm_live_dir=/path/to/npm/letsencrypt/live/CERTIFICATE_ID
tls_dir="${candidate_root}/tls"
install -d -m 0700 "${candidate_root}"
install -d -m 0700 "${tls_dir}"
install -m 0600 "${npm_live_dir}/fullchain.pem" "${tls_dir}/fullchain.pem"
install -m 0600 "${npm_live_dir}/privkey.pem" "${tls_dir}/privkey.pem"
openssl x509 -in "${tls_dir}/fullchain.pem" -noout -checkend 86400
openssl x509 -in "${tls_dir}/fullchain.pem" -noout -ext subjectAltName
test "$(openssl x509 -in "${tls_dir}/fullchain.pem" -pubkey -noout | sha256sum | cut -d' ' -f1)" = \
"$(openssl pkey -in "${tls_dir}/privkey.pem" -pubout | sha256sum | cut -d' ' -f1)"
```
Confirm the SAN contains exactly the intended hostname and the public keys match.
Set `MODELFORGE_CANDIDATE_TLS_DIR` to this absolute `tls_dir`, and build the Web
image with `VITE_API_BASE_URL=https://modelforge.example.com:18444`. The API's
`MODELFORGE_CORS_ORIGINS` must be the same origin. Use the base, production,
`docker-compose.rc-ingress.yml`, and `docker-compose.validation-tls.yml` overlays
in that order. The `rc` in the historical overlay filename is not a runtime name.
The Compose project must use the neutral `modelforge-validation-*` namespace.
Before `up`, render the exact Compose JSON and pass it through
`scripts/rc_isolation_preflight.py` with `--public-tls-port 18444`, candidate root,
protected production ports and resource names. The preflight admits this one
public port only for the isolated `tls` service; all other published ports must
remain loopback only. Run the observed-resource preflight again after `up`.
Scan the exact pinned Caddy image ID with the release Trivy severity policy and
retain its report alongside the four application-image scans. Do not start the
candidate while the host resource guard is active.
Verify the hostname and certificate without `-k` or `--insecure`:
```sh
server_ip=SERVER_LAN_IP
curl --fail --show-error --resolve "modelforge.example.com:18444:${server_ip}" \
https://modelforge.example.com:18444/api/v1/health/live
```
Verify the Console, its browser API requests, CSP and authentication against the
same origin. Record the exact image IDs, source SHA, certificate expiry, and before
and after production health. The staged key is per candidate and must be removed
with its candidate root only after retained evidence and cleanup checks pass.
If NPM renews the certificate while a candidate remains live, copy the new pair
and restart only that candidate's `tls` service after checking the SAN and key.