Skip to content

feat: Kubernetes ServiceAccount auth for gRPC#424

Open
wseaton wants to merge 14 commits into
ai-dynamo:mainfrom
wseaton:weaton/k8s-sa-auth
Open

feat: Kubernetes ServiceAccount auth for gRPC#424
wseaton wants to merge 14 commits into
ai-dynamo:mainfrom
wseaton:weaton/k8s-sa-auth

Conversation

@wseaton

@wseaton wseaton commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Opt-in ServiceAccount auth for the gRPC server. Off by default; existing deployments are unaffected.

  • AuthN: verifies the caller's projected ServiceAccount token via the Kubernetes TokenReview API, in-process (no sidecar or mesh). Requested audiences must overlap the reviewed token's audiences.
  • AuthZ: exact-match namespace:serviceaccount allowlist.
  • Modes off (default) and enforce. enforce fails config validation without a token audience and a non-empty allowlist; the Helm chart fails rendering on the same misconfiguration.
  • Health service stays ungated so kubelet probes need no token.
  • Verified tokens and definitive rejections are cached (bounded, default 60s TTL). TokenReview backend errors return UNAVAILABLE, not UNAUTHENTICATED, and are never cached.
  • Rust and Python clients attach the token when a projected token file is present and send nothing otherwise, so the same client works off-cluster against a server with auth off.
  • Helm: security.enabled injects the server env and, with serviceAccount.create, binds the SA to system:auth-delegator for TokenReview access.

Config via MODEL_EXPRESS_SECURITY_* env vars or security.* in the config file / Helm values. Covered by unit tests and an end-to-end test driving the real client against a real tonic server.

Summary by CodeRabbit

  • New Features

    • Added optional Kubernetes ServiceAccount authentication for gRPC services.
    • Added configurable token audiences, allowed service accounts, and authentication modes.
    • Python and Rust clients now automatically attach projected ServiceAccount bearer tokens when available.
    • Helm deployments can configure authentication and required Kubernetes permissions.
  • Documentation

    • Added deployment guidance, configuration details, environment variables, and Helm examples for ServiceAccount authentication.
  • Tests

    • Added coverage for token handling, caching, authorization, rejection scenarios, and end-to-end authentication behavior.

@copy-pr-bot

copy-pr-bot Bot commented Jun 5, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added the feat label Jun 5, 2026
@wseaton wseaton changed the title feat: Kubernetes ServiceAccount auth behind an off-by-default mode feat: Kubernetes ServiceAccount auth for gRPC Jun 5, 2026
@wseaton wseaton force-pushed the weaton/k8s-sa-auth branch from 5adb15b to 65c47cd Compare June 12, 2026 19:51
@codecov

codecov Bot commented Jun 12, 2026

Copy link
Copy Markdown

@wseaton wseaton force-pushed the weaton/k8s-sa-auth branch 2 times, most recently from 4f1f2ac to f7b00f5 Compare June 22, 2026 19:31
wseaton added 2 commits July 10, 2026 09:00
AuthN verifies the caller's projected ServiceAccount token via the Kubernetes
TokenReview API in-process (no sidecar/mesh). AuthZ is an exact-match
namespace/serviceaccount allowlist. Modes are off (default) and enforce; enforce
fails config validation without a token audience and a non-empty allowlist.

The health service stays ungated so kubelet probes need no token. The Rust and
Python clients attach the token when a projected token file is present and send
nothing otherwise, so the same client works off-cluster against an off server.
The Helm chart binds the server SA to system:auth-delegator when enabled.

Signed-off-by: Will Eaton <weaton@redhat.com>
Signed-off-by: Will Eaton <weaton@redhat.com>
@wseaton wseaton force-pushed the weaton/k8s-sa-auth branch from f7b00f5 to dedb4f8 Compare July 10, 2026 13:01
wseaton added 8 commits July 10, 2026 09:13
Signed-off-by: Will Eaton <weaton@redhat.com>
Signed-off-by: Will Eaton <weaton@redhat.com>
Signed-off-by: Will Eaton <weaton@redhat.com>
Signed-off-by: Will Eaton <weaton@redhat.com>
Signed-off-by: Will Eaton <weaton@redhat.com>
Signed-off-by: Will Eaton <weaton@redhat.com>
Signed-off-by: Will Eaton <weaton@redhat.com>
The env var pointing at the token file is process-global, so the three
scenarios share one test fn and one token file instead of racing.

Signed-off-by: Will Eaton <weaton@redhat.com>
@wseaton wseaton marked this pull request as ready for review July 10, 2026 13:32
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds optional Kubernetes ServiceAccount authentication for gRPC services, with TokenReview verification, allowlist authorization, caching, Helm configuration, and Rust/Python client bearer-token propagation. Tests cover token handling, middleware behavior, configuration, and end-to-end RPC outcomes.

Changes

ServiceAccount authentication

Layer / File(s) Summary
Security configuration and deployment settings
modelexpress_server/src/config.rs, modelexpress_common/src/envs.rs, helm/*, Cargo.toml
Adds security modes, audiences, allowlists, cache settings, environment variables, validation, and Helm wiring including TokenReview RBAC.
TokenReview verification and caching
modelexpress_server/src/auth.rs, modelexpress_server/src/auth/token.rs, modelexpress_server/Cargo.toml
Extracts bearer tokens, validates Kubernetes identities and audiences, performs TokenReview calls, maps denials, and caches successful and rejected reviews.
gRPC middleware and server wiring
modelexpress_server/src/auth/layer.rs, modelexpress_server/src/server.rs, modelexpress_server/src/lib.rs
Adds Tower authentication middleware and conditionally applies it to API, model, and P2P services.
Rust client token propagation
modelexpress_client/src/auth.rs, modelexpress_client/src/lib.rs
Reads projected tokens, caches them, injects bearer metadata, and wires intercepted channels into Rust clients.
Python client token propagation
modelexpress_client/python/modelexpress/auth.py, modelexpress_client/python/modelexpress/client.py, modelexpress_client/python/modelexpress/envs.py, modelexpress_client/python/tests/test_auth.py
Adds token-file caching, gRPC metadata interception, environment configuration, client integration, and tests.
Deployment documentation and end-to-end validation
docs/DEPLOYMENT.md, modelexpress_server/tests/auth_e2e.rs, .github/copy-pr-bot.yaml
Documents server and client authentication settings and tests successful, unauthorized, and unauthenticated RPC flows.

Estimated code review effort: 4 (Complex) | ~60 minutes

Poem

I’m a bunny with tokens tucked snug in my hay,
Bearer hops through each RPC on its way.
Kubernetes checks who is knocking at night,
Allowlists keep every service just right.
Cache the good, reject the bad—
Secure little carrots make rabbits glad.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 85.00% which is sufficient. The required threshold is 80.00%.
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 clearly summarizes the main change: adding Kubernetes ServiceAccount authentication for the gRPC server.

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

@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: 2

🧹 Nitpick comments (4)
modelexpress_client/src/lib.rs (1)

187-191: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Each Client::new creates its own TokenProvider, unlike the Python client's process-wide shared_provider().

Every call to Client::new constructs a fresh TokenProvider::from_env() with an empty cache, so callers that create multiple short-lived Clients (e.g. request_model_with_smart_fallback) each pay a cold-start file read instead of reusing a cached token, unlike modelexpress_client/python/modelexpress/auth.py's shared_provider(). Not a correctness issue since the cost per read is a cheap stat/read, but worth aligning with the Python design for consistency if multiple Clients are commonly constructed.

🤖 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 `@modelexpress_client/src/lib.rs` around lines 187 - 191, Update Client::new
and the Rust authentication setup around AuthInterceptor and TokenProvider to
reuse a process-wide shared TokenProvider, matching the Python auth.py
shared_provider() behavior. Ensure multiple Client instances share the same
cached provider instead of calling TokenProvider::from_env() for each
construction, while preserving interceptor cloning and existing client
initialization.
helm/values.yaml (1)

30-44: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Enabling security with defaults guarantees a crash-looping deployment.

Default mode: enforce combined with empty tokenAudiences/allowedServiceAccounts means simply flipping security.enabled: true (without also filling in the required lists) causes the server's own validate_resolved (in modelexpress_server/src/config.rs) to reject the config at startup, crash-looping the pod. Consider having Helm fail fast with a clear message (e.g. via required / fail template checks when security.enabled is true but the lists are empty) instead of deferring to an opaque runtime crash.

🤖 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 `@helm/values.yaml` around lines 30 - 44, Helm currently permits
security.enabled=true with enforce mode and empty required lists, causing a
runtime crash-loop. Add template validation for security.enabled in the chart’s
relevant template helpers, using required/fail to reject empty tokenAudiences or
allowedServiceAccounts with clear configuration messages before rendering.
modelexpress_server/src/auth/token.rs (1)

134-161: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer expect() over unwrap() in tests.

This test module relies on .unwrap() (e.g. Lines 147-160, 207, 246). Coding guidelines require expect() in tests, which also yields clearer failure messages.

As per coding guidelines: "Do not use unwrap() except in benchmarks. Use expect() only in tests."

🤖 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 `@modelexpress_server/src/auth/token.rs` around lines 134 - 161, Replace every
.unwrap() call in the tests module, including those in
extracts_bearer_case_insensitively and the other referenced tests, with
.expect() using clear failure messages while preserving the existing assertions
and behavior.

Source: Coding guidelines

modelexpress_server/src/auth.rs (1)

111-138: 🩺 Stability & Availability | 🔵 Trivial

Consider load-shedding for unauthenticated TokenReview traffic.

An unauthenticated network caller can send an unbounded stream of distinct random bearer tokens. Each distinct token misses both caches and issues a fresh TokenReview create against the Kubernetes API server before any caching applies, so this path can be used to amplify load onto (or degrade) the cluster API server. The 10k-entry caches don't help against high-cardinality random input.

Consider a per-source rate limit / concurrency cap on cache-miss review_token calls, and/or a short-lived negative cache for backend failures to bound outbound API-server pressure.

🤖 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 `@modelexpress_server/src/auth.rs` around lines 111 - 138, Bound cache-miss
TokenReview traffic in verify by adding per-source rate limiting or a
concurrency cap around review_token calls, using the request source information
available from headers or the surrounding auth context; also consider briefly
caching backend failures separately from token rejections. Ensure rejected
requests fail with the appropriate denial and that successful and
token-rejection caching behavior remains unchanged.
🤖 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 `@helm/templates/deployment.yaml`:
- Around line 79-82: Update the conditional around
MODEL_EXPRESS_SECURITY_CACHE_TTL_SECS in the deployment template to distinguish
an unset value from an explicit zero, using a presence check such as hasKey on
.Values.security before rendering the quoted cacheTtlSecs value.

In `@modelexpress_client/python/modelexpress/auth.py`:
- Around line 24-34: Handle invalid MX_AUTH_TOKEN_TTL_SECONDS values gracefully
in TokenProvider.__init__: wrap the float conversion in safe parsing and fall
back to DEFAULT_TTL_SECONDS when parsing fails, matching the Rust
implementation. Preserve explicit valid TTL values and ensure construction never
raises ValueError for malformed environment input.

---

Nitpick comments:
In `@helm/values.yaml`:
- Around line 30-44: Helm currently permits security.enabled=true with enforce
mode and empty required lists, causing a runtime crash-loop. Add template
validation for security.enabled in the chart’s relevant template helpers, using
required/fail to reject empty tokenAudiences or allowedServiceAccounts with
clear configuration messages before rendering.

In `@modelexpress_client/src/lib.rs`:
- Around line 187-191: Update Client::new and the Rust authentication setup
around AuthInterceptor and TokenProvider to reuse a process-wide shared
TokenProvider, matching the Python auth.py shared_provider() behavior. Ensure
multiple Client instances share the same cached provider instead of calling
TokenProvider::from_env() for each construction, while preserving interceptor
cloning and existing client initialization.

In `@modelexpress_server/src/auth.rs`:
- Around line 111-138: Bound cache-miss TokenReview traffic in verify by adding
per-source rate limiting or a concurrency cap around review_token calls, using
the request source information available from headers or the surrounding auth
context; also consider briefly caching backend failures separately from token
rejections. Ensure rejected requests fail with the appropriate denial and that
successful and token-rejection caching behavior remains unchanged.

In `@modelexpress_server/src/auth/token.rs`:
- Around line 134-161: Replace every .unwrap() call in the tests module,
including those in extracts_bearer_case_insensitively and the other referenced
tests, with .expect() using clear failure messages while preserving the existing
assertions and behavior.
🪄 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: b1b6401f-b338-4e19-b94c-80dbacbde2dd

📥 Commits

Reviewing files that changed from the base of the PR and between ab2df38 and 2a069fd.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (21)
  • .github/copy-pr-bot.yaml
  • Cargo.toml
  • docs/DEPLOYMENT.md
  • helm/templates/deployment.yaml
  • helm/templates/rbac.yaml
  • helm/values.yaml
  • modelexpress_client/python/modelexpress/auth.py
  • modelexpress_client/python/modelexpress/client.py
  • modelexpress_client/python/modelexpress/envs.py
  • modelexpress_client/python/tests/test_auth.py
  • modelexpress_client/src/auth.rs
  • modelexpress_client/src/lib.rs
  • modelexpress_common/src/envs.rs
  • modelexpress_server/Cargo.toml
  • modelexpress_server/src/auth.rs
  • modelexpress_server/src/auth/layer.rs
  • modelexpress_server/src/auth/token.rs
  • modelexpress_server/src/config.rs
  • modelexpress_server/src/lib.rs
  • modelexpress_server/src/server.rs
  • modelexpress_server/tests/auth_e2e.rs

Comment thread helm/templates/deployment.yaml Outdated
Comment on lines +79 to +82
{{- if .Values.security.cacheTtlSecs }}
- name: MODEL_EXPRESS_SECURITY_CACHE_TTL_SECS
value: {{ .Values.security.cacheTtlSecs | quote }}
{{- end }}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

cacheTtlSecs: 0 is silently dropped.

{{- if .Values.security.cacheTtlSecs }} treats 0 as falsy in Helm/Go templates, so an operator who explicitly sets cacheTtlSecs: 0 (e.g., to disable caching) will have that value silently ignored, falling back to the server's default of 60s instead.

🐛 Proposed fix to distinguish "unset" from "zero"
-            {{- if .Values.security.cacheTtlSecs }}
+            {{- if hasKey .Values.security "cacheTtlSecs" }}
             - name: MODEL_EXPRESS_SECURITY_CACHE_TTL_SECS
               value: {{ .Values.security.cacheTtlSecs | quote }}
             {{- end }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{{- if .Values.security.cacheTtlSecs }}
- name: MODEL_EXPRESS_SECURITY_CACHE_TTL_SECS
value: {{ .Values.security.cacheTtlSecs | quote }}
{{- end }}
{{- if hasKey .Values.security "cacheTtlSecs" }}
- name: MODEL_EXPRESS_SECURITY_CACHE_TTL_SECS
value: {{ .Values.security.cacheTtlSecs | quote }}
{{- end }}
🤖 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 `@helm/templates/deployment.yaml` around lines 79 - 82, Update the conditional
around MODEL_EXPRESS_SECURITY_CACHE_TTL_SECS in the deployment template to
distinguish an unset value from an explicit zero, using a presence check such as
hasKey on .Values.security before rendering the quoted cacheTtlSecs value.

Comment on lines +24 to +34
def __init__(self, path: str | None = None, ttl_seconds: float | None = None):
self._path = path or envs.MX_AUTH_TOKEN_PATH or DEFAULT_TOKEN_PATH
if ttl_seconds is None:
raw_ttl = envs.MX_AUTH_TOKEN_TTL_SECONDS
ttl_seconds = float(raw_ttl) if raw_ttl else DEFAULT_TTL_SECONDS
self._ttl = ttl_seconds
self._lock = threading.Lock()
self._cached: str | None = None
self._cached_at: float = 0.0
self._cached_mtime: float = 0.0
self._warned_missing = False

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Unhandled ValueError on malformed MX_AUTH_TOKEN_TTL_SECONDS.

float(raw_ttl) will raise ValueError if the env var is set to a non-numeric string, crashing TokenProvider() construction and thus the whole client. The Rust counterpart (modelexpress_client/src/auth.rs, .and_then(|raw| raw.parse::<u64>().ok())) silently falls back to the default on parse failure. Mirror that graceful fallback here.

🛡️ Proposed fix
         if ttl_seconds is None:
             raw_ttl = envs.MX_AUTH_TOKEN_TTL_SECONDS
-            ttl_seconds = float(raw_ttl) if raw_ttl else DEFAULT_TTL_SECONDS
+            try:
+                ttl_seconds = float(raw_ttl) if raw_ttl else DEFAULT_TTL_SECONDS
+            except ValueError:
+                logger.warning(
+                    "Invalid MX_AUTH_TOKEN_TTL_SECONDS=%r; using default %.0fs",
+                    raw_ttl,
+                    DEFAULT_TTL_SECONDS,
+                )
+                ttl_seconds = DEFAULT_TTL_SECONDS
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def __init__(self, path: str | None = None, ttl_seconds: float | None = None):
self._path = path or envs.MX_AUTH_TOKEN_PATH or DEFAULT_TOKEN_PATH
if ttl_seconds is None:
raw_ttl = envs.MX_AUTH_TOKEN_TTL_SECONDS
ttl_seconds = float(raw_ttl) if raw_ttl else DEFAULT_TTL_SECONDS
self._ttl = ttl_seconds
self._lock = threading.Lock()
self._cached: str | None = None
self._cached_at: float = 0.0
self._cached_mtime: float = 0.0
self._warned_missing = False
def __init__(self, path: str | None = None, ttl_seconds: float | None = None):
self._path = path or envs.MX_AUTH_TOKEN_PATH or DEFAULT_TOKEN_PATH
if ttl_seconds is None:
raw_ttl = envs.MX_AUTH_TOKEN_TTL_SECONDS
try:
ttl_seconds = float(raw_ttl) if raw_ttl else DEFAULT_TTL_SECONDS
except ValueError:
logger.warning(
"Invalid MX_AUTH_TOKEN_TTL_SECONDS=%r; using default %.0fs",
raw_ttl,
DEFAULT_TTL_SECONDS,
)
ttl_seconds = DEFAULT_TTL_SECONDS
self._ttl = ttl_seconds
self._lock = threading.Lock()
self._cached: str | None = None
self._cached_at: float = 0.0
self._cached_mtime: float = 0.0
self._warned_missing = False
🤖 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 `@modelexpress_client/python/modelexpress/auth.py` around lines 24 - 34, Handle
invalid MX_AUTH_TOKEN_TTL_SECONDS values gracefully in TokenProvider.__init__:
wrap the float conversion in safe parsing and fall back to DEFAULT_TTL_SECONDS
when parsing fails, matching the Rust implementation. Preserve explicit valid
TTL values and ensure construction never raises ValueError for malformed
environment input.

wseaton added 3 commits July 10, 2026 10:52
Signed-off-by: Will Eaton <weaton@redhat.com>
…lowlist

Signed-off-by: Will Eaton <weaton@redhat.com>
Signed-off-by: Will Eaton <weaton@redhat.com>
@wseaton

wseaton commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Smoke-tested live on a Kubernetes cluster (real apiserver TokenReview, enforce mode, allowlist weaton-dev:mx-auth-client, audience modelexpress). Three probe pods calling a gated RPC (modelexpress-cli api send ping):

Probe Result
Allowlisted SA, projected token {"message":"pong"}, exit 0
No token file UNAUTHENTICATED, exit 1
Authenticated SA not in allowlist PERMISSION_DENIED, exit 1

Client without a token warns and degrades as designed:

WARN modelexpress auth: token file not found; RPCs sent without a bearer token (server must have auth off)
ERROR Command execution failed: gRPC error: status: Unauthenticated, message: "missing or invalid service account token"

Server-side denials:

WARN modelexpress_server::auth::layer: auth denied reason=missing or invalid service account token path=/model_express.api.ApiService/SendRequest
WARN modelexpress_server::auth::layer: auth denied reason=service account weaton-dev:mx-auth-intruder is not in the allowlist path=/model_express.api.ApiService/SendRequest

Health stays reachable without a token, so kubelet probes are unaffected.

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

Looks solid, a few non-blocking notes inline.

One cross-cutting thing worth handling before merge: with auth on but no TLS, the projected token rides on the wire in cleartext. The Python client attaches it to an insecure channel, so it's capturable and replayable until the token expires. Can we get a clear "enable auth only over TLS" warning in the deployment docs, and maybe a client-side warn when a token is attached to an insecure channel? Fine to merge without the client guard, but I'd like the docs warning in.

let state = self.state.clone();

Box::pin(async move {
match state.verify(req.headers()).await {

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.

Auth runs once when the stream opens, so a long-lived server-streaming RPC keeps flowing even after the token expires or the SA is revoked. Fine for a first cut. Might be worth a doc line noting auth is enforced at RPC start, not per message.

let token = extract_bearer(headers).ok_or(Denial::Unauthenticated)?;
let key = TokenCacheKey::new(token.expose_secret());

if self.negative_cache.get(&key).await.is_some() {

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.

The negative cache is keyed on the raw token, so a caller cycling unique bad tokens skips it and every one lands a fresh TokenReview on the apiserver. Fine for an opt-in feature. If you ever want to harden it, a global or per-source TokenReview rate limit would cap the amplification.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants