Skip to content

feat(security): credential binding enforcement#1671

Merged
mergify[bot] merged 10 commits into
mainfrom
jsell/credential-rbac-enforcement
Jun 9, 2026
Merged

feat(security): credential binding enforcement#1671
mergify[bot] merged 10 commits into
mainfrom
jsell/credential-rbac-enforcement

Conversation

@jsell-rh

@jsell-rh jsell-rh commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the credential binding enforcement spec (specs/security/credential-binding-enforcement.spec.md):

  • Hierarchical credential resolution in the control plane: agent → project → global binding precedence with earliest-created-at tiebreaker
  • Authorization rules for binding/unbinding credentials: credential:owner required to bind, project:editor+ required for project-level, platform:admin for global, asymmetric unbind (editors can remove without credential ownership)
  • Agent validation: agent_id requires project_id, agent must belong to specified project
  • credential:token-reader lifecycle: grants created at session provision, revoked at termination
  • Security hardening from 10-agent security audit: scope enum validation, empty-string FK rejection, PATCH nil-deref fix, DB error redaction, TSL injection prevention, legacy cross-tenant fallback removed

Files changed

Component Files What
API server plugins/roleBindings/handler.go Create/Delete/Patch authorization rewrite
API server pkg/rbac/hierarchy.go credential:token-reader in RoleLevel
API server plugins/roles/migration.go, plugin.go role_binding:delete for project:editor
Control plane reconciler/kube_reconciler.go Hierarchical resolver + token-reader lifecycle
Control plane config/config.go, main.go ServiceIdentity config

Test plan

  • 10 Go integration tests (testcontainers) — all pass
  • 7 resolver unit tests (mock HTTP server) — all pass
  • 173 e2e tests (rbac_e2e_test.sh on Kind cluster) — all pass
  • Phase 26 added: 12 credential binding e2e scenarios
  • 10-agent security audit: 5 blockers + 4 criticals identified and fixed
  • Reviewer: verify no regressions in existing RBAC behavior

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Credential binding enforcement: Role-based access control for binding credentials to projects and agents, with asymmetric permissions for creation and deletion.
    • Automatic token-reader bindings: Sessions now automatically receive token-reader role bindings for resolved credentials.
    • Hierarchical credential resolution: Credentials now resolve through a three-tier system (agent-level → project-level → global).
  • Improvements

    • Project editors can now delete credential bindings without requiring credential ownership.
    • Enhanced validation for credential-scoped bindings with stricter scope and role checks.
    • Global credential bindings now restricted to platform administrators.

jsell-rh and others added 8 commits June 5, 2026 13:36
Hierarchical credential binding authorization per credential-binding-enforcement spec:
- Relax project check from project:owner to project:editor+ for credential bindings
- Add agent-level validation (agent_id requires project_id, agent must belong to project)
- Global credential bindings require platform:admin
- Allow platform:admin to create internal role bindings (credential:token-reader)
- Asymmetric unbind: project:editor can remove credential bindings without credential:owner
- Non-credential delete restricted to project:owner/platform:admin
- Scope enum validation rejects invalid scope values
- Empty-string FK validation prevents invalid DB state
- Nil-guard all nullable field dereferences in PATCH handler
- Replace raw DB error messages with generic authorization failure responses
- Row-level locking (SELECT FOR UPDATE) on last-owner count queries
- Add credential:token-reader to RoleLevel hierarchy map

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Migration adds role_binding:delete permission to project:editor so the
RBAC middleware allows editors to reach the Delete handler for credential
binding asymmetric unbind. The handler's secondary authorization restricts
editors to credential-scope bindings only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lifecycle

Replace legacy resolveCredentialIDs (which listed ALL credentials) with
hierarchical binding resolution: agent → project → global precedence.
Earliest created_at wins for same-provider duplicates at the same tier.

Add credential:token-reader grant lifecycle:
- grantTokenReaderBindings creates scoped bindings at session provision
- revokeTokenReaderBindings cleans up at deprovision/cleanup with error surfacing
- ServiceIdentity config field from GRPC_SERVICE_ACCOUNT env var

Security hardening:
- TSL injection prevention via validateTSLValue regex before all fmt.Sprintf
- Remove legacy fallback that leaked credentials across tenants
- Revocation errors logged at Error level instead of silently swallowed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integration tests (Go, testcontainers):
- 10 tests covering Create handler authorization scenarios
- 7 tests for hierarchical credential resolver with mock API server
- Tests cover: editor bind, viewer reject, agent validation, global
  binding, asymmetric unbind, internal role creation, agent-project check

