Skip to content

HYPERFLEET-1237 - feat: add JWT auth support for HyperFleet API client#220

Open
rh-amarin wants to merge 1 commit into
openshift-hyperfleet:mainfrom
rh-amarin:1237
Open

HYPERFLEET-1237 - feat: add JWT auth support for HyperFleet API client#220
rh-amarin wants to merge 1 commit into
openshift-hyperfleet:mainfrom
rh-amarin:1237

Conversation

@rh-amarin

Copy link
Copy Markdown
Contributor

Summary

  • Adds optional Kubernetes service account token (JWT) authentication to the HyperFleet API HTTP client; when configured, a Bearer token is read from a file and injected into every API request
  • Backs token reading with a configurable in-memory cache (token_cache_ttl > 0) or file-per-request mode (token_cache_ttl: 0), using sync.RWMutex with double-checked locking for safe concurrent access
  • Extends the Helm chart with an auth block under config.clients.hyperfleetApi that mounts a projected ServiceAccountToken volume with configurable audience, tokenPath, expirationSeconds, and tokenCacheTtl; disabled by default so existing deployments are unaffected

Configuration

# config.yaml
clients:
  hyperfleet_api:
    auth:
      token_path: /var/run/secrets/hyperfleet/token
      token_cache_ttl: 30s  # 0 disables caching
# values.yaml
config:
  clients:
    hyperfleetApi:
      auth:
        enabled: true
        audience: hyperfleet-api
        tokenPath: /var/run/secrets/hyperfleet/token
        expirationSeconds: 3600
        tokenCacheTtl: 30s

The env vars HYPERFLEET_API_AUTH_TOKEN_PATH and HYPERFLEET_API_AUTH_TOKEN_CACHE_TTL can also override the config file.

Test plan

  • make verify && make test-unit — all packages pass
  • helm lint charts/ — no errors
  • helm template with auth.enabled=true renders projected volume, volume mount, and auth.token_path in configmap
  • helm template with auth.enabled=false (default) renders no auth-related resources
  • Deploy with auth enabled in a cluster and confirm Authorization: Bearer <token> header reaches the API

🤖 Generated with Claude Code

@openshift-ci openshift-ci Bot requested review from rafabene and vkareh July 7, 2026 16:36
@openshift-ci

openshift-ci Bot commented Jul 7, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign vkareh for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 78dc6aeb-15d3-452e-84b0-55264cdf30c0

📥 Commits

Reviewing files that changed from the base of the PR and between 6b6168c and 7e7e041.

📒 Files selected for processing (17)
  • charts/README.md
  • charts/templates/_helpers.tpl
  • charts/templates/configmap.yaml
  • charts/templates/deployment.yaml
  • charts/values.yaml
  • cmd/sentinel/main.go
  • configs/dev-example.yaml
  • configs/gcp-pubsub-example.yaml
  • configs/rabbitmq-example.yaml
  • internal/client/client.go
  • internal/client/client_test.go
  • internal/client/token.go
  • internal/client/token_test.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/sentinel/sentinel_test.go
  • test/integration/integration_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift-hyperfleet/architecture (manual)
  • openshift-hyperfleet/hyperfleet-api (manual)
  • openshift-hyperfleet/hyperfleet-sentinel (manual)
  • openshift-hyperfleet/hyperfleet-adapter (manual)
  • openshift-hyperfleet/hyperfleet-broker (manual)
✅ Files skipped from review due to trivial changes (4)
  • configs/rabbitmq-example.yaml
  • configs/dev-example.yaml
  • configs/gcp-pubsub-example.yaml
  • charts/README.md
🚧 Files skipped from review as they are similar to previous changes (13)
  • charts/templates/deployment.yaml
  • internal/client/token.go
  • charts/templates/_helpers.tpl
  • internal/config/config_test.go
  • test/integration/integration_test.go
  • cmd/sentinel/main.go
  • internal/client/token_test.go
  • internal/sentinel/sentinel_test.go
  • charts/values.yaml
  • internal/client/client.go
  • internal/client/client_test.go
  • internal/config/config.go
  • charts/templates/configmap.yaml

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added optional JWT-based authentication for the HyperFleet API client (disabled by default).
    • When enabled, the chart mounts a projected service-account token and the client sends it as a Bearer authorization header, with configurable audience, token path, expiration, and in-memory token caching.
  • Bug Fixes
    • Improved behavior when the token can’t be read: requests fail with non-retriable “bearer token unavailable” errors, and chart/config validation prevents invalid settings (e.g., missing or non-absolute token paths).
  • Documentation
    • Updated Helm values, chart configuration, and example configs to document the new authentication options.
  • Tests
    • Expanded coverage for authorization header injection and token-file read/caching behavior.

Walkthrough

Optional JWT auth was added for the HyperFleet API client. Helm values, templates, and examples now include auth.enabled, tokenPath, audience, expirationSeconds, and tokenCacheTtl, with validation that tokenPath is absolute. Sentinel config now validates and passes auth settings through to the client. The client reads a bearer token from a file, caches it by TTL, and adds Authorization: Bearer on requests. Call sites and tests were updated.

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

Sequence Diagram(s)

sequenceDiagram
  participant Sentinel as runServe
  participant Client as NewHyperFleetClient
  participant Token as fileTokenSource
  participant Disk as token file
  participant API as HyperFleetAPI

  Sentinel->>Client: tokenPath, tokenCacheTTL
  Client->>Token: newFileTokenSource() when tokenPath is set
  Client->>Token: get()
  alt cache hit
    Token-->>Client: cached token
  else cache miss or disabled
    Token->>Disk: read token file
    Disk-->>Token: trimmed token
    Token-->>Client: token
  end
  Client->>API: Authorization: Bearer token
Loading

CWE-306: auth stays optional and disabled by default.

CWE-522: bearer material is loaded from a projected service-account token file.

CWE-209: token read failures return errors without exposing token contents.

🚥 Pre-merge checks | ✅ 11
✅ Passed checks (11 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: adding JWT auth support for the HyperFleet API client.
Description check ✅ Passed The description is directly aligned with the code changes and explains the auth, caching, Helm, and env var updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Sec-02: Secrets In Log Output ✅ Passed No CWE-532 issue: direct sweep found no non-test, non-example slog/log/zap/fmt.Print* calls interpolating token/password/credential/secret.
No Hardcoded Secrets ✅ Passed No hardcoded credentials (CWE-798/CWE-321). The new auth path reads tokens from disk; added docs/examples only use placeholders and a mount path.
No Weak Cryptography ✅ Passed PASS: No CWE-327/CWE-208 issues; touched code adds bearer-token file I/O and Authorization headers only, with no md5/des/rc4/sha1/ECB or secret comparisons.
No Injection Vectors ✅ Passed No CWE-89/78/79/502 vectors found in changed code: no exec.Command, template.HTML, or yaml.Unmarshal; config uses UnmarshalExact and tokenPath is validated.
No Privileged Containers ✅ Passed No new privileged settings in changed Helm templates/Dockerfiles; runtime stays non-root with allowPrivilegeEscalation=false. No CWE-250/CWE-269 issue introduced.
No Pii Or Sensitive Data In Logs ✅ Passed PASS: Touched logs/prints emit only redacted config or non-sensitive status/errors; no request/response bodies, PII, tokens, or credentialed hostnames found (CWE-532).
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

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

🧹 Nitpick comments (2)
internal/client/token_test.go (1)

141-178: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No test for token-retrieval failure propagating through FetchResources.

client.go's request editor wraps token errors as "bearer token unavailable: %w", but no test exercises this — e.g. a missing/unreadable token file at request time. As per path instructions, "Error paths SHOULD be tested, not just happy paths."

✅ Proposed additional test
func TestNewHyperFleetClient_BearerTokenError_WhenTokenFileMissing(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Fatal("server should not be called when token retrieval fails")
	}))
	defer server.Close()

	c, err := NewHyperFleetClient(server.URL, 10*time.Second, "test-sentinel", "test", "/nonexistent/path/token", 0)
	if err != nil {
		t.Fatalf("NewHyperFleetClient: %v", err)
	}

	if _, err := c.FetchResources(context.Background(), ResourceTypeClusters, nil); err == nil {
		t.Fatal("expected error when token file is missing")
	}
}
🤖 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 `@internal/client/token_test.go` around lines 141 - 178, Add a negative test
for token retrieval failure in `internal/client/token_test.go` to cover the
`requestEditor`/`FetchResources` path when the bearer token cannot be read.
Create a test like
`TestNewHyperFleetClient_BearerTokenError_WhenTokenFileMissing` that uses
`NewHyperFleetClient` with a nonexistent token file, calls `FetchResources`, and
asserts an error is returned. Also verify the test server is never hit so the
failure is clearly coming from token loading rather than the request itself.

Source: Path instructions

internal/client/token.go (1)

28-62: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Exclusive lock taken even when caching is disabled.

When cacheTTL == 0, no shared field is ever written, yet every call still takes the exclusive s.mu.Lock() at line 41 to read the file. This serializes all concurrent per-request token reads under the "no-cache" mode — the opposite of what you want for a hot path hit on every outbound API call.

