Skip to content

feat(gr-foundry): migrate sentinel from gr-prod (Vault-backed config) - #129

Merged
BK1031 merged 6 commits into
mainfrom
bk1031/foundry-sentinel
Jul 11, 2026
Merged

feat(gr-foundry): migrate sentinel from gr-prod (Vault-backed config)#129
BK1031 merged 6 commits into
mainfrom
bk1031/foundry-sentinel

Conversation

@BK1031

@BK1031 BK1031 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Migrates sentinel from gr-prod (EKS) to gr-foundry (on-prem k3s). Follows the same pattern as the vault migration (#123), with one big shift: every sensitive AND non-sensitive config env value now comes from a `VaultSecretSync`-materialized k8s Secret, not baked into the Deployment YAMLs.

Why route config through Vault

  • Single source of truth — all rotatable values live in the Vault UI. No more hunting through YAML for a URL or client ID.
  • Auto pod rollout — vault-k8s-operator's `rolloutTargets` restarts every listed Deployment when any value changes. Rotate a secret in Vault → sentinel pods pick it up within `refreshInterval` + rollout time.
  • Removes the `kubectl create secret` dance — no more manual secret bootstrap per cluster.

What lives where

In Vault (`sentinel` app-secret, 19 fields, snake_case):

Sensitive:

  • `postgres_password`, `internal_bootstrap_secret`, `kerbecs_password`
  • `discord_token`, `discord_client_secret`, `google_service_account`

Config (previously plain env):

  • `database_host`, `database_user`, `database_name`
  • `issuer`
  • `kerbecs_endpoint`, `kerbecs_user`
  • `discord_client_id`, `discord_redirect_uri`, `discord_guild`, `discord_prefix`
  • `web_base_url`
  • `team_google_client_id`
  • `google_admin_subject`

Plain in the Deployment manifests (truly structural, never changes without a code change):

  • `ENV=PROD`
  • `PORT=` (must match `containerPort`)
  • `DATABASE_PORT=5432`
  • `KERBECS_CONFIG=/etc/kerbecs/kerbecs.yaml` (mount path)

Data persistence

Sentinel state (users, OAuth apps, SAML signing key, JWKS row) lives in the `sentinel` DB on gr-postgres. Same DB, same signing key → tokens minted by the new pod validate against the same JWKS. Every downstream RP that trusts `https://sentinel-v5.gauchoracing.com\` keeps working.

Pre-merge bootstrap

Populate the `sentinel` app-secret in Vault UI (https://vault.gauchoracing.com) with all 19 fields. Sensitive values → extract from gr-prod's Secret:

```bash
kubectl --context gr-prod -n sentinel get secret sentinel-secrets -o json \
| jq -r '.data | to_entries[] | "\(.key | ascii_downcase) = \(.value|@base64d)"'
```

Config values → read straight off the current gr-prod Deployments (or just enter the known constants: `issuer=https://sentinel-v5.gauchoracing.com\`, `database_host=postgres.sentinel.svc.cluster.local`, etc.).

Cutover order

  1. Populate the Vault `sentinel` app-secret (all 19 fields).
  2. Merge PR.
  3. gr-prod ArgoCD prunes sentinel Application → workloads torn down on gr-prod (~30s).
  4. gr-foundry ArgoCD creates sentinel Application + VaultSecretSync.
  5. vault-k8s-operator fetches from Vault → materializes `sentinel-secrets` k8s Secret.
  6. Sentinel pods come up on gr-foundry with all envs populated.
  7. External-dns writes new `sentinel-v5.gauchoracing.com` CNAME → tunnel.
  8. Traffic on gr-foundry within CF TTL.

Total downtime for `sentinel-v5.gauchoracing.com`: ~2-4 min from merge.

Test plan

  • Vault `sentinel` app-secret populated with all 19 fields before merge.
  • Post-merge: foundry `kubectl -n sentinel get pods` shows all six services Running.
  • `kubectl -n sentinel get secret sentinel-secrets` shows 19 keys.
  • `kubectl port-forward -n sentinel svc/kerbecs 10310:10310` + curl `-H 'Host: sentinel-v5.gauchoracing.com' http://localhost:10310/api/core/keys\` returns a JWKS matching what gr-prod was returning.
  • After DNS flip: `https://sentinel-v5.gauchoracing.com\` login flow works (Discord + Google), token issuance succeeds, `iss` claim unchanged.
  • Rotate a value in the Vault UI (e.g., `discord_prefix` to some throwaway value, then back) — observe the sentinel Deployments get restarted by vault-k8s-operator on the next reconcile.

… Vault

Same migration recipe as vault (#123): copy manifests with Traefik +
public-hostname adjustments, add ArgoCD Application on the gr-foundry
tree, remove from gr-prod. Same shared gr-postgres DB — sentinel state
(users, OAuth apps, SAML signing keys, JWKS) persists across the move.

Difference from vault: every sensitive AND non-sensitive config env
value comes from a VaultSecretSync-materialized k8s Secret rather than
being baked into the Deployment YAMLs. Rationale: single source of
truth for both secret rotations and hostname/ID changes, with
vault-k8s-operator auto-rolling the Deployments listed in
rolloutTargets on any value change.

What's in Vault (`sentinel` app-secret, 19 fields):

  Sensitive
  - postgres_password
  - internal_bootstrap_secret
  - kerbecs_password
  - discord_token
  - discord_client_secret
  - google_service_account

  Config (previously plain env)
  - database_host / database_user / database_name
  - issuer
  - kerbecs_endpoint / kerbecs_user
  - discord_client_id / discord_redirect_uri / discord_guild / discord_prefix
  - web_base_url
  - team_google_client_id
  - google_admin_subject

What stays plain in the Deployment manifests (truly structural):

  - ENV=PROD
  - PORT=<service-specific, must match containerPort>
  - DATABASE_PORT=5432
  - KERBECS_CONFIG=/etc/kerbecs/kerbecs.yaml

Pre-merge bootstrap: populate the `sentinel` app-secret in the Vault UI
at https://vault.gauchoracing.com with all 19 fields. Values come from
the gr-prod sentinel-secrets Secret + the current plain env values on
the gr-prod Deployments. Snippet to extract the sensitive half:

  kubectl --context gr-prod -n sentinel get secret sentinel-secrets -o json \
    | jq -r '.data | to_entries[] | "\(.key | ascii_downcase) = \(.value|@base64d)"'

Post-merge:
  1. gr-prod ArgoCD prunes sentinel Application → workloads down on gr-prod.
  2. gr-foundry ArgoCD creates the Application + VaultSecretSync.
  3. Operator fetches from Vault → materializes sentinel-secrets → pods start.
  4. External-dns updates sentinel-v5.gauchoracing.com CNAME → tunnel.
  5. Traffic on gr-foundry.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review


P1 Badge Include VaultSecretSync in kustomization resources

Because this directory has a Kustomization, ArgoCD builds only the files listed under resources; the newly added vaultsecretsync.yaml is not included here. On a fresh gr-foundry sync the VaultSecretSync CR is never applied, so sentinel-secrets is never materialized and every Deployment using envFrom: sentinel-secrets will fail to create pods unless someone manually creates that Secret. Add vaultsecretsync.yaml to this resources list.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Codex caught this on PR #129: the kustomization.yaml resources list was
missing vaultsecretsync.yaml, so kustomize would silently skip it on
ArgoCD sync. Without the VaultSecretSync applied, sentinel-secrets never
materializes and every Deployment's `envFrom: sentinel-secrets` fails at
pod creation.

Root cause: I `cp`'d the file from gr-prod (which never had a
VaultSecretSync) and my subsequent Write to update the resources list
silently errored out mid-batch — Write requires a Read on existing files
first, and I hadn't Read this one post-cp. The Write's failure got
buried in a stream of successful ones.
@BK1031

BK1031 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Good catch @chatgpt-codex-connector — confirmed the bug and pushed a fix in the next commit. The kustomization.yaml was a leftover from the gr-prod cp that never got updated (an earlier Write to overwrite it silently errored mid-batch). vaultsecretsync.yaml is now in the resources list.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create an environment for this repo.

Two coupled changes that grew out of populating the Vault app-secret:

1. Public hostname changes from sentinel-v5.gauchoracing.com to
   sso.gauchoracing.com. The Ingress on gr-foundry now serves sso, and
   every downstream OIDC RP has its SENTINEL_URL updated to match:
     - gr-foundry/manifests/vault/server.yaml
     - gr-prod/manifests/mapache/{auth,query}.yaml

   ISSUER itself is now Vault-managed (see the vaultsecretsync); the
   Vault value has already been set to https://sso.gauchoracing.com,
   which is what future tokens will claim in `iss`. Existing tokens
   with iss=https://sentinel-v5.gauchoracing.com become invalid on
   cutover — every user re-authenticates once.

2. VaultSecretSync references updated from `sentinel.<field>` to
   `sentinel-prod.<field>` to match the actual Vault app-secret name
   we're using. Convention matches other app-secrets and leaves room
   for future sentinel-dev / sentinel-staging alongside prod.
@BK1031

BK1031 commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

Scope expanded a bit — the Vault values populated by @BK1031 point at sso.gauchoracing.com, so this PR now also:

  • Renames the sentinel public hostname from sentinel-v5.gauchoracing.comsso.gauchoracing.com on the gr-foundry Ingress
  • Updates every downstream OIDC RP's SENTINEL_URL to match (gr-foundry vault-server, gr-prod mapache-auth, gr-prod mapache-query)
  • Corrects the VaultSecretSync paths to sentinel-prod.<field> (matches the app-secret name @BK1031 created)

Two things to know for cutover:

  1. Every existing user session becomes invalid — tokens carry iss: https://sentinel-v5.gauchoracing.com which won't match the new iss: https://sso.gauchoracing.com. Users log in once against the new URL.
  2. Discord + Google OAuth apps need https://sso.gauchoracing.com/auth/login/discord (and the equivalent Google) added as allowed redirect URIs in their respective dev portals — otherwise the OAuth handshake fails.

BK1031 added 3 commits July 10, 2026 21:41
…TABASE_NAME

Two shifts to the config sourcing:

1. DATABASE_HOST/PORT/USER + POSTGRES_PASSWORD now come from the
   shared `gr-postgres` Vault app-secret rather than being duplicated
   in `sentinel-prod`. One place to rotate the postgres password —
   every consumer's Deployment restarts automatically via
   vault-k8s-operator's rolloutTargets.

   Follow-up: mapache-secrets and vault-secrets on gr-foundry could
   move to the same pattern in later PRs.

2. DATABASE_NAME is not in Vault anymore. It's a per-app structural
   constant that matches the app name and never rotates — hardcoded
   as plain env on each Deployment, matching how vault-server
   (`DATABASE_NAME: vault`) and mapache (`DATABASE_NAME: mapache`)
   already do it. Frees up one Vault field per service.

Also drop the plain `DATABASE_PORT: "5432"` overrides on every
sentinel Deployment now that the port comes from gr-postgres via
envFrom.

Fields deleted from `sentinel-prod` in Vault after this lands (optional
cleanup): database_host, database_user, database_name, postgres_password.
The postgres.sentinel.svc.cluster.local ExternalName pointed at
gr-postgres.gauchoracing.com so Deployments could dial a
namespace-local DNS name. With DATABASE_HOST now sourced from the
gr-postgres Vault app-secret (currently gr-hamilton.gauchoracing.com),
Deployments dial the postgres server directly and the Service shim
is unused. Removing it.

Also update kustomization.yaml to drop postgres.yaml from resources.
Forgot to include this in the previous commit — the file was removed
from disk but kustomize still tried to include it, which would break
the ArgoCD sync.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant