64 lines
3.5 KiB
Markdown
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.
|