Skip to content

fix(runner): use CP OIDC token for backend credential fetches#1219

Merged
markturansky merged 1 commit intoalphafrom
fix/runner-credential-sa-token
Apr 6, 2026
Merged

fix(runner): use CP OIDC token for backend credential fetches#1219
markturansky merged 1 commit intoalphafrom
fix/runner-credential-sa-token

Conversation

@markturansky
Copy link
Copy Markdown
Contributor

@markturansky markturansky commented Apr 6, 2026

Summary

  • The credential endpoint (GET /api/ambient/v1/credentials/{id}/token) lives on the ambient-api-server, which validates tokens via RHSSO JWT signature — the same issuer that signs the CP OIDC token used for gRPC
  • The K8s SA token path added in the previous commit was unnecessary: the CP OIDC token is already a valid RHSSO JWT and authenticates to the same server for both gRPC and HTTP credential fetches
  • Removes get_sa_token() from the credential fetch path; get_bot_token() (CP OIDC) is now used as the primary token when no caller token is present
  • The CP's OIDC client identity requires a role_binding granting credential:read in the DB (infra/ops concern)

Test plan

  • 31 tests pass in test_shared_session_credentials.py
  • Regression tests updated: verify CP OIDC token (get_bot_token()) is sent in Authorization header when no caller token present
  • Deploy and verify runner can fetch GitHub credentials without HTTP 401

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for reading Kubernetes ServiceAccount tokens when available.
    • Emit a debug log when falling back to a bot/CP token source for credential requests.
  • Tests

    • Added regression tests verifying credential fetches and outgoing Authorization headers when the caller token is absent.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

📝 Walkthrough

Walkthrough

Added a Kubernetes ServiceAccount token reader utility, added a debug log in credential fetching when falling back to the bot-token path, and added tests exercising _fetch_credential behavior when ctx.caller_token is absent and the code uses a CP OIDC/bot token.

Changes

Cohort / File(s) Summary
Service Account Token Utility
components/runners/ambient-runner/ambient_runner/platform/utils.py
Added _SA_TOKEN_FILE constant and new get_sa_token() -> str that reads the kubelet-mounted ServiceAccount token file and returns its content or an empty string on error/missing file.
Authentication Logging
components/runners/ambient-runner/ambient_runner/platform/auth.py
Added a debug log emitted when _fetch_credential falls back from a missing caller token to the bot-token code path; no changes to control flow, header behavior, or token selection logic.
Tests
components/runners/ambient-runner/tests/test_shared_session_credentials.py
Added TestFetchCredentialBotToken with two async tests validating _fetch_credential behavior when ctx.caller_token is absent and that outbound requests carry the expected Authorization header (CP OIDC / bot-token scenarios).
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning Title mentions 'CP OIDC token' but the PR objectives clearly state the change prioritizes Kubernetes SA token, not CP OIDC token. Update title to 'fix(runner): use K8s SA token for backend credential fetches' to accurately reflect the main change.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.
Performance And Algorithmic Complexity ✅ Passed Code introduces no performance regressions: O(1) file read with proper exception handling, single bounded HTTP request with optional fallback retry, no N+1 patterns or unbounded operations.
Security And Secret Handling ✅ Passed No plaintext logging, hardcoded secrets, or injection vulnerabilities detected. SA token retrieval reads tokens safely and secret redaction utilities remain intact.
Kubernetes Resource Safety ✅ Passed Kubernetes Resource Safety check not applicable; PR modifies only Python source code without creating or modifying Kubernetes manifests, RBAC policies, or security configurations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/runner-credential-sa-token
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/runner-credential-sa-token

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.

The credential endpoint (GET /api/ambient/v1/credentials/{id}/token) is
on the ambient-api-server, which validates tokens via RHSSO JWT signature
verification — the same issuer that signs the CP OIDC token used for gRPC.

The K8s SA token path added previously was unnecessary: the CP OIDC token
is already a valid RHSSO JWT and authenticates to the same api-server for
both gRPC and HTTP credential fetches. The CP's OIDC client identity
requires a role_binding granting credential:read in the DB.

Removes get_sa_token() from the credential fetch path. Updates regression
tests to verify the CP OIDC token is sent.

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

Co-Authored-By: Claude <noreply@anthropic.com>
@markturansky markturansky force-pushed the fix/runner-credential-sa-token branch from 9c4c5c1 to c78f884 Compare April 6, 2026 02:19
@markturansky markturansky changed the title fix(runner): use K8s SA token for backend credential fetches fix(runner): use CP OIDC token for backend credential fetches Apr 6, 2026
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/runners/ambient-runner/tests/test_shared_session_credentials.py`:
- Around line 738-818: The tests in TestFetchCredentialBotToken are asserting
the bot (CP OIDC) token is used when ctx.caller_token is absent, but the
intended behavior is: prefer a mounted Kubernetes ServiceAccount (SA) token and
fall back to the bot token only if the SA token is unavailable; update
TestFetchCredentialBotToken.test_uses_bot_token_when_no_caller_token and
test_bot_token_used_when_no_caller_token to simulate an available SA token and
assert the Authorization header equals "Bearer <sa-token>" (reference
_fetch_credential, ctx.caller_token, _CredentialHandler.captured_headers, and
fake_urlopen), and add/modify a separate case that removes the SA token
simulation to assert the bot token returned by
ambient_runner.platform.auth.get_bot_token is used as the fallback.
🪄 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: Pro

Run ID: 4e97c16b-7f08-4cda-956e-e4bd4441a2d2

📥 Commits

Reviewing files that changed from the base of the PR and between 9c4c5c1 and c78f884.

📒 Files selected for processing (3)
  • components/runners/ambient-runner/ambient_runner/platform/auth.py
  • components/runners/ambient-runner/ambient_runner/platform/utils.py
  • components/runners/ambient-runner/tests/test_shared_session_credentials.py
✅ Files skipped from review due to trivial changes (1)
  • components/runners/ambient-runner/ambient_runner/platform/auth.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/runners/ambient-runner/ambient_runner/platform/utils.py

Comment on lines +738 to +818
class TestFetchCredentialBotToken:
@pytest.mark.asyncio
async def test_uses_bot_token_when_no_caller_token(self):
"""_fetch_credential sends the CP OIDC token when caller_token is absent.

The api-server validates the CP OIDC token via RHSSO JWT signature verification.
The CP's OIDC client identity must have a role_binding granting credential:read.

Regression for: runner gets HTTP 401 on credential fetch in gRPC-initiated runs.
"""
server = HTTPServer(("127.0.0.1", 0), _CredentialHandler)
port = server.server_address[1]
thread = Thread(target=server.handle_request, daemon=True)
thread.start()

_CredentialHandler.response_body = {"token": "gh-tok-via-oidc"}
_CredentialHandler.captured_headers = {}

cp_oidc_token = "cp-oidc-jwt-token"

try:
with (
patch.dict(
os.environ,
{
"BACKEND_API_URL": f"http://127.0.0.1:{port}/api",
"CREDENTIAL_IDS": json.dumps({"github": "cred-gh-bot-test"}),
},
),
patch("ambient_runner.platform.auth.get_bot_token", return_value=cp_oidc_token),
):
ctx = _make_context() # no caller_token
result = await _fetch_credential(ctx, "github")

assert result.get("token") == "gh-tok-via-oidc", (
"credential fetch must succeed using CP OIDC token — "
"regression for HTTP 401 on gRPC-initiated runs"
)
assert _CredentialHandler.captured_headers.get("Authorization") == (
f"Bearer {cp_oidc_token}"
), "request must use the CP OIDC token"
finally:
server.server_close()
thread.join(timeout=2)

@pytest.mark.asyncio
async def test_bot_token_used_when_no_caller_token(self):
"""CP OIDC token (get_bot_token) is used when caller_token is absent.

The credential endpoint on the api-server validates via RHSSO JWT,
the same issuer that signs the CP OIDC token — one token for both
gRPC and HTTP credential fetches.
"""
called_with = {}

def fake_urlopen(req, timeout=None):
called_with["auth"] = req.get_header("Authorization")
mock_resp = MagicMock()
mock_resp.read.return_value = json.dumps({"token": "ok"}).encode()
mock_resp.__enter__ = lambda s: s
mock_resp.__exit__ = MagicMock(return_value=False)
return mock_resp

with (
patch.dict(
os.environ,
{
"BACKEND_API_URL": "http://backend.svc.cluster.local/api",
"CREDENTIAL_IDS": json.dumps({"github": "cred-gh-pref"}),
},
),
patch("urllib.request.urlopen", side_effect=fake_urlopen),
patch("ambient_runner.platform.auth.get_bot_token", return_value="cp-oidc-token"),
):
ctx = _make_context() # no caller_token
await _fetch_credential(ctx, "github")

assert called_with.get("auth") == "Bearer cp-oidc-token", (
"CP OIDC token must be used for credential fetch — "
"same token used for gRPC and HTTP credential endpoint"
)
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 | 🟠 Major

Tests are asserting the wrong auth source for the no-caller-token path.

Line 740 and Line 784 both enforce CP OIDC/bot token usage when ctx.caller_token is absent. That conflicts with this PR’s intended behavior (Kubernetes ServiceAccount token first, bot token fallback only when SA token is unavailable). As written, these tests either fail against the intended implementation or lock in the old behavior.

Please update these cases to assert:

  1. SA token is used when mounted/available, and
  2. bot token is used only when SA token is unavailable.

As per coding guidelines, "**/*: Flag only errors, security risks, or functionality-breaking problems."

🧰 Tools
🪛 Ruff (0.15.9)

[error] 756-756: Possible hardcoded password assigned to: "cp_oidc_token"

(S105)

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

In `@components/runners/ambient-runner/tests/test_shared_session_credentials.py`
around lines 738 - 818, The tests in TestFetchCredentialBotToken are asserting
the bot (CP OIDC) token is used when ctx.caller_token is absent, but the
intended behavior is: prefer a mounted Kubernetes ServiceAccount (SA) token and
fall back to the bot token only if the SA token is unavailable; update
TestFetchCredentialBotToken.test_uses_bot_token_when_no_caller_token and
test_bot_token_used_when_no_caller_token to simulate an available SA token and
assert the Authorization header equals "Bearer <sa-token>" (reference
_fetch_credential, ctx.caller_token, _CredentialHandler.captured_headers, and
fake_urlopen), and add/modify a separate case that removes the SA token
simulation to assert the bot token returned by
ambient_runner.platform.auth.get_bot_token is used as the fallback.

@markturansky
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@markturansky
Copy link
Copy Markdown
Contributor Author

Re: CodeRabbit inline comment on TestFetchCredentialBotToken

The SA token priority has been intentionally removed in this commit. Investigation confirmed:

  • The credential endpoint (GET /api/ambient/v1/credentials/{id}/token) is on the ambient-api-server
  • It validates tokens via RHSSO JWT signature (AuthenticateAccountJWT middleware, keys from the RHSSO JWK endpoint)
  • There is no K8s SelfSubjectReview on this path — the isBotToken concept does not exist here
  • The CP OIDC token is already a valid RHSSO JWT — it authenticates to the same server for both gRPC and HTTP credential fetches
  • One token, one issuer, one server. The K8s SA token was unnecessary.

@markturansky markturansky merged commit 8329cad into alpha Apr 6, 2026
44 checks passed
@markturansky markturansky deleted the fix/runner-credential-sa-token branch April 6, 2026 02:42
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