feat(RAILIANCE-WP-0027): prepare operator-only OpenBao access
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02b90-83bf-75c2-81c8-aa705414e4d4
This commit is contained in:
codex 2026-08-23 12:50:23 +02:00
parent 082c76979e
commit 517f68593d
13 changed files with 413 additions and 82 deletions

View file

@ -32,6 +32,7 @@ OPENBAO_VALUES ?= helm/openbao-values.yaml
OPENBAO_MIDDLEWARE ?= helm/openbao-middleware.yaml
OPENBAO_UI_OVERLAY_DIR ?= helm/openbao-ui-overlay
OPENBAO_UI_OVERLAY_K8S ?= helm/openbao-ui-overlay-k8s.yaml
OPENBAO_PUBLIC_INGRESS_ROLLBACK ?= helm/openbao-public-ingress.rollback.yaml
OPENBAO_VERIFY_AUTH_ARGS ?=
OPENBAO_RESTORE_EVIDENCE ?= /tmp/netkingdom-openbao-restore-drill/evidence.json
OPENBAO_EMERGENCY_EVIDENCE ?= /tmp/netkingdom-openbao-emergency-drill/evidence.json
@ -215,7 +216,6 @@ openbao-verify-login-overlay: ## Verify public KeyCape login overlay is active
openbao-deploy: openbao-repo ## Deploy / upgrade OpenBao to the openbao namespace
$(KUBECTL) create namespace $(OPENBAO_NAMESPACE) --dry-run=client -o yaml | $(KUBECTL) apply -f -
$(KUBECTL) apply -f $(OPENBAO_MIDDLEWARE)
$(HELM) upgrade --install $(OPENBAO_RELEASE) openbao/openbao \
--version $(OPENBAO_CHART_VERSION) \
--namespace $(OPENBAO_NAMESPACE) \
@ -223,6 +223,29 @@ openbao-deploy: openbao-repo ## Deploy / upgrade OpenBao to the openbao namespac
--wait --timeout 5m
$(MAKE) openbao-overlay-apply
.PHONY: openbao-public-listener-dry-run openbao-public-listener-status openbao-public-listener-retract openbao-public-listener-rollback
openbao-public-listener-dry-run: ## Validate the guarded public-listener retraction without mutation
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
OPENBAO_UI_OVERLAY_K8S=$(OPENBAO_UI_OVERLAY_K8S) \
OPENBAO_PUBLIC_INGRESS_ROLLBACK=$(OPENBAO_PUBLIC_INGRESS_ROLLBACK) \
scripts/openbao-public-listener-transition.sh dry-run
openbao-public-listener-status: ## Report public Ingress and private tunnel readiness
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
scripts/openbao-public-listener-transition.sh status
openbao-public-listener-retract: ## Attended deletion after exact tunnel/login confirmation
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
OPENBAO_PUBLIC_INGRESS_ROLLBACK=$(OPENBAO_PUBLIC_INGRESS_ROLLBACK) \
scripts/openbao-public-listener-transition.sh apply
openbao-public-listener-rollback: ## Attended restoration of the prior public Ingress
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
OPENBAO_MIDDLEWARE=$(OPENBAO_MIDDLEWARE) \
OPENBAO_PUBLIC_INGRESS_ROLLBACK=$(OPENBAO_PUBLIC_INGRESS_ROLLBACK) \
scripts/openbao-public-listener-transition.sh rollback
openbao-status: ## Show OpenBao pods, services, PVCs, and seal/init status
$(KUBECTL) get pods,svc,pvc -n $(OPENBAO_NAMESPACE) \
-l app.kubernetes.io/instance=$(OPENBAO_RELEASE) -o wide

View file