♻️ Proposed fix: bypass locking when caching is disabled
 func (s *fileTokenSource) get() (string, error) {
+	if s.cacheTTL == 0 {
+		return s.readToken()
+	}
+
 	now := time.Now().UnixNano()
-
-	if s.cacheTTL > 0 {
-		s.mu.RLock()
-		if now < s.expiresAt {
-			tok := s.cached
-			s.mu.RUnlock()
-			return tok, nil
-		}
-		s.mu.RUnlock()
-	}
+	s.mu.RLock()
+	if now < s.expiresAt {
+		tok := s.cached
+		s.mu.RUnlock()
+		return tok, nil
+	}
+	s.mu.RUnlock()
 
 	s.mu.Lock()
 	defer s.mu.Unlock()
-	// Re-check under write lock — another goroutine may have refreshed.
-	if s.cacheTTL > 0 && now < s.expiresAt {
+	// Re-check under write lock — another goroutine may have refreshed.
+	if now < s.expiresAt {
 		return s.cached, nil
 	}
 
-	raw, err := os.ReadFile(s.path)
-	if err != nil {
-		return "", fmt.Errorf("reading token file %s: %w", s.path, err)
-	}
-	tok := strings.TrimSpace(string(raw))
-	if tok == "" {
-		return "", fmt.Errorf("token file %s is empty", s.path)
-	}
-
-	if s.cacheTTL > 0 {
-		s.cached = tok
-		s.expiresAt = time.Now().Add(s.cacheTTL).UnixNano()
-	}
+	tok, err := s.readToken()
+	if err != nil {
+		return "", err
+	}
+	s.cached = tok
+	s.expiresAt = time.Now().Add(s.cacheTTL).UnixNano()
 	return tok, nil
 }
+
+func (s *fileTokenSource) readToken() (string, error) {
+	raw, err := os.ReadFile(s.path)
+	if err != nil {
+		return "", fmt.Errorf("reading token file %s: %w", s.path, err)
+	}
+	tok := strings.TrimSpace(string(raw))
+	if tok == "" {
+		return "", fmt.Errorf("token file %s is empty", s.path)
+	}
+	return tok, nil
+}
🤖 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 `@internal/client/token.go` around lines 28 - 62, The fileTokenSource.get
method still takes s.mu.Lock even when cacheTTL is disabled, which unnecessarily
serializes concurrent token reads on the hot path. Update get to bypass both the
read and write lock paths entirely when cacheTTL == 0, while still preserving
the existing cache validation and refresh logic for the cached case. Keep the
changes localized to fileTokenSource.get and the s.mu/s.cached/s.expiresAt
handling so the no-cache mode reads the token file without locking.
🤖 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 `@charts/templates/deployment.yaml`:
- Around line 125-133: The projected service account token in the
hyperfleet-api-token volume is using Kubernetes’ default world-readable
permissions, so tighten it by setting an explicit defaultMode on the projected
volume in the deployment template. Update the hyperfleetApi auth block in the
deployment.yaml template near the serviceAccountToken source so the token file
is mounted with restricted permissions instead of inheriting 0644, keeping the
existing audience, expirationSeconds, and tokenPath settings intact.
- Around line 111-115: The Hyperfleet API token volume mount uses
`.Values.config.clients.hyperfleetApi.auth.tokenPath | dir`, which can turn a
bare filename into an invalid mount path and reject the Pod spec. Update the
validation in `internal/config/config.go` to require an absolute `tokenPath`
when `config.clients.hyperfleetApi.auth.enabled` is set, and make sure the
`charts/templates/deployment.yaml` mountPath logic assumes that validated
absolute path instead of deriving a directory from a relative file name.

In `@internal/client/client.go`:
- Around line 52-57: The doc comment on NewHyperFleetClient contradicts the
actual zero-TTL behavior in fileTokenSource.get: tokenCacheTTL of 0 does not use
a 30s default, it disables caching and re-reads the token file on every request.
Update the comment on NewHyperFleetClient and the tokenCacheTTL description to
match the implementation in internal/client/token.go, so operators get the
correct expectation for cache behavior.

In `@internal/config/config.go`:
- Around line 90-99: Add table-driven tests for HyperFleetAPIAuthConfig.Validate
to cover the new auth validation path used by SentinelConfig.Validate. Include
cases for a missing token_path, a negative token_cache_ttl, and a valid auth
configuration, and assert the expected errors or success so the CWE-20 boundary
checks remain covered.

In `@internal/sentinel/sentinel_test.go`:
- Around line 165-166: `client.NewHyperFleetClient(...)` is being called with
its error discarded at several `sentinel_test.go` call sites, which can hide
setup failures and later cause nil-pointer panics in `NewSentinel` or `trigger`.
Update each affected test to check the returned error explicitly (using the same
pattern already used in `TestTrigger_CreatesRequiredSpans`) and fail the test
immediately if client construction fails, so the failure is reported at the call
site instead of downstream.

In `@test/integration/integration_test.go`:
- Line 171: The constructor error from client.NewHyperFleetClient is being
ignored at multiple call sites in this test file, which can hide client setup
failures and lead to nil-pointer panics. Update each affected call site to
handle the returned error the same way as TestIntegration_EndToEndSpanHierarchy
does: capture the error, fail the test immediately if construction fails, and
keep the existing client variable usage only after successful initialization.

---

Nitpick comments:
In `@internal/client/token_test.go`:
- Around line 141-178: Add a negative test for token retrieval failure in
`internal/client/token_test.go` to cover the `requestEditor`/`FetchResources`
path when the bearer token cannot be read. Create a test like
`TestNewHyperFleetClient_BearerTokenError_WhenTokenFileMissing` that uses
`NewHyperFleetClient` with a nonexistent token file, calls `FetchResources`, and
asserts an error is returned. Also verify the test server is never hit so the
failure is clearly coming from token loading rather than the request itself.

In `@internal/client/token.go`:
- Around line 28-62: The fileTokenSource.get method still takes s.mu.Lock even
when cacheTTL is disabled, which unnecessarily serializes concurrent token reads
on the hot path. Update get to bypass both the read and write lock paths
entirely when cacheTTL == 0, while still preserving the existing cache
validation and refresh logic for the cached case. Keep the changes localized to
fileTokenSource.get and the s.mu/s.cached/s.expiresAt handling so the no-cache
mode reads the token file without locking.
🪄 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: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 2a8c06cb-a8e1-4b9f-b428-3d53b3daf585

📥 Commits

Reviewing files that changed from the base of the PR and between 8ca2c8f and a32217e.

📒 Files selected for processing (15)
  • charts/README.md
  • charts/templates/configmap.yaml
  • charts/templates/deployment.yaml
  • charts/values.yaml
  • cmd/sentinel/main.go
  • configs/dev-example.yaml
  • configs/gcp-pubsub-example.yaml
  • configs/rabbitmq-example.yaml
  • internal/client/client.go
  • internal/client/client_test.go
  • internal/client/token.go
  • internal/client/token_test.go
  • internal/config/config.go
  • internal/sentinel/sentinel_test.go
  • test/integration/integration_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift-hyperfleet/architecture (manual)
  • openshift-hyperfleet/hyperfleet-api (manual)
  • openshift-hyperfleet/hyperfleet-sentinel (manual)
  • openshift-hyperfleet/hyperfleet-adapter (manual)
  • openshift-hyperfleet/hyperfleet-broker (manual)

Comment thread charts/templates/deployment.yaml
Comment thread charts/templates/deployment.yaml
Comment thread internal/client/client_test.go Outdated
Comment thread internal/client/client.go
Comment thread internal/config/config.go
Comment thread internal/sentinel/sentinel_test.go Outdated
Comment thread test/integration/integration_test.go Outdated

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

♻️ Duplicate comments (1)
internal/client/client.go (1)

52-54: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Doc comment still misrepresents zero-TTL caching behavior.

This exact text was flagged in a prior review round and remains unfixed. NewHyperFleetClient doc says "0 uses the default (30s)", but fileTokenSource.get() in internal/client/token.go treats cacheTTL == 0 as "no caching — read the file on every request" — there's no 30s default anywhere in the implementation. An operator relying on this comment will misconfigure token_cache_ttl: 0 expecting a 30s cache and instead get a disk read every poll cycle.

📝 Proposed fix
 // tokenPath is optional; when non-empty the client reads a bearer token from that file and
 // injects it as an Authorization header on every request. tokenCacheTTL controls how long
-// the token is cached before the file is re-read; 0 uses the default (30s).
+// the token is cached before the file is re-read; 0 disables caching, causing the file to
+// be re-read on every request.
🤖 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 `@internal/client/client.go` around lines 52 - 54, Update the documentation for
NewHyperFleetClient so it matches the actual zero-TTL behavior in
fileTokenSource.get(): tokenCacheTTL == 0 means the token is never cached and
the file is re-read on every request, not that a 30s default is used. Adjust the
comment near the tokenPath/tokenCacheTTL description to remove the incorrect
default wording and clearly describe the real caching semantics.
🧹 Nitpick comments (1)
internal/client/client.go (1)

300-323: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Auth-header injection logic duplicated verbatim in two call sites.

VerifyConnectivity (Lines 317-323) and fetchResourcesPage (Lines 451-457) both re-implement "check tokenSource, fetch token, set header, wrap error" identically except for the error type returned. Any future change to auth logic (e.g. adding retry-on-token-error, token refresh, or scoping headers) has to be kept in sync in two places.

♻️ Proposed fix: extract shared helper
func (c *HyperFleetClient) setAuthHeader(req *http.Request) error {
	if c.tokenSource == nil {
		return nil
	}
	tok, err := c.tokenSource.get()
	if err != nil {
		return fmt.Errorf("bearer token unavailable: %w", err)
	}
	req.Header.Set("Authorization", "Bearer "+tok)
	return nil
}
 	req.Header.Set("User-Agent", c.userAgent)
-	if c.tokenSource != nil {
-		tok, err := c.tokenSource.get()
-		if err != nil {
-			return fmt.Errorf("bearer token unavailable: %w", err)
-		}
-		req.Header.Set("Authorization", "Bearer "+tok)
-	}
+	if err := c.setAuthHeader(req); err != nil {
+		return fmt.Errorf("could not verify connectivity: %w", err)
+	}
 	req.Header.Set("User-Agent", c.userAgent)
-	if c.tokenSource != nil {
-		tok, err := c.tokenSource.get()
-		if err != nil {
-			return nil, 0, &APIError{StatusCode: 0, Message: fmt.Sprintf("bearer token unavailable: %v", err), Retriable: false}
-		}
-		req.Header.Set("Authorization", "Bearer "+tok)
-	}
+	if err := c.setAuthHeader(req); err != nil {
+		return nil, 0, &APIError{StatusCode: 0, Message: err.Error(), Retriable: false}
+	}

Also applies to: 437-457

🤖 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 `@internal/client/client.go` around lines 300 - 323, The auth-header injection
logic is duplicated in VerifyConnectivity and fetchResourcesPage, so extract it
into a shared helper on HyperFleetClient such as setAuthHeader and use it from
both call sites. Move the tokenSource nil check, token retrieval, Authorization
header assignment, and token error wrapping into that helper, then keep the
existing request-specific error handling in VerifyConnectivity and
fetchResourcesPage by calling the helper before the request is sent.
🤖 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 `@charts/README.md`:
- Line 58: The inline config default in the README table is being parsed as
extra columns because the JSON contains the messageDecision expression with ||.
Update the config cell in the values table to escape the pipe characters or
otherwise move the full default object out of the table so the row stays intact.
Use the existing config, messageDecision, and auth-enabled default entry as the
location to fix.

In `@internal/client/client.go`:
- Around line 399-435: fetchPaginated currently relies only on total and empty
pages to stop, so if the API misreports counts or ignores page it can loop
indefinitely and keep growing allResources. Add an independent hard limit on
pagination in fetchPaginated (using the existing page loop and c.pageSize), and
fail closed or return an error when that ceiling is reached or when the page
sequence looks inconsistent. Keep the fix localized to fetchPaginated and
preserve the existing fetchPage, convert, and resourceLabel flow.

---

Duplicate comments:
In `@internal/client/client.go`:
- Around line 52-54: Update the documentation for NewHyperFleetClient so it
matches the actual zero-TTL behavior in fileTokenSource.get(): tokenCacheTTL ==
0 means the token is never cached and the file is re-read on every request, not
that a 30s default is used. Adjust the comment near the tokenPath/tokenCacheTTL
description to remove the incorrect default wording and clearly describe the
real caching semantics.

---

Nitpick comments:
In `@internal/client/client.go`:
- Around line 300-323: The auth-header injection logic is duplicated in
VerifyConnectivity and fetchResourcesPage, so extract it into a shared helper on
HyperFleetClient such as setAuthHeader and use it from both call sites. Move the
tokenSource nil check, token retrieval, Authorization header assignment, and
token error wrapping into that helper, then keep the existing request-specific
error handling in VerifyConnectivity and fetchResourcesPage by calling the
helper before the request is sent.
🪄 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: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: b685e0a2-f16c-44bf-9569-887d66c4daf1

📥 Commits

Reviewing files that changed from the base of the PR and between a32217e and 772aaa2.

📒 Files selected for processing (15)
  • charts/README.md
  • charts/templates/configmap.yaml
  • charts/templates/deployment.yaml
  • charts/values.yaml
  • cmd/sentinel/main.go
  • configs/dev-example.yaml
  • configs/gcp-pubsub-example.yaml
  • configs/rabbitmq-example.yaml
  • internal/client/client.go
  • internal/client/client_test.go
  • internal/client/token.go
  • internal/client/token_test.go
  • internal/config/config.go
  • internal/sentinel/sentinel_test.go
  • test/integration/integration_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift-hyperfleet/architecture (manual)
  • openshift-hyperfleet/hyperfleet-api (manual)
  • openshift-hyperfleet/hyperfleet-sentinel (manual)
  • openshift-hyperfleet/hyperfleet-adapter (manual)
  • openshift-hyperfleet/hyperfleet-broker (manual)
✅ Files skipped from review due to trivial changes (3)
  • configs/gcp-pubsub-example.yaml
  • configs/rabbitmq-example.yaml
  • configs/dev-example.yaml
🚧 Files skipped from review as they are similar to previous changes (9)
  • charts/templates/deployment.yaml
  • cmd/sentinel/main.go
  • charts/templates/configmap.yaml
  • internal/config/config.go
  • internal/client/token_test.go
  • internal/client/client_test.go
  • internal/sentinel/sentinel_test.go
  • internal/client/token.go
  • charts/values.yaml

@hyperfleet-ci-bot

hyperfleet-ci-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

Risk Score: 5 — risk/high

Signal Detail Points
PR size 552 lines (>500) +2
Sensitive paths cmd/ +2
Test coverage Missing tests for: cmd/sentinel +1

Computed by hyperfleet-risk-scorer

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 2

♻️ Duplicate comments (1)
internal/client/client.go (1)

52-54: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Doc comment still misrepresents zero-TTL caching behavior.

This exact text was flagged in a prior review round and remains unfixed. NewHyperFleetClient doc says "0 uses the default (30s)", but fileTokenSource.get() in internal/client/token.go treats cacheTTL == 0 as "no caching — read the file on every request" — there's no 30s default anywhere in the implementation. An operator relying on this comment will misconfigure token_cache_ttl: 0 expecting a 30s cache and instead get a disk read every poll cycle.

📝 Proposed fix
 // tokenPath is optional; when non-empty the client reads a bearer token from that file and
 // injects it as an Authorization header on every request. tokenCacheTTL controls how long
-// the token is cached before the file is re-read; 0 uses the default (30s).
+// the token is cached before the file is re-read; 0 disables caching, causing the file to
+// be re-read on every request.
🤖 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 `@internal/client/client.go` around lines 52 - 54, Update the documentation for
NewHyperFleetClient so it matches the actual zero-TTL behavior in
fileTokenSource.get(): tokenCacheTTL == 0 means the token is never cached and
the file is re-read on every request, not that a 30s default is used. Adjust the
comment near the tokenPath/tokenCacheTTL description to remove the incorrect
default wording and clearly describe the real caching semantics.
🧹 Nitpick comments (1)
internal/client/client.go (1)

300-323: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Auth-header injection logic duplicated verbatim in two call sites.

VerifyConnectivity (Lines 317-323) and fetchResourcesPage (Lines 451-457) both re-implement "check tokenSource, fetch token, set header, wrap error" identically except for the error type returned. Any future change to auth logic (e.g. adding retry-on-token-error, token refresh, or scoping headers) has to be kept in sync in two places.

♻️ Proposed fix: extract shared helper
func (c *HyperFleetClient) setAuthHeader(req *http.Request) error {
	if c.tokenSource == nil {
		return nil
	}
	tok, err := c.tokenSource.get()
	if err != nil {
		return fmt.Errorf("bearer token unavailable: %w", err)
	}
	req.Header.Set("Authorization", "Bearer "+tok)
	return nil
}
 	req.Header.Set("User-Agent", c.userAgent)
-	if c.tokenSource != nil {
-		tok, err := c.tokenSource.get()
-		if err != nil {
-			return fmt.Errorf("bearer token unavailable: %w", err)
-		}
-		req.Header.Set("Authorization", "Bearer "+tok)
-	}
+	if err := c.setAuthHeader(req); err != nil {
+		return fmt.Errorf("could not verify connectivity: %w", err)
+	}
 	req.Header.Set("User-Agent", c.userAgent)
-	if c.tokenSource != nil {
-		tok, err := c.tokenSource.get()
-		if err != nil {
-			return nil, 0, &APIError{StatusCode: 0, Message: fmt.Sprintf("bearer token unavailable: %v", err), Retriable: false}
-		}
-		req.Header.Set("Authorization", "Bearer "+tok)
-	}
+	if err := c.setAuthHeader(req); err != nil {
+		return nil, 0, &APIError{StatusCode: 0, Message: err.Error(), Retriable: false}
+	}

Also applies to: 437-457

🤖 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 `@internal/client/client.go` around lines 300 - 323, The auth-header injection
logic is duplicated in VerifyConnectivity and fetchResourcesPage, so extract it
into a shared helper on HyperFleetClient such as setAuthHeader and use it from
both call sites. Move the tokenSource nil check, token retrieval, Authorization
header assignment, and token error wrapping into that helper, then keep the
existing request-specific error handling in VerifyConnectivity and
fetchResourcesPage by calling the helper before the request is sent.
🤖 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 `@charts/README.md`:
- Line 58: The inline config default in the README table is being parsed as
extra columns because the JSON contains the messageDecision expression with ||.
Update the config cell in the values table to escape the pipe characters or
otherwise move the full default object out of the table so the row stays intact.
Use the existing config, messageDecision, and auth-enabled default entry as the
location to fix.

In `@internal/client/client.go`:
- Around line 399-435: fetchPaginated currently relies only on total and empty
pages to stop, so if the API misreports counts or ignores page it can loop
indefinitely and keep growing allResources. Add an independent hard limit on
pagination in fetchPaginated (using the existing page loop and c.pageSize), and
fail closed or return an error when that ceiling is reached or when the page
sequence looks inconsistent. Keep the fix localized to fetchPaginated and
preserve the existing fetchPage, convert, and resourceLabel flow.

---

Duplicate comments:
In `@internal/client/client.go`:
- Around line 52-54: Update the documentation for NewHyperFleetClient so it
matches the actual zero-TTL behavior in fileTokenSource.get(): tokenCacheTTL ==
0 means the token is never cached and the file is re-read on every request, not
that a 30s default is used. Adjust the comment near the tokenPath/tokenCacheTTL
description to remove the incorrect default wording and clearly describe the
real caching semantics.

---

Nitpick comments:
In `@internal/client/client.go`:
- Around line 300-323: The auth-header injection logic is duplicated in
VerifyConnectivity and fetchResourcesPage, so extract it into a shared helper on
HyperFleetClient such as setAuthHeader and use it from both call sites. Move the
tokenSource nil check, token retrieval, Authorization header assignment, and
token error wrapping into that helper, then keep the existing request-specific
error handling in VerifyConnectivity and fetchResourcesPage by calling the
helper before the request is sent.
🪄 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: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: b685e0a2-f16c-44bf-9569-887d66c4daf1

📥 Commits

Reviewing files that changed from the base of the PR and between a32217e and 772aaa2.

📒 Files selected for processing (15)
  • charts/README.md
  • charts/templates/configmap.yaml
  • charts/templates/deployment.yaml
  • charts/values.yaml
  • cmd/sentinel/main.go
  • configs/dev-example.yaml
  • configs/gcp-pubsub-example.yaml
  • configs/rabbitmq-example.yaml
  • internal/client/client.go
  • internal/client/client_test.go
  • internal/client/token.go
  • internal/client/token_test.go
  • internal/config/config.go
  • internal/sentinel/sentinel_test.go
  • test/integration/integration_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift-hyperfleet/architecture (manual)
  • openshift-hyperfleet/hyperfleet-api (manual)
  • openshift-hyperfleet/hyperfleet-sentinel (manual)
  • openshift-hyperfleet/hyperfleet-adapter (manual)
  • openshift-hyperfleet/hyperfleet-broker (manual)
✅ Files skipped from review due to trivial changes (3)
  • configs/gcp-pubsub-example.yaml
  • configs/rabbitmq-example.yaml
  • configs/dev-example.yaml
🚧 Files skipped from review as they are similar to previous changes (9)
  • charts/templates/deployment.yaml
  • cmd/sentinel/main.go
  • charts/templates/configmap.yaml
  • internal/config/config.go
  • internal/client/token_test.go
  • internal/client/client_test.go
  • internal/sentinel/sentinel_test.go
  • internal/client/token.go
  • charts/values.yaml
🛑 Comments failed to post (2)
charts/README.md (1)

58-58: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Escape the pipes in the default JSON.

The inline config default contains ||, which Markdown parses as table separators. That breaks the generated values table at Line 58 (MD056), so the auth-enabled default is effectively unreadable. Escape the pipes (\|\|) or move this object out of the table. CWE-116.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 58-58: Table column count
Expected: 4; Actual: 10; Too many cells, extra data will be missing

(MD056, table-column-count)

🤖 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 `@charts/README.md` at line 58, The inline config default in the README table
is being parsed as extra columns because the JSON contains the messageDecision
expression with ||. Update the config cell in the values table to escape the
pipe characters or otherwise move the full default object out of the table so
the row stays intact. Use the existing config, messageDecision, and auth-enabled
default entry as the location to fix.

Source: Linters/SAST tools

internal/client/client.go (1)

399-435: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant file and nearby symbols first.
ast-grep outline internal/client/client.go --view expanded || true

printf '\n--- relevant lines ---\n'
sed -n '340,470p' internal/client/client.go

printf '\n--- page size / pagination usages ---\n'
rg -n "fetchPaginated|pageSize|total|Pagination|List.*Page" internal/client -n

Repository: openshift-hyperfleet/hyperfleet-sentinel

Length of output: 10365


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- fetchResourcesPage response shape ---'
sed -n '438,560p' internal/client/client.go

printf '\n%s\n' '--- pagination tests ---'
sed -n '1120,1295p' internal/client/client_test.go

printf '\n%s\n' '--- any other pagination safeguards ---'
rg -n "len\\(items\\) == 0|pageSize|totalResources|max page|maxPage|pagination" internal/client docs configs openapi -n

Repository: openshift-hyperfleet/hyperfleet-sentinel

Length of output: 10681


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- FetchResources call sites ---'
rg -n "FetchResources\\(" .

printf '\n%s\n' '--- context lifetime around resource polling ---'
rg -n "context.WithTimeout|context.WithDeadline|WithCancel|poll|fetchResourcesOnce|fetchResources\\(" internal cmd pkg .

Repository: openshift-hyperfleet/hyperfleet-sentinel

Length of output: 39559


Bound pagination with an independent hard capfetchPaginated only stops on len(allResources) >= total or an empty page. If the API misreports total or keeps returning non-empty pages while ignoring page, this can pin the poll cycle and grow allResources without limit (CWE-400). Add an independent page ceiling or fail closed on inconsistent pagination.

🤖 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 `@internal/client/client.go` around lines 399 - 435, fetchPaginated currently
relies only on total and empty pages to stop, so if the API misreports counts or
ignores page it can loop indefinitely and keep growing allResources. Add an
independent hard limit on pagination in fetchPaginated (using the existing page
loop and c.pageSize), and fail closed or return an error when that ceiling is
reached or when the page sequence looks inconsistent. Keep the fix localized to
fetchPaginated and preserve the existing fetchPage, convert, and resourceLabel
flow.

@rh-amarin rh-amarin force-pushed the 1237 branch 2 times, most recently from 61e5602 to 7c2dc6a Compare July 7, 2026 17:19

@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 `@charts/README.md`:
- Line 58: The generated values table row for the `config` entry contains an
unescaped CEL `||` in `messageDecision.result`, which breaks the Markdown table
layout. Update the `charts/README.md` source that produces this row so the `||`
characters are escaped or the default value is rewritten to avoid pipes, keeping
the table columns intact. Focus on the `config` documentation row and the
`messageDecision.result` default string so the rendered README stays valid.
🪄 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: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: f4ab46fd-e30c-4397-9de0-a3bcfaf58479

📥 Commits

Reviewing files that changed from the base of the PR and between 772aaa2 and 7c2dc6a.

📒 Files selected for processing (17)
  • charts/README.md
  • charts/templates/_helpers.tpl
  • charts/templates/configmap.yaml
  • charts/templates/deployment.yaml
  • charts/values.yaml
  • cmd/sentinel/main.go
  • configs/dev-example.yaml
  • configs/gcp-pubsub-example.yaml
  • configs/rabbitmq-example.yaml
  • internal/client/client.go
  • internal/client/client_test.go
  • internal/client/token.go
  • internal/client/token_test.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/sentinel/sentinel_test.go
  • test/integration/integration_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift-hyperfleet/architecture (manual)
  • openshift-hyperfleet/hyperfleet-api (manual)
  • openshift-hyperfleet/hyperfleet-sentinel (manual)
  • openshift-hyperfleet/hyperfleet-adapter (manual)
  • openshift-hyperfleet/hyperfleet-broker (manual)
✅ Files skipped from review due to trivial changes (3)
  • configs/rabbitmq-example.yaml
  • configs/gcp-pubsub-example.yaml
  • configs/dev-example.yaml
🚧 Files skipped from review as they are similar to previous changes (11)
  • charts/templates/deployment.yaml
  • cmd/sentinel/main.go
  • internal/sentinel/sentinel_test.go
  • internal/client/token.go
  • charts/templates/configmap.yaml
  • internal/config/config.go
  • internal/client/token_test.go
  • test/integration/integration_test.go
  • charts/values.yaml
  • internal/client/client.go
  • internal/client/client_test.go

Comment thread charts/README.md Outdated
Adds optional Kubernetes service account token authentication to the
HyperFleet API HTTP client. When configured, a bearer token is read
from a file and injected as an Authorization header on every request.

Token reading is backed by a configurable in-memory cache (TTL > 0)
or file-per-request mode (TTL == 0) using a sync.RWMutex with
double-checked locking for safe concurrent access.

The Helm chart gains an auth block under config.clients.hyperfleetApi
that mounts a projected ServiceAccountToken volume with a configurable
audience, token path, expiration, and cache TTL. Auth is disabled by
default; existing deployments are unaffected.

Co-Authored-By: Claude <noreply@anthropic.com>
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.

1 participant