E2e tests (bash, Kind cluster):
- Phase 26 added to rbac_e2e_test.sh with 12 credential binding scenarios
- Tests real Keycloak JWT auth against running API server
- Covers bind, unbind, agent validation, global binding, internal roles

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jsell-rh jsell-rh added the ambient-code:self-reviewed Self-reviewed by Ambient agent label Jun 9, 2026
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@jsell-rh, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 16 minutes and 45 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

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.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 9429f3ba-32da-4ae5-9e44-af8150663e6f

📥 Commits

Reviewing files that changed from the base of the PR and between 4cad7b1 and 57aae13.

📒 Files selected for processing (2)
  • components/ambient-control-plane/internal/config/config.go
  • components/ambient-control-plane/internal/reconciler/kube_reconciler.go
📝 Walkthrough

Walkthrough

This PR implements credential binding enforcement across the API and control plane. It adds credential-scoped role authorization rules (create/patch/delete), enables credential resolution via hierarchical role bindings instead of simple listing, manages credential:token-reader bindings during session provisioning, and includes integration and e2e tests.

Changes

Credential Binding Enforcement

Layer / File(s) Summary
Role Hierarchy & Permissions Migration
components/ambient-api-server/pkg/rbac/hierarchy.go, components/ambient-api-server/plugins/roles/migration.go, components/ambient-api-server/plugins/roles/plugin.go, components/ambient-api-server/test/integration/rbac_test.go
RoleLevel expanded to include credential-scoped role variants at level 2; new migration grants role_binding:delete permission to project:editor; test fixtures updated to seed the new permission.
API Credential Binding Authorization & Validation
components/ambient-api-server/plugins/roleBindings/handler.go
Handler Create/Patch/Delete enforce credential-scoped binding rules: scope validation, deferred internal-role blocking, and asymmetric unbind where project:editor can delete credential bindings without being credential owner; credential:owner required to create, project:editor+ for project-scoped, platform:admin for global.
API Integration Tests
components/ambient-api-server/test/integration/credential_binding_test.go, components/ambient-api-server/test/integration/integration_test.go
Comprehensive integration tests (9 cases) for credential binding create/delete authorization: project editor vs viewer, agent/project validation, global admin requirement, ownership enforcement, internal role restrictions, agent-level bindings; test plugins configured to include agents and credentials.
Control Plane Configuration & ServiceIdentity
components/ambient-control-plane/internal/config/config.go, components/ambient-control-plane/cmd/ambient-control-plane/main.go, components/ambient-control-plane/internal/reconciler/kube_reconciler.go
ControlPlaneConfig adds ServiceIdentity field (loaded from GRPC_SERVICE_ACCOUNT env); KubeReconcilerConfig receives and uses it for token-reader binding creation.
Credential Resolution via Role Bindings
components/ambient-control-plane/internal/reconciler/kube_reconciler.go
New resolveCredentialIDs queries agent/project/global credential bindings with hierarchical tier precedence; earlier-created bindings win per scope; agent tier overrides project and global; replaces simple credential listing.
Token-Reader Binding Management
components/ambient-control-plane/internal/reconciler/kube_reconciler.go
New helpers: grantTokenReaderBindings creates credential:token-reader bindings during session provisioning; revokeTokenReaderBindings deletes them during deprovisioning/cleanup (best-effort, errors logged).
Credential Resolver Tests
components/ambient-control-plane/internal/reconciler/credential_resolver_test.go
Test suite with mock HTTP server and 7 cases validating agent/project/global precedence, empty bindings, multi-provider aggregation, earliest-binding selection, and agent-over-global override.
E2E Phase 26 Tests
components/ambient-api-server/test/e2e/rbac_e2e_test.sh
New phase with 10 scenarios: project editor can bind/unbind credentials; project viewer cannot; agent/project validation; global requires admin; ownership enforcement; internal role restriction; phase cleanup.

Possibly related PRs

Suggested labels

queued


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (2 errors, 1 warning)

