Skip to content

feat(gitops): sealed broker-realm secrets + CI guard + canonical insight client scope (Phase 1, boxes 2–3) - #2209

Merged
cyberantonz merged 5 commits into
constructorfabric:mainfrom
cyberantonz:feat/broker-realm-secrets-and-scope
Aug 5, 2026
Merged

feat(gitops): sealed broker-realm secrets + CI guard + canonical insight client scope (Phase 1, boxes 2–3)#2209
cyberantonz merged 5 commits into
constructorfabric:mainfrom
cyberantonz:feat/broker-realm-secrets-and-scope

Conversation

@cyberantonz

@cyberantonz cyberantonz commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What

The remaining two Phase-1 boxes of the broker rollout, delivered together (they are one coherent unit: the realm YAML, the Secret it references, and the guard that polices it):

Box 2 — secrets via SealedSecrets + CI guard

  • insight-keycloak-config sealed-secret shape template (environments/local/sealed-secrets/insight/…yaml.template) + inventory secrets.services entry (off by default). One Secret feeds the config-cli hook Job (keycloakConfig.existingSecret, feat(gitops): keycloak-config-cli sync Job — broker realms as code (Phase 1, box 1) #2204): the KEYCLOAK_USER/KEYCLOAK_PASSWORD login plus every $(env:VAR) placeholder the realm YAML references.
  • Secret hygiene: realm YAML only ever references secrets — env placeholders resolved at apply time from sealed Secrets. keycloakConfig.extraEnv (added here) lets placeholders resolve from other already-sealed Secrets via explicit secretKeyRef mappings, so a value shared with another consumer (e.g. the authenticator's client secret) has exactly one sealed source of truth and is never sealed twice — also the only way to consume Secret keys that are not valid env-var names, which envFrom drops silently. A bespoke realm-YAML lint was tried and dropped in review: the repository's standard secret scanning covers these files, and the authoring rules (placeholders only; no placeholder syntax in YAML comments — config-cli substitution scans comments and fails the import loudly at deploy time) are documented in the gitops README.

Box 3 — canonical insight client scope

  • environments/local/keycloak/realms/insight-broker.yaml — the canonical realm shape environments copy when onboarding: a clientScopes entry named insight emitting exactly email and the single-string tenant_id (DD-AUTH-04), and the insight-authenticator confidential client with fullScopeAllowed: false and exactly [basic, insight] assigned — the allow-list ADR-0003 mandates, so upstream-IdP claims cannot pass through unless a mapper re-emits them. (basic is the Keycloak 24+ built-in carrying the protocol-required sub/session claims; it emits no identity claims.) The tenant_id mapper shape matches the compose realm generator. The file is inert until an environment flips keycloakConfig.enabled (Phase 4).

Verification

  • Live claim-set proof on a scratch cluster: the realm applied through the feat(gitops): keycloak-config-cli sync Job — broker realms as code (Phase 1, box 1) #2204 hook Job (env-var substitution from the Secret shape above), then admin-API example-token evaluation for a probe user carrying both allow-listed attributes and a stray attribute: ID and access tokens contain exactly sub, email, tenant_id plus protocol claims — the stray attribute does not leak, and no profile-scope claims appear.
  • helm lint unaffected (no chart changes); pre-commit clean.

One finding for Phase 3 (#2196)

Keycloak 24+ declarative user profile drops unmanaged user attributes written via the admin REST API by default (unmanagedAttributePolicy unset) — a probe user created that way silently lost tenant_id until the scratch realm allowed unmanaged attributes. The production path is unaffected (identity-provider mappers stamp the attribute at first broker login, as the Phase-0 PoC proved end-to-end), but Phase 3 should decide whether realms declare tenant_id in the user profile explicitly rather than relying on that difference.

Refs #2195
Part of #2193

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a standard Keycloak broker realm configuration with limited client scopes and environment-based credentials.
    • Added a local sealed-secret template for securely configuring Keycloak credentials and redirect settings.
  • Documentation

    • Updated setup guidance for realm credentials, environment substitutions, and sealed-secret workflows.
  • Bug Fixes

    • Added validation that detects exposed credentials, invalid placeholders, and malformed realm configuration files before deployment.

…t client scope

Boxes 2+3 of constructorfabric#2195 (ADR-0003 broker realms as code):

- environments/local/keycloak/realms/insight-broker.yaml — the canonical
  realm shape environments copy: the `insight` client scope allow-lists
  exactly `email` + single-string `tenant_id` (plus protocol-level
  `sub`), and the insight-authenticator client runs with fullScopeAllowed
  off and exactly [basic, insight] assigned, so upstream-IdP claims die at
  the broker unless a mapper re-emits them. tenant_id mapper shape matches
  the compose realm generator. Inert until an env sets
  keycloakConfig.enabled (Phase 4).
- insight-keycloak-config sealed-secret shape template + inventory entry
  (off by default): one Secret carries the config-cli login and every
  env-var placeholder the realm YAML references.
- scripts/ci/realm_secret_guard.py + ci.yml job + pre-commit hook:
  credential fields in realm YAML must hold env placeholders (client
  secret, IdP clientSecret, passwords, bindCredential, credentials
  values); also rejects placeholder syntax in YAML comments, which fails
  the config-cli import at deploy time (Phase-0 finding). Unparseable
  YAML is an error, never a pass.

Verified live on a scratch cluster: realm applied by the hook Job; example
ID/access tokens for a probe user carry exactly sub+email+tenant_id and a
stray user attribute does not leak. Guard exercised on pass, literal-secret,
comment-placeholder, and unparseable-YAML inputs.

Refs constructorfabric#2195
Part of constructorfabric#2193

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>
@cyberantonz
cyberantonz requested a review from a team as a code owner August 5, 2026 04:07
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@cyberantonz, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 19 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: faea141f-1497-4cbe-9fb3-5728a69ffb21

📥 Commits

Reviewing files that changed from the base of the PR and between 50557fa and 1986a15.

📒 Files selected for processing (7)
  • charts/insight/templates/keycloak-config-job.yaml
  • charts/insight/values.yaml
  • deploy/gitops/Makefile
  • deploy/gitops/README.md
  • deploy/gitops/environments/local/inventory.yaml.template
  • deploy/gitops/environments/local/keycloak/realms/insight-broker.yaml
  • deploy/gitops/environments/local/sealed-secrets/insight/insight-keycloak-config-sealedsecret.yaml.template
📝 Walkthrough

Walkthrough

The PR adds a canonical Keycloak broker realm, sealed-secret configuration, and environment wiring. It adds a Python guard that rejects literal credentials and invalid placeholders, then runs the guard in CI and pre-commit.

Changes

Keycloak realm guard

Layer / File(s) Summary
Realm configuration and secret wiring
deploy/gitops/environments/local/keycloak/realms/insight-broker.yaml, deploy/gitops/environments/local/sealed-secrets/..., deploy/gitops/environments/local/inventory.yaml.template, deploy/gitops/README.md
Adds the insight-broker realm, its restricted client scope and authenticator client, sealed-secret template, inventory entry, and credential-handling documentation.
Realm secret validation
scripts/ci/realm_secret_guard.py
Recursively checks sensitive fields, accepts strict $(env:VAR) placeholders, rejects placeholders in comments, reports YAML errors, and returns a failure status when findings exist.
CI and pre-commit enforcement
.github/workflows/ci.yml, .pre-commit-config.yaml
Runs the guard for relevant pull requests and through a local pre-commit hook with PyYAML installed.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PullRequest
  participant GitHubActions
  participant PyYAML
  participant realm_secret_guard
  participant RealmYAML

  PullRequest->>GitHubActions: Change realm YAML
  GitHubActions->>PyYAML: Install parser
  GitHubActions->>realm_secret_guard: Run validation
  realm_secret_guard->>RealmYAML: Read and inspect
  RealmYAML-->>realm_secret_guard: YAML content
  realm_secret_guard-->>GitHubActions: Findings and exit status
Loading

Possibly related issues

Possibly related PRs

Suggested reviewers: ktursunov

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately identifies the sealed secrets, CI guard, and canonical client scope added by the pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@deploy/gitops/environments/local/keycloak/realms/insight-broker.yaml`:
- Around line 37-46: The email mapper currently reads from the user attribute
map instead of Keycloak’s built-in UserModel.email property. Update the mapper
named “email” to use oidc-usermodel-property-mapper while preserving its
existing claim name, token inclusion, and email configuration.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1dbbdc00-1173-4fb8-82b0-10b9bfbb6c36

📥 Commits

Reviewing files that changed from the base of the PR and between a3a2fb4 and 50557fa.

📒 Files selected for processing (7)
  • .github/workflows/ci.yml
  • .pre-commit-config.yaml
  • deploy/gitops/README.md
  • deploy/gitops/environments/local/inventory.yaml.template
  • deploy/gitops/environments/local/keycloak/realms/insight-broker.yaml
  • deploy/gitops/environments/local/sealed-secrets/insight/insight-keycloak-config-sealedsecret.yaml.template
  • scripts/ci/realm_secret_guard.py

cyberantonz and others added 4 commits August 5, 2026 12:21
…m other existing Secrets

Realm placeholders should reference already-sealed values (e.g. the
authenticator client secret both the realm and the authenticator config
must agree on) instead of sealing the same value twice. envFrom cannot do
this for keys that are not valid env-var names (it drops them silently —
hyphenated keys like oidc-client-secret), so extraEnv passes verbatim env
entries with secretKeyRef mappings into the config-cli container.

Refs constructorfabric#2195
Part of constructorfabric#2193

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>
Per review: the repository's standard secret scanning covers realm YAML
like any other file, and realm content only ever references secrets via
env placeholders resolved from sealed Secrets (keycloakConfig.extraEnv
for already-sealed values). The two authoring rules the script enforced
stay documented in the gitops README — including the config-cli
comment-substitution gotcha, which fails loudly at deploy time anyway.

Refs constructorfabric#2195
Part of constructorfabric#2193

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>
email is a built-in UserModel property; the property mapper is what the
stock `email` client scope uses. The attribute mapper worked on 26.4
(properties are unified into the attribute view there) but the property
mapper is the canonical, version-robust shape. Live claim-set probe
re-run: identical output (sub+email+tenant_id, stray attribute does not
leak). Addresses the CodeRabbit finding on constructorfabric#2209.

Refs constructorfabric#2195

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>
…im comments

Realm IdP mappers pin the tenant per environment from the existing
global.tenantDefaultId single source of truth (hardcoded-attribute-idp-
mapper, never an upstream claim — changing the IdP must not change the
tenant). Comments across the broker-realm files cut to short, exact notes.

Refs constructorfabric#2195
Part of constructorfabric#2193

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>
@cyberantonz
cyberantonz added this pull request to the merge queue Aug 5, 2026
Merged via the queue into constructorfabric:main with commit 007ba6f Aug 5, 2026
40 checks passed
@cyberantonz
cyberantonz deleted the feat/broker-realm-secrets-and-scope branch August 5, 2026 05:29
cyberantonz added a commit to cyberantonz/insight that referenced this pull request Aug 5, 2026
The broker Keycloak deploys with the stack instead of being an external
prerequisite (amends ADR-0002 Option A; ADR-0003 already allowed the
subchart mechanism). insight-keycloak gains mode=production: `start`,
MariaDB via the existing L2 (no second DBMS, per the Phase-4 DB note),
bootstrap admin from a Secret, health probes on the management port, no
realm import — realm content stays config-cli-as-code (constructorfabric#2204/constructorfabric#2209).
mariadb-init-svcdbs provisions the keycloak database.

Verified on a scratch cluster: boots against MariaDB (89 KC tables),
canonical realm applied via the hook Job, discovery 200, realm survives
pod restart.

Refs constructorfabric#2193 constructorfabric#2198

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>
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.

2 participants