@ -24,11 +24,12 @@
| workplan | RAILIANCE-WP-0023 | finished | — | workplans/RAILIANCE-WP-0023-hub-core-candidate-credential-lanes.md |
| workplan | RAILIANCE-WP-0024 | active | — | workplans/RAILIANCE-WP-0024-audit-core-custody-and-recovery-coordination.md |
| workplan | RAILIANCE-WP-0025 | finished | — | workplans/RAILIANCE-WP-0025-versioned-ephemeral-custody-lifecycle.md |
| workplan | RAILIANCE-WP-0026 | ready | — | workplans/RAILIANCE-WP-0026-attended-login-output-containment.md |
| workplan | RAILIANCE-WP-0026 | finished | — | workplans/RAILIANCE-WP-0026-attended-login-output-containment.md |
| workplan | RPF-WP-0018 | finished | — | workplans/RPF-WP-0018-policy-surface-alignment.md |
| workplan | RPF-WP-0019 | finished | — | workplans/RPF-WP-0019-apps-pg-recoverability-and-controls.md |
| workplan | RPF-WP-0020 | finished | — | workplans/RPF-WP-0020-ccr-schema-drift.md |
| workplan | RPF-WP-0021 | finished | — | workplans/RPF-WP-0021-core-hub-platform-onboarding.md |
| workplan | RAILIANCE-WP-0027 | blocked | — | workplans/RAILIANCE-WP-0027-openbao-operator-only-access.md |
| task | RAILIANCE-WP-0005-T01 | done | — | workplans/RAILIANCE-WP-0005-credential-request-and-lease-broker.md |
| task | RAILIANCE-WP-0005-T02 | done | — | workplans/RAILIANCE-WP-0005-credential-request-and-lease-broker.md |
| task | RAILIANCE-WP-0005-T03 | done | — | workplans/RAILIANCE-WP-0005-credential-request-and-lease-broker.md |
@ -109,7 +110,7 @@
| task | RAILIANCE-WP-0025-T04 | done | — | workplans/RAILIANCE-WP-0025-versioned-ephemeral-custody-lifecycle.md |
| task | RAILIANCE-WP-0025-T05 | done | — | workplans/RAILIANCE-WP-0025-versioned-ephemeral-custody-lifecycle.md |
| task | RAILIANCE-WP-0025-T06 | done | — | workplans/RAILIANCE-WP-0025-versioned-ephemeral-custody-lifecycle.md |
| task | RAILIANCE-WP-0026-T01 | todo | — | workplans/RAILIANCE-WP-0026-attended-login-output-containment.md |
| task | RAILIANCE-WP-0026-T01 | done | — | workplans/RAILIANCE-WP-0026-attended-login-output-containment.md |
| task | RPF-WP-0018-T01 | done | — | workplans/RPF-WP-0018-policy-surface-alignment.md |
| task | RPF-WP-0018-T02 | done | — | workplans/RPF-WP-0018-policy-surface-alignment.md |
| task | RPF-WP-0018-T03 | done | — | workplans/RPF-WP-0018-policy-surface-alignment.md |
@ -130,3 +131,6 @@
| task | RPF-WP-0021-T03 | done | — | workplans/RPF-WP-0021-core-hub-platform-onboarding.md |
| task | RPF-WP-0021-T04 | done | — | workplans/RPF-WP-0021-core-hub-platform-onboarding.md |
| task | RPF-WP-0021-T05 | done | — | workplans/RPF-WP-0021-core-hub-platform-onboarding.md |
| task | RAILIANCE-WP-0027-T01 | done | — | workplans/RAILIANCE-WP-0027-openbao-operator-only-access.md |
| task | RAILIANCE-WP-0027-T02 | done | — | workplans/RAILIANCE-WP-0027-openbao-operator-only-access.md |
| task | RAILIANCE-WP-0027-T03 | wait | — | workplans/RAILIANCE-WP-0027-openbao-operator-only-access.md |

View file

@ -0,0 +1,68 @@
# OpenBao public-listener transition
Status: source implementation complete; live retraction held.
This is the S3-owner procedure for `RMASTER-WP-0020-T09`. It preserves the
OpenBao Deployment, the `openbao-ui-gateway` Deployment, and its ClusterIP
Service while removing the public `bao.coulomb.social` Ingress.
## Target access
- Exposure posture: `operator`
- Named tunnel: `openbao-ui-railiance01`
- Local UI: `http://127.0.0.1:18200`
- Cluster target: `openbao/openbao-ui-gateway:8080`
- Public Ingress: absent after the transition
The tunnel is an ops-bridge local forward to the current ClusterIP and does not
open a node port. If the Service is recreated with a new ClusterIP, update and
revalidate the tunnel before use.
Browser OIDC derives its callback from the local origin. Before retraction,
both the KeyCape `openbao-admin` client and OpenBao
`auth/netkingdom/role/platform-admin` must allow exactly:
```text
http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback
```
Record only a value-safe attended-login result. Do not record the authorization
code, token, accessor, callback query, browser storage, or screenshots.
## Guarded sequence
```bash
make openbao-public-listener-dry-run
make openbao-public-listener-status
```
The status command deliberately fails if ops-bridge does not report the named
tunnel as lifecycle-healthy, even when an orphan forward still answers. Repair
that state before proceeding.
After KeyCape/OpenBao callback configuration and one attended MFA login through
the local URL, use the exact hold point:
```bash
OPENBAO_OPERATOR_LOGIN_VERIFIED=true \
OPENBAO_PUBLIC_LISTENER_CONFIRM='RETRACT RMASTER-WP-0020-T09 PUBLIC OPENBAO LISTENER' \
make openbao-public-listener-retract
```
The command pins the railiance01 cluster UID, rechecks the ClusterIP gateway and
tunnel, deletes only `openbao/openbao-ui-gateway` Ingress, then proves the
private path remains healthy and the public name no longer returns a successful
HTTP status.
## Rollback
During the bounded observation window:
```bash
OPENBAO_PUBLIC_LISTENER_CONFIRM='ROLLBACK RMASTER-WP-0020-T09 PUBLIC OPENBAO LISTENER' \
make openbao-public-listener-rollback
```
Rollback reapplies only the retained Traefik middleware and the explicitly
named rollback Ingress. It does not mutate OpenBao policy, Raft, PVCs, Secrets,
or the CoulombCore retention state.

View file

@ -29,8 +29,9 @@ S5 workloads / operators
-> audit storage PVC mounted at /openbao/audit
Platform operators with approved admin identity
-> https://bao.coulomb.social
-> Traefik Ingress + TLS
-> bridge up openbao-ui-railiance01
-> http://127.0.0.1:18200
-> SSH local forward to the ClusterIP
-> openbao-ui service
-> OpenBao UI/API
-> KeyCape OIDC at https://kc.coulomb.social for login
@ -39,7 +40,8 @@ Platform operators with approved admin identity
- OpenBao is the canonical Railiance S3 secrets service.
- SOPS/age remains the Git-at-rest bootstrap mechanism.
- The first Railiance01 deployment is single-replica Raft, not true HA.
- Browser UI/API exposure is declared for `https://bao.coulomb.social`.
- Browser UI exposure is operator-only through the named
`openbao-ui-railiance01` tunnel.
Operators authenticate through KeyCape/OIDC with MFA and the
`platform-admin` role. Do not use the root token through the browser UI.
- `kubectl exec` and port-forwarding remain valid break-glass/operator paths
@ -61,11 +63,11 @@ make openbao-deploy
make openbao-status
```
`make openbao-deploy` applies `helm/openbao-middleware.yaml` (Traefik
rate-limit and HSTS), upgrades the OpenBao Helm release, then applies the
KeyCape login overlay gateway (`helm/openbao-ui-overlay-k8s.yaml`). Public
ingress for `bao.coulomb.social` targets `openbao-ui-gateway`, not the chart
ingress (which stays disabled in `helm/openbao-values.yaml`).
`make openbao-deploy` upgrades the OpenBao Helm release, then applies the
KeyCape login overlay gateway (`helm/openbao-ui-overlay-k8s.yaml`) as a
ClusterIP-only surface. The chart ingress stays disabled. Public-listener
retraction and rollback are separate guarded commands documented in
`docs/openbao-public-listener-transition.md`.
On Railiance01 directly:
@ -107,8 +109,8 @@ Expected immediately after install:
- `openbao-0` is Running.
- `openbao`, `openbao-active`, `openbao-internal`, and `openbao-ui` services
exist as cluster-internal services.
- After DNS points at the cluster ingress, `https://bao.coulomb.social` serves
the OpenBao UI over valid TLS.
- With `openbao-ui-railiance01` healthy,
`http://127.0.0.1:18200` serves the operator UI.
- data and audit PVCs are Bound.
- `bao status` reports `Initialized: false` and `Sealed: true`.
@ -327,17 +329,17 @@ Initial auth model:
### Browser UI Login
The browser operator surface is:
The browser operator surface is the named local tunnel:
```text
https://bao.coulomb.social
http://127.0.0.1:18200
```
Operators see a streamlined **Sign in with KeyCape** mask. The raw OpenBao
fields (namespace, method, mount path, role) are hidden presets applied by the
UI overlay in `helm/openbao-ui-overlay/`. Public ingress targets the
`openbao-ui-gateway` nginx proxy, which injects overlay assets and forwards to
the OpenBao service.
Run `bridge up openbao-ui-railiance01` first. Operators then see a streamlined
**Sign in with KeyCape** mask. The raw OpenBao fields (namespace, method, mount
path, role) are hidden presets applied by the UI overlay in
`helm/openbao-ui-overlay/`. The ClusterIP-only `openbao-ui-gateway` nginx proxy
injects overlay assets and forwards to the OpenBao service.
Hidden defaults (also in `helm/openbao-ui-overlay/presets.json`):
@ -378,10 +380,10 @@ standalone page that exchanges the authorization code, stores the UI session
token, and redirects into the Ember app (no popup/`window.opener` flow).
The OpenBao UI redirects the browser to KeyCape at `kc.coulomb.social`, then
returns to:
returns through the same local tunnel to:
```text
https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback
http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback
```
The legacy `keycape` mount remains a compatibility alias for existing
@ -394,7 +396,7 @@ The browser callback URI must be present in both:
- OpenBao `auth/netkingdom/role/platform-admin` `allowed_redirect_uris`.
If the compatibility alias is kept enabled, also keep
`https://bao.coulomb.social/ui/vault/auth/keycape/oidc/callback` in the
`http://127.0.0.1:18200/ui/vault/auth/keycape/oidc/callback` in the
KeyCape client and `auth/keycape/role/platform-admin`.
Use the browser UI for metadata inspection and attended operator workflows.

View file

@ -0,0 +1,33 @@
# Rollback-only public listener for an attended RMASTER-WP-0020-T09 reversal.
# This file is never applied by the ordinary platform deployment path.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: openbao-ui-gateway
namespace: openbao
labels:
app.kubernetes.io/name: openbao-ui-gateway
app.kubernetes.io/part-of: railiance-platform
railiance-platform/component: secrets
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.middlewares: >-
openbao-openbao-rate-limit@kubernetescrd,
openbao-openbao-hsts@kubernetescrd
spec:
ingressClassName: traefik
tls:
- secretName: bao-tls
hosts:
- bao.coulomb.social
rules:
- host: bao.coulomb.social
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: openbao-ui-gateway
port:
number: 8080

View file

@ -1,6 +1,6 @@
# OpenBao browser UI gateway — injects the KeyCape login overlay and proxies
# to the OpenBao service. Public ingress for bao.coulomb.social targets this
# gateway instead of the chart-managed OpenBao ingress.
# to the OpenBao service. The gateway is ClusterIP-only and is reached through
# the named openbao-ui-railiance01 operator tunnel.
#
# ConfigMap data is applied by scripts/openbao-ui-overlay-apply.sh from
# helm/openbao-ui-overlay/*.
@ -87,35 +87,3 @@ spec:
port: 8080
targetPort: http
protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: openbao-ui-gateway
namespace: openbao
labels:
app.kubernetes.io/name: openbao-ui-gateway
app.kubernetes.io/part-of: railiance-platform
railiance-platform/component: secrets
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.middlewares: >-
openbao-openbao-rate-limit@kubernetescrd,
openbao-openbao-hsts@kubernetescrd
spec:
ingressClassName: traefik
tls:
- secretName: bao-tls
hosts:
- bao.coulomb.social
rules:
- host: bao.coulomb.social
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: openbao-ui-gateway
port:
number: 8080

View file

@ -1,8 +1,9 @@
# OpenBao KeyCape login overlay
Streamlines the browser login mask at `https://bao.coulomb.social` to a single
**Sign in with KeyCape** action. Namespace, auth method, mount path, and role
are preset in `presets.json` and hidden by `overlay.css` / `overlay.js`.
Streamlines the browser login mask reached through the named
`openbao-ui-railiance01` operator tunnel at `http://127.0.0.1:18200` to a
single **Sign in with KeyCape** action. Namespace, auth method, mount path, and
role are preset in `presets.json` and hidden by `overlay.css` / `overlay.js`.
## Mechanism (T01 decision)
@ -54,9 +55,9 @@ make openbao-verify-login-overlay
3. Fetch live UI assets and compare hashes:
```bash
curl -sS https://bao.coulomb.social/ui/ -o /tmp/index.html
curl -sS http://127.0.0.1:18200/ui/ -o /tmp/index.html
# locate vault-*.js path in /tmp/index.html, then:
curl -sS "https://bao.coulomb.social/ui/assets/vault-....js" -o /tmp/vault.js
curl -sS "http://127.0.0.1:18200/ui/assets/vault-....js" -o /tmp/vault.js
sha256sum /tmp/index.html /tmp/vault.js
```
@ -64,6 +65,9 @@ make openbao-verify-login-overlay
`overlay.css` / `overlay.js` selectors against the new Ember templates.
5. Write `patches/<new-version>/manifest.sha256`, update `VERSION`.
6. Run `make openbao-verify-login-overlay CHECK_UPSTREAM_DRIFT=1`.
7. Attended browser login through KeyCape MFA.
7. Attended browser login through KeyCape MFA. The OpenBao role and KeyCape
client must both allow the exact loopback callback
`http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback` before the
former public listener is retracted.
Workplan: `helix-forge/workplans/HF-WP-0003-openbao-keycape-login-overlay.md`
Workplan: `helix-forge/workplans/HF-WP-0003-openbao-keycape-login-overlay.md`

View file

@ -1,8 +1,8 @@
# OpenBao UI asset fingerprints for image tag 2.5.4.
# Regenerate after an OpenBao image bump when login markup drifts.
# Compare vault.js only — index.html is intentionally modified by the gateway.
# curl -sS https://bao.coulomb.social/ui/ -o /tmp/index.html
# curl -sS http://127.0.0.1:18200/ui/ -o /tmp/index.html
# vault_path=$(rg -o '/ui/assets/vault-[a-f0-9]+\\.js' /tmp/index.html | head -1)
# curl -sS "https://bao.coulomb.social${vault_path}" -o /tmp/vault.js
# curl -sS "http://127.0.0.1:18200${vault_path}" -o /tmp/vault.js
# sha256sum /tmp/vault.js
f0214b5be89377395f8d6521c34139877529bd95ba703901c78b527ab0f1c231 ui/assets/vault-bae6b876038fbf475728f993b5a62002.js
f0214b5be89377395f8d6521c34139877529bd95ba703901c78b527ab0f1c231 ui/assets/vault-bae6b876038fbf475728f993b5a62002.js

View file

@ -0,0 +1,153 @@
#!/usr/bin/env bash
set -euo pipefail
ACTION="${1:-status}"
OPENBAO_NAMESPACE="${OPENBAO_NAMESPACE:-openbao}"
KUBECTL="${KUBECTL:-kubectl}"
INGRESS_NAME="${OPENBAO_PUBLIC_INGRESS_NAME:-openbao-ui-gateway}"
SERVICE_NAME="${OPENBAO_UI_SERVICE_NAME:-openbao-ui-gateway}"
DEPLOYMENT_NAME="${OPENBAO_UI_DEPLOYMENT_NAME:-openbao-ui-gateway}"
TUNNEL_NAME="${OPENBAO_UI_TUNNEL_NAME:-openbao-ui-railiance01}"
TUNNEL_URL="${OPENBAO_UI_BASE_URL:-http://127.0.0.1:18200}"
PUBLIC_URL="${OPENBAO_PUBLIC_URL:-https://bao.coulomb.social/}"
EXPECTED_CLUSTER_UID="${RAILIANCE01_CLUSTER_UID:-a553c742-0115-43d4-99a4-a5ca56fe0786}"
PRIVATE_MANIFEST="${OPENBAO_UI_OVERLAY_K8S:-helm/openbao-ui-overlay-k8s.yaml}"
ROLLBACK_MANIFEST="${OPENBAO_PUBLIC_INGRESS_ROLLBACK:-helm/openbao-public-ingress.rollback.yaml}"
MIDDLEWARE_MANIFEST="${OPENBAO_MIDDLEWARE:-helm/openbao-middleware.yaml}"
CONFIRM_RETRACT="RETRACT RMASTER-WP-0020-T09 PUBLIC OPENBAO LISTENER"
CONFIRM_ROLLBACK="ROLLBACK RMASTER-WP-0020-T09 PUBLIC OPENBAO LISTENER"
usage() {
cat <<'USAGE'
Usage: scripts/openbao-public-listener-transition.sh status|dry-run|preflight|apply|rollback
`apply` requires both:
OPENBAO_OPERATOR_LOGIN_VERIFIED=true
OPENBAO_PUBLIC_LISTENER_CONFIRM='RETRACT RMASTER-WP-0020-T09 PUBLIC OPENBAO LISTENER'
`rollback` requires:
OPENBAO_PUBLIC_LISTENER_CONFIRM='ROLLBACK RMASTER-WP-0020-T09 PUBLIC OPENBAO LISTENER'
The script never reads OpenBao credentials or secret values.
USAGE
}
fail() { printf '[ERR] %s\n' "$*" >&2; exit 1; }
ok() { printf '[OK] %s\n' "$*"; }
kube() {
# KUBECTL may include an explicit --kubeconfig argument from Make.
# shellcheck disable=SC2086
$KUBECTL "$@"
}
require_cluster() {
local observed
observed="$(kube get namespace kube-system -o jsonpath='{.metadata.uid}')"
[ "$observed" = "$EXPECTED_CLUSTER_UID" ] ||
fail "cluster identity mismatch: expected $EXPECTED_CLUSTER_UID, observed $observed"
ok "railiance01 cluster identity pinned"
}
require_private_source() {
[ -f "$PRIVATE_MANIFEST" ] || fail "missing private gateway manifest: $PRIVATE_MANIFEST"
if grep -Eq '^kind:[[:space:]]*Ingress[[:space:]]*$' "$PRIVATE_MANIFEST"; then
fail "ordinary gateway manifest still contains an Ingress"
fi
ok "ordinary gateway manifest is Ingress-free"
}
require_private_runtime() {
local service_type service_port
service_type="$(kube -n "$OPENBAO_NAMESPACE" get service "$SERVICE_NAME" -o jsonpath='{.spec.type}')"
service_port="$(kube -n "$OPENBAO_NAMESPACE" get service "$SERVICE_NAME" -o jsonpath='{.spec.ports[?(@.name=="http")].port}')"
[ "$service_type" = "ClusterIP" ] || fail "gateway Service type is $service_type, not ClusterIP"
[ "$service_port" = "8080" ] || fail "gateway Service http port is $service_port, not 8080"
kube -n "$OPENBAO_NAMESPACE" rollout status "deployment/$DEPLOYMENT_NAME" --timeout=30s >/dev/null
ok "gateway Deployment Ready behind ClusterIP"
}
require_tunnel() {
command -v bridge >/dev/null || fail "bridge CLI not found"
command -v jq >/dev/null || fail "jq not found"
local report
if ! report="$(bridge check "$TUNNEL_NAME" --json 2>/dev/null)"; then
fail "named tunnel $TUNNEL_NAME is not lifecycle-healthy"
fi
jq -e --arg name "$TUNNEL_NAME" '.[] | select(.tunnel == $name and .ok == true)' \
<<<"$report" >/dev/null || fail "named tunnel $TUNNEL_NAME did not report ok=true"
curl -fsS --max-time 10 "$TUNNEL_URL/ui/platform-overlay/presets.json" >/dev/null ||
fail "operator UI is not reachable through $TUNNEL_NAME"
ok "named operator tunnel is healthy and reaches the UI"
}
status() {
require_cluster
require_private_source
require_private_runtime
if kube -n "$OPENBAO_NAMESPACE" get ingress "$INGRESS_NAME" >/dev/null 2>&1; then
printf '[INFO] public Ingress is present\n'
else
printf '[INFO] public Ingress is absent\n'
fi
require_tunnel
}
dry_run() {
require_cluster
require_private_source
[ -f "$ROLLBACK_MANIFEST" ] || fail "missing rollback manifest: $ROLLBACK_MANIFEST"
kube apply --server-side --dry-run=server -f "$PRIVATE_MANIFEST" >/dev/null
kube apply --server-side --dry-run=server -f "$ROLLBACK_MANIFEST" >/dev/null
kube -n "$OPENBAO_NAMESPACE" delete ingress "$INGRESS_NAME" --dry-run=server >/dev/null
ok "private apply, rollback apply, and Ingress deletion pass server dry-run"
}
preflight() {
status
kube -n "$OPENBAO_NAMESPACE" get ingress "$INGRESS_NAME" >/dev/null 2>&1 ||
fail "public Ingress is already absent; use status"
ok "public Ingress is present for attended transition"
}
apply_retraction() {
[ "${OPENBAO_OPERATOR_LOGIN_VERIFIED:-false}" = "true" ] ||
fail "attended operator login has not been explicitly verified"
[ "${OPENBAO_PUBLIC_LISTENER_CONFIRM:-}" = "$CONFIRM_RETRACT" ] ||
fail "exact retraction confirmation is missing"
preflight
kube -n "$OPENBAO_NAMESPACE" delete ingress "$INGRESS_NAME" --wait=true
require_private_runtime
require_tunnel
if kube -n "$OPENBAO_NAMESPACE" get ingress "$INGRESS_NAME" >/dev/null 2>&1; then
fail "public Ingress still exists after deletion"
fi
local code
code="$(curl -kLsS --max-time 10 -o /dev/null -w '%{http_code}' "$PUBLIC_URL" || true)"
case "$code" in
2??|3??) fail "public hostname still returns successful HTTP status $code" ;;
esac
ok "public Ingress absent; private gateway and named tunnel remain healthy"
}
rollback() {
[ "${OPENBAO_PUBLIC_LISTENER_CONFIRM:-}" = "$CONFIRM_ROLLBACK" ] ||
fail "exact rollback confirmation is missing"
require_cluster
[ -f "$MIDDLEWARE_MANIFEST" ] || fail "missing middleware manifest: $MIDDLEWARE_MANIFEST"
[ -f "$ROLLBACK_MANIFEST" ] || fail "missing rollback manifest: $ROLLBACK_MANIFEST"
kube apply -f "$MIDDLEWARE_MANIFEST"
kube apply -f "$ROLLBACK_MANIFEST"
kube -n "$OPENBAO_NAMESPACE" get ingress "$INGRESS_NAME" >/dev/null
ok "prior public Ingress restored from rollback-only manifest"
}
case "$ACTION" in
status) status ;;
dry-run) dry_run ;;
preflight) preflight ;;
apply) apply_retraction ;;
rollback) rollback ;;
-h|--help|help) usage ;;
*) usage >&2; exit 2 ;;
esac

View file

@ -11,8 +11,9 @@ usage() {
cat <<'USAGE'
Usage: scripts/openbao-ui-overlay-apply.sh
Builds and applies the OpenBao KeyCape login overlay ConfigMaps and gateway
Deployment/Service/Ingress. Idempotent — safe to run on every openbao-deploy.
Builds and applies the OpenBao KeyCape login overlay ConfigMaps and private
gateway Deployment/ClusterIP Service. Idempotent — safe on every deploy. It
does not create or delete a public Ingress.
Environment:
OPENBAO_NAMESPACE Kubernetes namespace. Default: openbao
@ -71,4 +72,4 @@ $KUBECTL rollout restart deployment/openbao-ui-gateway -n "$OPENBAO_NAMESPACE"
# shellcheck disable=SC2086
$KUBECTL rollout status deployment/openbao-ui-gateway -n "$OPENBAO_NAMESPACE" --timeout=120s
printf '[OK] OpenBao UI overlay applied from %s\n' "$OVERLAY_DIR"
printf '[OK] OpenBao UI overlay applied from %s\n' "$OVERLAY_DIR"

View file

@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
BASE_URL="${OPENBAO_UI_BASE_URL:-https://bao.coulomb.social}"
BASE_URL="${OPENBAO_UI_BASE_URL:-http://127.0.0.1:18200}"
OVERLAY_DIR="${OPENBAO_UI_OVERLAY_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/helm/openbao-ui-overlay}"
CHECK_DRIFT="${CHECK_UPSTREAM_DRIFT:-0}"
@ -13,11 +13,11 @@ usage() {
cat <<'USAGE'
Usage: scripts/openbao-verify-login-overlay.sh [--check-upstream-drift]
Verifies the public OpenBao UI serves the KeyCape login overlay assets and
that index.html injection is present.
Verifies the operator-tunneled OpenBao UI serves the KeyCape login overlay
assets and that index.html injection is present.
Environment:
OPENBAO_UI_BASE_URL Public UI base URL. Default: https://bao.coulomb.social
OPENBAO_UI_BASE_URL Operator UI base URL. Default: http://127.0.0.1:18200
OPENBAO_UI_OVERLAY_DIR Local overlay directory for drift fingerprints
CHECK_UPSTREAM_DRIFT Set to 1 to compare live UI hashes with patches/
USAGE
@ -170,4 +170,4 @@ if [ "$CHECK_DRIFT" = "1" ]; then
ok "vault bundle hash matches patches/$version/manifest.sha256 (${expected_vault_path:-$vault_asset})"
fi
printf '\nOpenBao login overlay verification passed for %s\n' "$BASE_URL"
printf '\nOpenBao login overlay verification passed for %s\n' "$BASE_URL"

View file

@ -4,7 +4,7 @@ type: workplan
title: "Contain attended OpenBao login output before authentication"
domain: financials
repo: railiance-platform
status: ready
status: finished
owner: codex
topic_slug: railiance
created: "2026-08-23"
@ -50,7 +50,7 @@ seal/unseal ceremony, or reuse of the terminal drill scenario.
```task
id: RAILIANCE-WP-0026-T01
status: todo
status: done
priority: high
```
@ -90,10 +90,19 @@ both reviewers, and `railiance-platform` accepts the exact revision. Completion
of this task still does not authorize a live drill; that requires fresh owner
receipts, current preflight, and a new human decision under a new scenario id.
Completed 2026-08-23. Railiance Platform accepts ops-warden revision
`0fae0904ce8d8694338dd53a8a79abec5fec788d`. The committed owner receipt at
ops-warden revision `bc1966da825fbeaa4d83947a4f942b5d5f085408` has SHA-256
`d2ba444ed16989590325697e69d25283dc75a9432c29a72e627e80bf9fd987e4`,
reports 42 focused tests and 390 repository tests passing, and records all
seven containment outcomes without sensitive material. Independent
railiance-infra review at revision `186b030` approves the same implementation.
This is source acceptance only and authorizes no live OIDC or drill.
## Acceptance
- [ ] The adapter refuses unsafe storage before OIDC.
- [ ] No underlying login output can reach the agent transcript.
- [ ] Any issued credential is contained and revoked on failure.
- [ ] Focused success and failure-path tests pass without credential material.
- [ ] Both direct reviewers receive and accept the value-safe fix receipt.
- [x] The adapter refuses unsafe storage before OIDC.
- [x] No underlying login output can reach the agent transcript.
- [x] Any issued credential is contained and revoked on failure.
- [x] Focused success and failure-path tests pass without credential material.
- [x] Both direct reviewers receive and accept the value-safe fix receipt.

View file

@ -0,0 +1,66 @@
---
id: RAILIANCE-WP-0027
type: workplan
title: "Retract public OpenBao listener behind operator-only access"
domain: financials
repo: railiance-platform
status: blocked
owner: codex
topic_slug: railiance
created: "2026-08-23"
updated: "2026-08-23"
related:
- RMASTER-WP-0020-T09
- RAPP-OPENBAO-WP-0002
---
# RAILIANCE-WP-0027 — OpenBao operator-only access
## Goal
Implement the S3-owner half of RMASTER-WP-0020-T09 without coupling it to
destructive CoulombCore cleanup.
## T01 — Align the retained compatibility source
```task
id: RAILIANCE-WP-0027-T01
status: done
priority: high
```
The retained platform manifest now matches the canonical package posture:
Deployment plus ClusterIP Service only. Ordinary deploy no longer applies the
public-only middleware. The old Ingress remains solely in an explicitly named
rollback artifact.
## T02 — Add guarded retraction and rollback
```task
id: RAILIANCE-WP-0027-T02
status: done
priority: high
```
`scripts/openbao-public-listener-transition.sh` pins the cluster UID, verifies
source and runtime packet posture, requires a lifecycle-healthy named tunnel,
and gates live deletion on exact confirmation plus attended-login verification.
It deletes only the Ingress and provides an exact rollback path.
## T03 — Complete the attended operator cutover
```task
id: RAILIANCE-WP-0027-T03
status: wait
priority: high
```
Blocked on two facts: KeyCape and the OpenBao role must accept the exact
loopback callback, and an attended MFA login must pass. The host-namespace
preflight already proves `openbao-ui-railiance01` lifecycle-healthy and reaches
the expected overlay. Then execute the guarded retraction, coordinate public
DNS withdrawal with railiance-infra, and return non-secret acceptance evidence
to Railiance Master.
This workplan authorizes no OpenBao seal/unseal, policy broadening, PVC or
Secret mutation, reboot, restore, or RMASTER-WP-0020-T08 cleanup.