Check name Status Explanation Resolution
Performance And Algorithmic Complexity ❌ Error N+1 pattern in resolveCredentialIDs: global credential bindings list fetches all system bindings (ListAll Size:100), then calls Get per unique credential ID during session provisioning. Batch credential lookups: use List() with credential ID filter or implement early-exit pagination for global bindings to avoid full-system list.
Security And Secret Handling ❌ Error ServiceIdentity not normalized with TrimSpace (config.go:95); partial credential grant/revoke failures swallowed without propagating errors, risking orphaned or unauthorized bindings. Normalize ServiceIdentity with strings.TrimSpace; propagate grant/revoke errors to ensure failed bindings halt provisioning or trigger reconciliation retry.
Docstring Coverage ⚠️ Warning Docstring coverage is 27.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title follows Conventional Commits format (feat(security): description) and accurately describes the core feature implementation of credential binding enforcement.
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.
Kubernetes Resource Safety ✅ Passed All containers have CPU/memory limits, security contexts set (nonRoot, no privilege escalation), namespace scoping, and RBAC lacks wildcards.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jsell/credential-rbac-enforcement
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch jsell/credential-rbac-enforcement

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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
components/ambient-api-server/plugins/roleBindings/handler.go (1)

246-283: 💤 Low value

Consider extracting scope FK validation to reduce duplication.

The pattern for preventing scope FK changes is repeated 4 times (ProjectId, AgentId, SessionId, CredentialId) with identical logic. Consider extracting to a helper function.

However, the current explicit approach is clear and low-risk, so this is optional.

🤖 Prompt for 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.

In `@components/ambient-api-server/plugins/roleBindings/handler.go` around lines
246 - 283, The repeated "prevent scope widening" checks for ProjectId, AgentId,
SessionId, and CredentialId duplicate the same logic; extract that logic into a
helper like validateNoScopeFKChange(callerLevel int, isSet func() bool, get
func() *T, foundVal *T) (or a typed variant) and call it for each field from the
patch handling block so the pattern in handler.go (the callerLevel != 0 branch
that checks patch.<Field>.IsSet(), patchVal := patch.<Field>.Get(),
found.<Field> == nil || patchVal == nil || *patchVal != *found.<Field>) is
centralized and reused for ProjectId, AgentId, SessionId, and CredentialId
validation.
🤖 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 `@components/ambient-control-plane/internal/config/config.go`:
- Line 95: The ServiceIdentity field in config.go is set from
os.Getenv("GRPC_SERVICE_ACCOUNT") without normalization; update the config
initialization to mirror the middleware by trimming whitespace/newlines so the
service identity matches the API's caller context. Locate the ServiceIdentity
assignment in the config construction (the ServiceIdentity symbol) and replace
the raw env read with a normalized value (e.g., strings.TrimSpace of the env
var) so trailing spaces/newlines do not create mismatched user_id values.

In `@components/ambient-control-plane/internal/reconciler/kube_reconciler.go`:
- Around line 206-208: The call to r.grantTokenReaderBindings currently swallows
partial failures and only logs a top-level error, which can cause ensurePod to
receive credentialIDs the identity wasn't granted; update
grantTokenReaderBindings (and other similar usages) to either return a non-nil
error on any individual grant failure or return a filtered map/slice of
credential IDs that were actually bound successfully, and then pass that
filtered set into ensurePod instead of the original credentialIDs (refer to
r.grantTokenReaderBindings, credentialIDs, session.ID, and ensurePod to locate
the callsite and implementation). Ensure the chosen approach propagates errors
or explicitly collects and returns successful IDs so no partial failures are
silently ignored.
- Around line 228-235: The revokeTokenReaderBindings error is being logged and
swallowed in the session cleanup path; change the logic in the block that calls
r.factory.ForProject(ctx, session.ProjectID) and
r.revokeTokenReaderBindings(ctx, sdk, session.ID) so that if
revokeTokenReaderBindings returns an error you return it (or wrap and return it)
instead of only logging, ensuring the reconciler requeues; do the same for the
second occurrence around r.revokeTokenReaderBindings at the later branch. Locate
the calls to r.factory.ForProject and r.revokeTokenReaderBindings in
kube_reconciler.go and replace the logging-only error handling with propagation
(return err or fmt.Errorf/contextual wrap) so partial revocation failures are
not silently ignored.

---

Nitpick comments:
In `@components/ambient-api-server/plugins/roleBindings/handler.go`:
- Around line 246-283: The repeated "prevent scope widening" checks for
ProjectId, AgentId, SessionId, and CredentialId duplicate the same logic;
extract that logic into a helper like validateNoScopeFKChange(callerLevel int,
isSet func() bool, get func() *T, foundVal *T) (or a typed variant) and call it
for each field from the patch handling block so the pattern in handler.go (the
callerLevel != 0 branch that checks patch.<Field>.IsSet(), patchVal :=
patch.<Field>.Get(), found.<Field> == nil || patchVal == nil || *patchVal !=
*found.<Field>) is centralized and reused for ProjectId, AgentId, SessionId, and
CredentialId validation.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 5268f141-74a2-4b45-8e61-da3138460711

📥 Commits

Reviewing files that changed from the base of the PR and between c5de4fc and 4cad7b1.

📒 Files selected for processing (12)
  • components/ambient-api-server/pkg/rbac/hierarchy.go
  • components/ambient-api-server/plugins/roleBindings/handler.go
  • components/ambient-api-server/plugins/roles/migration.go
  • components/ambient-api-server/plugins/roles/plugin.go
  • components/ambient-api-server/test/e2e/rbac_e2e_test.sh
  • components/ambient-api-server/test/integration/credential_binding_test.go
  • components/ambient-api-server/test/integration/integration_test.go
  • components/ambient-api-server/test/integration/rbac_test.go
  • components/ambient-control-plane/cmd/ambient-control-plane/main.go
  • components/ambient-control-plane/internal/config/config.go
  • components/ambient-control-plane/internal/reconciler/credential_resolver_test.go
  • components/ambient-control-plane/internal/reconciler/kube_reconciler.go

Comment thread components/ambient-control-plane/internal/config/config.go Outdated
Comment thread components/ambient-control-plane/internal/reconciler/kube_reconciler.go Outdated
jsell-rh and others added 2 commits June 9, 2026 16:11
The credential:owner auto-binding (created when a credential is created)
has the same shape as a global injection binding: scope=credential,
credential_id set, project_id=NULL, agent_id=NULL. The resolver was
treating ownership bindings as injection intent, causing unbound
credentials to be injected into sessions.

Fix: look up the credential:owner role ID and filter out ownership
bindings from all three resolution tiers (agent, project, global).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- TrimSpace on GRPC_SERVICE_ACCOUNT to match API server's middleware normalization
- grantTokenReaderBindings returns only successfully granted credential IDs;
  ensurePod receives the filtered map so sidecars are only created for
  credentials the service identity can actually read
- Propagate revokeTokenReaderBindings errors from deprovisionSession and
  cleanupSession so the reconciler requeues on failure instead of leaving
  orphaned token-reader grants

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RolePlatformViewer: 2,
RoleProjectEditor: 2,
RoleAgentOperator: 2,
"agent:editor": 2,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

inconsistent constant

@mergify mergify Bot added the queued label Jun 9, 2026
@mergify

mergify Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • Entered queue2026-06-09 20:29 UTC · Rule: default
  • Checks skipped · PR is already up-to-date
  • Merged2026-06-09 20:31 UTC · at 57aae137f3efc29cdd019bcb21b022d707457e08 · squash

This pull request spent 1 minute 27 seconds in the queue, including 16 seconds running CI.

Required conditions to merge

@mergify mergify Bot merged commit 5ea8bef into main Jun 9, 2026
71 checks passed
@mergify mergify Bot deleted the jsell/credential-rbac-enforcement branch June 9, 2026 20:31
@mergify mergify Bot removed the queued label Jun 9, 2026
@netlify

netlify Bot commented Jun 9, 2026

Copy link
Copy Markdown

Deploy Preview for cheerful-kitten-f556a0 canceled.

Name Link
🔨 Latest commit 4cad7b1
🔍 Latest deploy log https://app.netlify.com/projects/cheerful-kitten-f556a0/deploys/6a2870eee47f0e00089d5ec0

@netlify

netlify Bot commented Jun 9, 2026

Copy link
Copy Markdown

Deploy Preview for cheerful-kitten-f556a0 canceled.

Name Link
🔨 Latest commit 3fac808
🔍 Latest deploy log https://app.netlify.com/projects/cheerful-kitten-f556a0/deploys/6a2873948f528200082cd51f

1 similar comment
@netlify

netlify Bot commented Jun 9, 2026

Copy link
Copy Markdown

Deploy Preview for cheerful-kitten-f556a0 canceled.

Name Link
🔨 Latest commit 3fac808
🔍 Latest deploy log https://app.netlify.com/projects/cheerful-kitten-f556a0/deploys/6a2873948f528200082cd51f

@netlify

netlify Bot commented Jun 9, 2026

Copy link
Copy Markdown

Deploy Preview for cheerful-kitten-f556a0 canceled.

Name Link
🔨 Latest commit 57aae13
🔍 Latest deploy log https://app.netlify.com/projects/cheerful-kitten-f556a0/deploys/6a287406a481ca0008a49904

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ambient-code:self-reviewed Self-reviewed by Ambient agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants