Skip to content

fix(api-server): register pre-auth interceptors when only GRPC_SERVICE_ACCOUNT is set#1454

Closed
markturansky wants to merge 2 commits intomainfrom
fix/grpc-service-account-caller-type
Closed

fix(api-server): register pre-auth interceptors when only GRPC_SERVICE_ACCOUNT is set#1454
markturansky wants to merge 2 commits intomainfrom
fix/grpc-service-account-caller-type

Conversation

@markturansky
Copy link
Copy Markdown
Contributor

@markturansky markturansky commented Apr 24, 2026

Summary

  • Follow-up to fix(api-server): recognize OIDC service tokens as service callers for gRPC authz #1452: the init() guard required AMBIENT_API_TOKEN to be non-empty before registering gRPC pre-auth interceptors
  • On Stage/production with OIDC auth, AMBIENT_API_TOKEN is not set — only GRPC_SERVICE_ACCOUNT is — so interceptors were never registered and OIDC service tokens were not recognized as service callers
  • Register interceptors when either env var is set

Test plan

  • After merge + auto-deploy, verify api-server logs show OIDC service account username: ocm-ams-service
  • Create session via acpctl start, tail runner logs — no PERMISSION_DENIED on WatchSessionMessages

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Expanded gRPC authentication capabilities with support for service account-based authentication in addition to bearer token authentication.
  • Configuration

    • Added GRPC_SERVICE_ACCOUNT environment variable to enable service account credential configuration.

user and others added 2 commits April 23, 2026 19:17
… gRPC authz

The control-plane gives runner pods OIDC tokens (SSO JWTs) for gRPC
auth. These tokens pass JWT verification but carry the OIDC client
username (e.g. ocm-ams-service), not the session creator's username.
WatchSessionMessages then rejects the call with PERMISSION_DENIED
because the JWT username doesn't match session.CreatedByUserId.

Add GRPC_SERVICE_ACCOUNT env var support: when the JWT username matches
this configured service account name, the pre-auth interceptor sets
CallerTypeService on the context, allowing the call to bypass the
per-user ownership check — same as static AMBIENT_API_TOKEN calls.

The env var is sourced from the ambient-api-server secret's clientId
key (same OIDC client ID the CP uses), with optional: true so
deployments without OIDC continue to work.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
…E_ACCOUNT is set

The init() guard required AMBIENT_API_TOKEN to be non-empty before
registering gRPC pre-auth interceptors. On Stage/production with OIDC
auth, AMBIENT_API_TOKEN is not set — only GRPC_SERVICE_ACCOUNT is.
This meant the interceptors were never registered and OIDC service
tokens were not recognized as service callers.

Register interceptors when either AMBIENT_API_TOKEN or
GRPC_SERVICE_ACCOUNT is set.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 24, 2026

Deploy Preview for cheerful-kitten-f556a0 ready!

Name Link
🔨 Latest commit 0d2c89e
🔍 Latest deploy log https://app.netlify.com/projects/cheerful-kitten-f556a0/deploys/69eabc6fc8afde000879437d
😎 Deploy Preview https://deploy-preview-1454--cheerful-kitten-f556a0.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

📝 Walkthrough

Walkthrough

The authentication middleware now supports service account authentication via a new GRPC_SERVICE_ACCOUNT environment variable. The gRPC interceptors verify JWT usernames against the configured service account and mark matching callers as CallerTypeService. This enables an additional authentication path alongside bearer token validation.

Changes

Cohort / File(s) Summary
Bearer Token Middleware
components/ambient-api-server/pkg/middleware/bearer_token.go, components/ambient-api-server/pkg/middleware/bearer_token_grpc.go
Interceptor functions now accept a second parameter for service account username. Pre-auth initialization passes both bearer token and service account to interceptors. Logic checks for service account match and sets caller type to CallerTypeService when credentials align.
Service Configuration
components/manifests/base/core/ambient-api-server-service.yml
Adds GRPC_SERVICE_ACCOUNT environment variable to api-server container, sourced from the ambient-api-server secret's clientId key with optional reference handling.

Important

Pre-merge checks failed

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

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Security And Secret Handling ❌ Error JWT parsing with ParseUnverified() bypasses signature validation, allowing forged tokens with matching username claims to elevate to CallerTypeService without cryptographic verification. Replace unverified JWT claim validation with cryptographically verified token validation requiring full signature verification using appropriate keys and strong claims validation.
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 (6 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 (fix type) and accurately describes the main fix: enabling interceptor registration when GRPC_SERVICE_ACCOUNT is set without AMBIENT_API_TOKEN.
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.
Performance And Algorithmic Complexity ✅ Passed PR introduces no performance regressions; JWT username extraction uses O(1) unverified parsing and fixed 3-iteration loop with no expensive operations.
Kubernetes Resource Safety ✅ Passed PR adds environment variable to existing container without introducing new child resources, RBAC rules, or altering resource limits.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/grpc-service-account-caller-type
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch fix/grpc-service-account-caller-type
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/grpc-service-account-caller-type

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@components/ambient-api-server/pkg/middleware/bearer_token_grpc.go`:
- Around line 32-34: The code elevates to CallerTypeService based on an
unverified JWT claim; remove the ParseUnverified-based path and replace it with
strict signature and claims validation: stop using
usernameFromJWT()/ParseUnverified(), instead validate the token signature (e.g.,
via jwt.ParseWithClaims or equivalent) and verify required claims (issuer,
audience/client_id, expiry) before matching the validated subject/username to
serviceAccountUsername, and only then call withCallerType(ctx,
CallerTypeService); keep the existing constant-time token comparison path intact
and ensure any new validation uses the configured signing key and fails closed
on errors.
🪄 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: 53ed6961-9628-4fff-b9fe-e799470ada2f

📥 Commits

Reviewing files that changed from the base of the PR and between 85af3da and 0d2c89e.

📒 Files selected for processing (3)
  • components/ambient-api-server/pkg/middleware/bearer_token.go
  • components/ambient-api-server/pkg/middleware/bearer_token_grpc.go
  • components/manifests/base/core/ambient-api-server-service.yml

Comment on lines +32 to +34
if serviceAccountUsername != "" && username == serviceAccountUsername {
ctx = withCallerType(ctx, CallerTypeService)
}
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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Where service caller privilege is assigned/checked:"
rg -n -C3 --type=go '\bwithCallerType\s*\(.*CallerTypeService|\bIsServiceCaller\s*\('

echo
echo "2) Which JWT claims are currently inspected for identity/elevation:"
rg -n -C3 --type=go 'preferred_username|username|sub|azp|client_id|clientId|aud|iss|issuer'

Repository: ambient-code/platform

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Finding usernameFromJWT implementation ==="
rg -n "func usernameFromJWT" --type=go -A 20

echo ""
echo "=== Finding JWT validation/parsing ==="
rg -n "jwt\.|JWT\." --type=go | head -30

echo ""
echo "=== Checking for JWT library usage ==="
grep -r "jwt" --include="*.go" components/ambient-api-server/pkg/middleware/ | head -20

Repository: ambient-code/platform

Length of output: 5098


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Full bearer_token_grpc.go context around lines 25-65 ==="
sed -n '20,70p' components/ambient-api-server/pkg/middleware/bearer_token_grpc.go

echo ""
echo "=== Check if expectedToken is required for username path ==="
sed -n '20,70p' components/ambient-api-server/pkg/middleware/bearer_token_grpc.go | grep -A 5 -B 5 "usernameFromJWT\|serviceAccountUsername"

Repository: ambient-code/platform

Length of output: 3764


Remove unverified JWT claim path for service caller elevation; implement strict signed token validation.

Lines 31–34 and 57–61 elevate to CallerTypeService when an unverified JWT username claim matches serviceAccountUsername. This is a critical vulnerability: usernameFromJWT() (line 80) uses ParseUnverified(), allowing any attacker to forge a JWT with a matching username claim and bypass token signing entirely. Service elevation must require signature validation and strong claims (e.g., issuer, audience, client_id) beyond username matching. The constant-time token path (lines 28–29, 54–55) is secure; the unverified path should be removed or replaced with cryptographic verification.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ambient-api-server/pkg/middleware/bearer_token_grpc.go` around
lines 32 - 34, The code elevates to CallerTypeService based on an unverified JWT
claim; remove the ParseUnverified-based path and replace it with strict
signature and claims validation: stop using usernameFromJWT()/ParseUnverified(),
instead validate the token signature (e.g., via jwt.ParseWithClaims or
equivalent) and verify required claims (issuer, audience/client_id, expiry)
before matching the validated subject/username to serviceAccountUsername, and
only then call withCallerType(ctx, CallerTypeService); keep the existing
constant-time token comparison path intact and ensure any new validation uses
the configured signing key and fails closed on errors.

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