Skip to content

HYPERLFEET-1134 - feat: add configurable caller identity for audit attribution#187

Merged
openshift-merge-bot[bot] merged 3 commits into
openshift-hyperfleet:mainfrom
rh-amarin:audit-info
Jun 1, 2026
Merged

HYPERLFEET-1134 - feat: add configurable caller identity for audit attribution#187
openshift-merge-bot[bot] merged 3 commits into
openshift-hyperfleet:mainfrom
rh-amarin:audit-info

Conversation

@rh-amarin
Copy link
Copy Markdown
Contributor

@rh-amarin rh-amarin commented May 28, 2026

Summary

Adds configurable caller identity for audit attribution (created_by, updated_by, force-delete logs) using eiter:

  • Claims from JWT token server.jwt.identity_claim
  • Customizable header server.identity_header

Header wins over JWT identity

When either identity method is configured, identity becomes mandatory for requests, or it will return error 401

Auth naming is clarified: JWTMiddleware / AuthenticateAccountJWT become CallerIdentityMiddleware / ResolveCallerIdentity, since that layer only resolves identity—not validates tokens.

These are the tests created that explain the expected behavior:

Unit tests (pkg/auth/identity_test.go)

TestCallerIdentityFromRequest

  • header overrides JWT claim — header value wins when both JWT and header are present
  • falls back to JWT when header absent — resolves from JWT claim when header not sent
  • rejects invalid header value — control characters in header → error
  • rejects header value exceeding max length — >256 chars → error
  • header only without JWT claim — resolves from header when no JWT claim configured
  • no resolution when nothing configured — returns empty when no identity source set
  • empty header falls back to JWT — empty header value falls back to JWT claim
  • whitespace-only header falls back to JWT — whitespace-trimmed empty falls back to JWT
  • header at exact max length accepted — 256-char header is valid (boundary)

TestNewCallerIdentityMiddleware

  • rejects forbidden header name — "Authorization" as header name → error
  • returns middleware when header validation passes — valid header name accepted
  • returns middleware with no config — empty config is valid

TestResolveCallerIdentityMiddleware

  • skips openapi paths — no enforcement on /openapi endpoints
  • allows GET without identity — read requests pass through without identity
  • returns 401 on POST without identity — mutating request rejected (JWT configured)
  • returns 401 on PATCH without identity — mutating request rejected (header configured)
  • returns 401 on DELETE without identity — mutating request rejected
  • returns 401 on PUT without identity — mutating request rejected
  • allows POST with identity from header — POST succeeds when header provides identity
  • returns 401 on POST with oversized header — >256 char header rejected through middleware
  • POST with empty header falls back to JWT — empty header + JWT claims → identity from JWT
  • header identity takes precedence over JWT on POST — header wins on mutating request

Integration tests (test/integration/caller_identity_test.go)

TestCallerIdentityCreate

  • header present overrides JWT — created_by from header when both present
  • header absent uses JWT claim — created_by from JWT email when no header

TestCallerIdentityPatch

  • patch with header updates updated_by — updated_by set from header, created_by unchanged
  • patch without header uses JWT identity — updated_by set from JWT email

TestCallerIdentityMultiplePatches

  • create as user-a, patch as user-b (JWT), patch as user-c (header); verifies created_by never changes, updated_by updates each time, generation increments 1→2→3, GET confirms persisted state

TestCallerIdentityDelete

  • create with header identity, soft-delete, verify deleted_by reflects caller identity and created_by is preserved

TestCallerIdentityEmptyHeaderFallback

  • create with empty identity header, verify fallback to JWT email for created_by and updated_by

TestCallerIdentityOversizedHeader

  • create with 257-char header → 401 rejected

@openshift-ci openshift-ci Bot requested review from crizzo71 and mbrudnoy May 28, 2026 12:30
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

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
📝 Walkthrough

Walkthrough

This PR implements header-first caller identity resolution for audit attribution with JWT-claim fallback, adds configuration/flags/env bindings and docs, introduces CallerIdentityMiddleware and header-name validation, shifts routing to rely on a global ResolveCallerIdentity middleware (route registration signatures removed), updates services to use actorFromContext for CreatedBy/UpdatedBy, and adds unit and integration tests plus Helm/chart updates.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant apiV1Router
  participant ResolveCallerIdentity
  participant CallerIdentityFromRequest
  participant GetIdentityFromContext
  participant ClusterService
  participant DB
  Client->>apiV1Router: HTTP request (JWT + optional identity header)
  apiV1Router->>ResolveCallerIdentity: middleware invocation
  ResolveCallerIdentity->>CallerIdentityFromRequest: read configured header (if enabled)
  alt header present and valid
    CallerIdentityFromRequest-->>ResolveCallerIdentity: header identity
  else header absent
    CallerIdentityFromRequest->>GetIdentityFromContext: extract configured JWT claim
    GetIdentityFromContext-->>CallerIdentityFromRequest: claim value or error
  end
  ResolveCallerIdentity->>ClusterService: downstream handler with username in context
  ClusterService->>DB: create cluster with CreatedBy = actorFromContext(ctx)
Loading

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main change: adding configurable caller identity for audit attribution. It is concise and specific.
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 JWT tokens, identity header values, passwords, or credentials are logged in any non-test files. Error messages use safe placeholders and generic descriptions without exposing sensitive data.
Description check ✅ Passed PR description clearly relates to changeset: adds configurable caller identity for audit attribution via JWT claims and optional HTTP header, with middleware renamed for clarity.

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

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

@hyperfleet-ci-bot
Copy link
Copy Markdown

hyperfleet-ci-bot Bot commented May 28, 2026

Risk Score: 5 — risk/high

Signal Detail Points
PR size 1382 lines (>500) +2
Sensitive paths cmd/ +2
Test coverage Missing tests for: cmd/hyperfleet-api/environments cmd/hyperfleet-api/server pkg/services plugins/channels plugins/clusters plugins/nodePools plugins/versions test +1

Computed by hyperfleet-risk-scorer

Copy link
Copy Markdown

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/config/server_test.go (1)

9-75: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Convert these multi-case validations to a table-driven test.

Both TestJWTConfig_Validate and TestIdentityHeaderConfig_Validate now contain multiple scenarios; converting to table-driven structure will reduce repetition and make new cases safer to add consistently.

As per coding guidelines **/*_test.go: "Table-driven tests used for multiple cases".

🤖 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 `@pkg/config/server_test.go` around lines 9 - 75, The tests
TestJWTConfig_Validate and TestIdentityHeaderConfig_Validate are written as many
separate subtests; convert them into table-driven tests to reduce repetition and
follow the repository guideline. Replace the multiple t.Run cases in
TestJWTConfig_Validate with a single loop over a slice of structs (name, cfg
JWTConfig, wantErr bool, wantMsg string) that calls cfg.Validate() and asserts
accordingly (use JWTConfig.Validate as the target), and do the same for
TestIdentityHeaderConfig_Validate using a slice of structs (name, cfg
IdentityHeaderConfig, wantErr bool, wantMsg string) that invokes
IdentityHeaderConfig.Validate; keep the existing case names and expected
assertions (ContainSubstring checks) as the table entries so behavior remains
identical.
🤖 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/values.yaml`:
- Around line 57-59: The new configurable identity header (identity_header.name
/ X-HyperFleet-Identity) must be added to the default masking list so identity
values are not logged; update the default config key
config.logging.masking.headers to include "X-HyperFleet-Identity" (or reference
identity_header.name dynamically where configuration is composed) so
header-based attribution will be masked by default.

In `@pkg/auth/context_test.go`:
- Around line 11-52: Refactor TestGetIdentityFromContext into a table-driven
test: replace the four separate t.Run blocks with a single test table (slice of
structs) containing fields like name, claims (use
contextWithClaims(jwt.MapClaims{...})), identityField (the configured claim
string), want (expected identity string) and wantErr (bool); then iterate over
the table calling t.Run(tc.name, func(t *testing.T) { RegisterTestingT(t);
identity, err := GetIdentityFromContext(ctx, tc.identityField); assert expected
results depending on tc.wantErr and tc.want (use Expect/ContainSubstring for
error text when wantErr is true) }); ensure you reference and reuse
contextWithClaims and GetIdentityFromContext and preserve all existing case
expectations (including fallback/default behavior and error containing the
missing claim).

In `@pkg/auth/identity_test.go`:
- Around line 12-81: Add a test that asserts
normalizeIdentityHeaderValue/CallerIdentityFromRequest rejects header values
longer than maxCallerIdentityLen (256). In the TestCallerIdentityFromRequest
suite add a new t.Run case that constructs a request with Header
"X-HyperFleet-Identity" set to a string of length 257, uses a
CallerIdentityConfig with HeaderEnabled true and HeaderName
"X-HyperFleet-Identity", calls CallerIdentityFromRequest(r.Context(), r, cfg)
and expects an error (Expect(err).To(HaveOccurred())). This verifies the max
length boundary enforced by normalizeIdentityHeaderValue.

---

Outside diff comments:
In `@pkg/config/server_test.go`:
- Around line 9-75: The tests TestJWTConfig_Validate and
TestIdentityHeaderConfig_Validate are written as many separate subtests; convert
them into table-driven tests to reduce repetition and follow the repository
guideline. Replace the multiple t.Run cases in TestJWTConfig_Validate with a
single loop over a slice of structs (name, cfg JWTConfig, wantErr bool, wantMsg
string) that calls cfg.Validate() and asserts accordingly (use
JWTConfig.Validate as the target), and do the same for
TestIdentityHeaderConfig_Validate using a slice of structs (name, cfg
IdentityHeaderConfig, wantErr bool, wantMsg string) that invokes
IdentityHeaderConfig.Validate; keep the existing case names and expected
assertions (ContainSubstring checks) as the table entries so behavior remains
identical.
🪄 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: ASSERTIVE

Plan: Enterprise

Run ID: ea0bcb02-ba3c-472a-9bea-f398472f6da5

📥 Commits

Reviewing files that changed from the base of the PR and between 04d5ee4 and ec8ad4d.

📒 Files selected for processing (28)
  • charts/templates/configmap.yaml
  • charts/values.yaml
  • cmd/hyperfleet-api/environments/e_integration_testing.go
  • cmd/hyperfleet-api/environments/types.go
  • cmd/hyperfleet-api/server/routes.go
  • configs/config.yaml.example
  • docs/authentication.md
  • docs/config.md
  • pkg/auth/auth_middleware.go
  • pkg/auth/auth_middleware_mock.go
  • pkg/auth/context.go
  • pkg/auth/context_test.go
  • pkg/auth/identity.go
  • pkg/auth/identity_test.go
  • pkg/config/flags.go
  • pkg/config/loader.go
  • pkg/config/loader_test.go
  • pkg/config/logging.go
  • pkg/config/server.go
  • pkg/config/server_test.go
  • pkg/services/cluster.go
  • pkg/services/node_pool.go
  • pkg/services/util.go
  • plugins/clusters/plugin.go
  • plugins/nodePools/plugin.go
  • test/helper.go
  • test/integration/caller_identity_test.go
  • test/integration/integration_test.go
💤 Files with no reviewable changes (1)
  • pkg/auth/auth_middleware_mock.go

Comment thread charts/values.yaml Outdated
Comment thread pkg/auth/context_test.go
Copy link
Copy Markdown

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/config/server_test.go (1)

9-43: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use table-driven tests for these multi-scenario validation suites.

Both validation tests encode multiple cases and should be table-driven to keep config validation coverage concise and easier to extend safely.

As per coding guidelines **/*_test.go: "Table-driven tests used for multiple cases".

Also applies to: 45-75

🤖 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 `@pkg/config/server_test.go` around lines 9 - 43, TestJWTConfig_Validate
currently repeats multiple subtests; convert it to a single table-driven test by
defining a slice of cases (struct with name, cfg JWTConfig, wantError bool,
wantErrContains string) and loop over them calling t.Run(case.name, func(t
*testing.T) { ... }); inside each iteration call cfg.Validate(), assert success
or error using Expect based on wantError and, if expecting an error, check
err.Error() contains wantErrContains; remove the repeated RegisterTestingT calls
and consolidate the four existing subtests (including the cases described in
TestJWTConfig_Validate) into this table so new cases can be added easily.
♻️ Duplicate comments (1)
pkg/auth/context_test.go (1)

11-52: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Convert this multi-case test to a table-driven test.

This test has multiple scenarios and is still structured as repeated t.Run blocks, which makes extension and maintenance harder.

As per coding guidelines **/*_test.go: "Table-driven tests used for multiple cases".

🤖 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 `@pkg/auth/context_test.go` around lines 11 - 52, Replace the repeated t.Run
blocks in TestGetIdentityFromContext with a single table-driven loop: define a
slice of structs (fields: name, claims jwt.MapClaims, field string, want string,
wantErr bool, wantErrSubstring string) that covers the four scenarios, call
RegisterTestingT(t) once at the top, then range over cases and call
t.Run(tc.name, func(t *testing.T) { ctx := contextWithClaims(tc.claims); got,
err := GetIdentityFromContext(ctx, tc.field); if tc.wantErr {
Expect(err).To(HaveOccurred()); if tc.wantErrSubstring != "" {
Expect(err.Error()).To(ContainSubstring(tc.wantErrSubstring)) } } else {
Expect(err).NotTo(HaveOccurred()); Expect(got).To(Equal(tc.want)) } }); this
keeps references to GetIdentityFromContext and contextWithClaims for locating
the logic and removes the repeated RegisterTestingT calls.
🤖 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/values.yaml`:
- Around line 55-59: The Helm chart was not version-bumped and the release NOTES
were not updated to reflect the new identity header settings added in
values.yaml; update charts/Chart.yaml to increment the chart version (and
appVersion if applicable) and update charts/templates/NOTES.txt to document the
new config keys (config.server.jwt.identity_claim and
config.server.identity_header.enabled/name), state the default
(identity_header.enabled: false) and the default header name
(X-HyperFleet-Identity), and include a short note about how enabling it affects
incoming requests and how to override via values.yaml or --set.

In `@pkg/auth/auth_middleware.go`:
- Around line 21-29: NewCallerIdentityMiddleware currently lets callers set
HeaderName values that IdentityHeaderConfig.Validate() would reject (e.g.,
"Authorization"); update NewCallerIdentityMiddleware to enforce the same
validation: after setting cfg.JWTIdentityClaim default and before returning
callerIdentityMiddleware, if cfg.HeaderEnabled run the IdentityHeaderConfig
validation (or call IdentityHeaderConfig.Validate()) on the header settings and
return an error if validation fails (preserving the existing error message about
required name), so callers cannot bypass forbidden header names; ensure the
function still returns &callerIdentityMiddleware{cfg: cfg}, nil when validation
passes.

In `@pkg/auth/identity_test.go`:
- Around line 12-81: Refactor TestCallerIdentityFromRequest into a table-driven
test: replace the four separate t.Run blocks with a single slice of test cases
(each case including name, request setup values, ctx claims via
contextWithClaims / jwt.MapClaims when needed, CallerIdentityConfig fields,
expected identity or expected error) and iterate with for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) { ... }) }, calling CallerIdentityFromRequest
and asserting results; remove redundant RegisterTestingT calls and keep
references to CallerIdentityFromRequest, CallerIdentityConfig,
contextWithClaims, and jwt.MapClaims to build each case (header overrides JWT,
falls back to JWT, rejects invalid header, header only when JWT disabled).

---

Outside diff comments:
In `@pkg/config/server_test.go`:
- Around line 9-43: TestJWTConfig_Validate currently repeats multiple subtests;
convert it to a single table-driven test by defining a slice of cases (struct
with name, cfg JWTConfig, wantError bool, wantErrContains string) and loop over
them calling t.Run(case.name, func(t *testing.T) { ... }); inside each iteration
call cfg.Validate(), assert success or error using Expect based on wantError
and, if expecting an error, check err.Error() contains wantErrContains; remove
the repeated RegisterTestingT calls and consolidate the four existing subtests
(including the cases described in TestJWTConfig_Validate) into this table so new
cases can be added easily.

---

Duplicate comments:
In `@pkg/auth/context_test.go`:
- Around line 11-52: Replace the repeated t.Run blocks in
TestGetIdentityFromContext with a single table-driven loop: define a slice of
structs (fields: name, claims jwt.MapClaims, field string, want string, wantErr
bool, wantErrSubstring string) that covers the four scenarios, call
RegisterTestingT(t) once at the top, then range over cases and call
t.Run(tc.name, func(t *testing.T) { ctx := contextWithClaims(tc.claims); got,
err := GetIdentityFromContext(ctx, tc.field); if tc.wantErr {
Expect(err).To(HaveOccurred()); if tc.wantErrSubstring != "" {
Expect(err.Error()).To(ContainSubstring(tc.wantErrSubstring)) } } else {
Expect(err).NotTo(HaveOccurred()); Expect(got).To(Equal(tc.want)) } }); this
keeps references to GetIdentityFromContext and contextWithClaims for locating
the logic and removes the repeated RegisterTestingT calls.
🪄 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: ASSERTIVE

Plan: Enterprise

Run ID: 44eae421-c975-4040-adbf-3ae93496bc77

📥 Commits

Reviewing files that changed from the base of the PR and between 04d5ee4 and 7ef2cd4.

📒 Files selected for processing (28)
  • charts/templates/configmap.yaml
  • charts/values.yaml
  • cmd/hyperfleet-api/environments/e_integration_testing.go
  • cmd/hyperfleet-api/environments/types.go
  • cmd/hyperfleet-api/server/routes.go
  • configs/config.yaml.example
  • docs/authentication.md
  • docs/config.md
  • pkg/auth/auth_middleware.go
  • pkg/auth/auth_middleware_mock.go
  • pkg/auth/context.go
  • pkg/auth/context_test.go
  • pkg/auth/identity.go
  • pkg/auth/identity_test.go
  • pkg/config/flags.go
  • pkg/config/loader.go
  • pkg/config/loader_test.go
  • pkg/config/logging.go
  • pkg/config/server.go
  • pkg/config/server_test.go
  • pkg/services/cluster.go
  • pkg/services/node_pool.go
  • pkg/services/util.go
  • plugins/clusters/plugin.go
  • plugins/nodePools/plugin.go
  • test/helper.go
  • test/integration/caller_identity_test.go
  • test/integration/integration_test.go
💤 Files with no reviewable changes (1)
  • pkg/auth/auth_middleware_mock.go

Comment thread charts/values.yaml Outdated
Comment thread pkg/auth/auth_middleware.go
Comment thread pkg/auth/identity_test.go
Copy link
Copy Markdown

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

🤖 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 `@pkg/config/server.go`:
- Around line 8-9: The config package should not import auth; move the
IsForbiddenIdentityHeaderName function and forbiddenIdentityHeaderNames slice
out of pkg/auth into pkg/config (or a new shared pkg/validation) and update
usages accordingly so auth imports config/validation instead of the reverse;
locate the IsForbiddenIdentityHeaderName and forbiddenIdentityHeaderNames
declarations in your diff, copy them into the chosen package, remove the auth
import from the config package, and update all callers to reference the new
package name.

In `@test/integration/caller_identity_test.go`:
- Around line 24-70: Two near-duplicate tests
(TestCallerIdentityHeaderOverridesJWTOnCreate and
TestCallerIdentityFromJWTWhenHeaderAbsent) exercise the same create flow;
refactor them into a single table-driven test that iterates cases for "header
present" and "header absent". Replace the two test functions with one
TestCallerIdentityCreate table-driven test that builds the same clusterInput,
calls client.PostClusterWithResponse with test.WithAuthToken(ctx) and
conditionally adds test.WithIdentityHeader(test.IdentityHeaderName(),
headerActor) for the header-present case, then asserts resp.StatusCode() is
http.StatusCreated and resp.JSON201.CreatedBy equals either headerActor or
account.Email depending on the case; keep using helpers NewAccount,
NewAuthenticatedContext, PostClusterWithResponse, test.WithAuthToken and
test.WithIdentityHeader to locate code.
- Around line 1-2: This integration test file (package integration) needs the Go
build tag to prevent running during normal `go test`; add the line `//go:build
integration` at the very top of the file (and ensure a blank line after build
tag(s) before the `package integration` declaration); apply the same tag to
other `*_test.go` files in this integration suite if present so they are only
included when explicitly built with the integration tag.
🪄 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: ASSERTIVE

Plan: Enterprise

Run ID: 977ef133-5fee-4d28-86e6-5af1c40ebdab

📥 Commits

Reviewing files that changed from the base of the PR and between 7ef2cd4 and 4730885.

📒 Files selected for processing (30)
  • charts/Chart.yaml
  • charts/templates/NOTES.txt
  • charts/templates/configmap.yaml
  • charts/values.yaml
  • cmd/hyperfleet-api/environments/e_integration_testing.go
  • cmd/hyperfleet-api/environments/types.go
  • cmd/hyperfleet-api/server/routes.go
  • configs/config.yaml.example
  • docs/authentication.md
  • docs/config.md
  • pkg/auth/auth_middleware.go
  • pkg/auth/auth_middleware_mock.go
  • pkg/auth/context.go
  • pkg/auth/context_test.go
  • pkg/auth/identity.go
  • pkg/auth/identity_test.go
  • pkg/config/flags.go
  • pkg/config/loader.go
  • pkg/config/loader_test.go
  • pkg/config/logging.go
  • pkg/config/server.go
  • pkg/config/server_test.go
  • pkg/services/cluster.go
  • pkg/services/node_pool.go
  • pkg/services/util.go
  • plugins/clusters/plugin.go
  • plugins/nodePools/plugin.go
  • test/helper.go
  • test/integration/caller_identity_test.go
  • test/integration/integration_test.go
💤 Files with no reviewable changes (1)
  • pkg/auth/auth_middleware_mock.go

Comment thread pkg/config/server.go Outdated
Comment thread test/integration/caller_identity_test.go
Comment thread test/integration/caller_identity_test.go Outdated
Copy link
Copy Markdown

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/config/server.go (1)

73-84: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject whitespace-only identity config values.

Line 80 and Line 96 only reject ""; values like " " pass validation, which can silently break audit attribution resolution at runtime. Trim before validating required fields.

Suggested fix
 import (
 	"fmt"
 	"net"
 	"strconv"
+	"strings"
 	"time"

 	"github.com/openshift-hyperfleet/hyperfleet-api/pkg/validation"
 )
@@
 func (c *JWTConfig) Validate() error {
 	if !c.Enabled {
 		return nil
 	}
 	if c.IssuerURL == "" {
 		return fmt.Errorf("server.jwt.issuer_url is required when jwt is enabled")
 	}
-	if c.IdentityClaim == "" {
+	c.IdentityClaim = strings.TrimSpace(c.IdentityClaim)
+	if c.IdentityClaim == "" {
 		return fmt.Errorf("server.jwt.identity_claim is required when jwt is enabled")
 	}
 	return nil
 }
@@
 func (c *IdentityHeaderConfig) Validate() error {
 	if !c.Enabled {
 		return nil
 	}
-	if c.Name == "" {
+	c.Name = strings.TrimSpace(c.Name)
+	if c.Name == "" {
 		return fmt.Errorf("server.identity_header.name is required when identity_header is enabled")
 	}
 	if validation.IsForbiddenIdentityHeaderName(c.Name) {
 		return fmt.Errorf("server.identity_header.name %q is not allowed", c.Name)
 	}
 	return nil
 }
As per coding guidelines "Configuration changes affect all deployments. Review for: Validation rules in Validate() method".

Also applies to: 92-101

🤖 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 `@pkg/config/server.go` around lines 73 - 84, The Validate method on JWTConfig
currently only checks for empty strings, allowing whitespace-only values; update
JWTConfig.Validate to trim whitespace (e.g., use strings.TrimSpace) on
c.IssuerURL and c.IdentityClaim before testing emptiness so values like "   "
are rejected, and apply the same trimming-and-empty check pattern to the related
Validate block referenced (lines 92-101) for any other required JWT fields to
ensure whitespace-only config values fail validation.
♻️ Duplicate comments (1)
test/integration/caller_identity_test.go (1)

1-1: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add the integration build tag to prevent default test runs from executing this suite.

Line 1 is missing //go:build integration, so this integration test can run during normal go test.

Proposed fix
+//go:build integration
+
 package integration

As per coding guidelines **/*_test.go: Integration tests tagged with //go:build integration.

🤖 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 `@test/integration/caller_identity_test.go` at line 1, Add the integration
build tag to the top of caller_identity_test.go so it doesn't run in default `go
test`; insert `//go:build integration` as the very first line (and optionally
add the legacy `// +build integration` line for older Go toolchains) before the
`package integration` declaration.
🤖 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 `@pkg/config/server_test.go`:
- Around line 28-42: Replace the two separate t.Run validation cases for
JWTConfig with a single table-driven test: create a slice of test cases
(structs) describing name, JWTConfig fields (Enabled, IssuerURL, IdentityClaim)
and expected error presence/message, then loop over them with t.Run(case.name,
func(t *testing.T){ RegisterTestingT(t); err := case.cfg.Validate(); assert
expected outcome using Expect/ContainSubstring as needed }). Update the tests
referencing JWTConfig and Validate() (including the similar block around lines
45-73) to follow the same table-driven pattern so you have one clean loop that
covers all existing cases.

In `@pkg/validation/identity_header_test.go`:
- Around line 9-13: Replace the single-case TestIsForbiddenIdentityHeaderName
with a table-driven test: create a slice of cases each with a name, input
header, and expected bool, then iterate and call t.Run(case.name, func(t
*testing.T) { RegisterTestingT(t);
Expect(IsForbiddenIdentityHeaderName(case.header)).To(Equal(case.expected)) });
include the two scenarios ("authorization header" -> "Authorization" -> true and
"hyperfleet identity header" -> "X-HyperFleet-Identity" -> false) so failures
are reported per scenario and the test is easier to extend.

---

Outside diff comments:
In `@pkg/config/server.go`:
- Around line 73-84: The Validate method on JWTConfig currently only checks for
empty strings, allowing whitespace-only values; update JWTConfig.Validate to
trim whitespace (e.g., use strings.TrimSpace) on c.IssuerURL and c.IdentityClaim
before testing emptiness so values like "   " are rejected, and apply the same
trimming-and-empty check pattern to the related Validate block referenced (lines
92-101) for any other required JWT fields to ensure whitespace-only config
values fail validation.

---

Duplicate comments:
In `@test/integration/caller_identity_test.go`:
- Line 1: Add the integration build tag to the top of caller_identity_test.go so
it doesn't run in default `go test`; insert `//go:build integration` as the very
first line (and optionally add the legacy `// +build integration` line for older
Go toolchains) before the `package integration` declaration.
🪄 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: ASSERTIVE

Plan: Enterprise

Run ID: 30750a01-9819-4a0c-ad8d-99fbfe4dd1fe

📥 Commits

Reviewing files that changed from the base of the PR and between 4730885 and afaa531.

📒 Files selected for processing (32)
  • charts/Chart.yaml
  • charts/templates/NOTES.txt
  • charts/templates/configmap.yaml
  • charts/values.yaml
  • cmd/hyperfleet-api/environments/e_integration_testing.go
  • cmd/hyperfleet-api/environments/types.go
  • cmd/hyperfleet-api/server/routes.go
  • configs/config.yaml.example
  • docs/authentication.md
  • docs/config.md
  • pkg/auth/auth_middleware.go
  • pkg/auth/auth_middleware_mock.go
  • pkg/auth/context.go
  • pkg/auth/context_test.go
  • pkg/auth/identity.go
  • pkg/auth/identity_test.go
  • pkg/config/flags.go
  • pkg/config/loader.go
  • pkg/config/loader_test.go
  • pkg/config/logging.go
  • pkg/config/server.go
  • pkg/config/server_test.go
  • pkg/services/cluster.go
  • pkg/services/node_pool.go
  • pkg/services/util.go
  • pkg/validation/identity_header.go
  • pkg/validation/identity_header_test.go
  • plugins/clusters/plugin.go
  • plugins/nodePools/plugin.go
  • test/helper.go
  • test/integration/caller_identity_test.go
  • test/integration/integration_test.go
💤 Files with no reviewable changes (1)
  • pkg/auth/auth_middleware_mock.go

Comment thread pkg/config/server_test.go
Comment thread pkg/validation/identity_header_test.go
Copy link
Copy Markdown

@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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/auth/context_test.go`:
- Around line 50-53: The test helper function contextWithClaims in
pkg/auth/context_test.go is used by subtests but doesn't call t.Helper(), which
misattributes failures; update the helper signature used in the tests to call
t.Helper() at the top of the helper (inside the function body of
contextWithClaims or any local helper wrapper invoked by the subtests) so test
failures point to the caller rather than the helper.
🪄 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: 820c4701-7f71-4cd6-bb22-862347c671b4

📥 Commits

Reviewing files that changed from the base of the PR and between afaa531 and ae647b8.

📒 Files selected for processing (33)
  • Dockerfile
  • charts/Chart.yaml
  • charts/templates/NOTES.txt
  • charts/templates/configmap.yaml
  • charts/values.yaml
  • cmd/hyperfleet-api/environments/e_integration_testing.go
  • cmd/hyperfleet-api/environments/types.go
  • cmd/hyperfleet-api/server/routes.go
  • configs/config.yaml.example
  • docs/authentication.md
  • docs/config.md
  • pkg/auth/auth_middleware.go
  • pkg/auth/auth_middleware_mock.go
  • pkg/auth/context.go
  • pkg/auth/context_test.go
  • pkg/auth/identity.go
  • pkg/auth/identity_test.go
  • pkg/config/flags.go
  • pkg/config/loader.go
  • pkg/config/loader_test.go
  • pkg/config/logging.go
  • pkg/config/server.go
  • pkg/config/server_test.go
  • pkg/services/cluster.go
  • pkg/services/node_pool.go
  • pkg/services/util.go
  • pkg/validation/identity_header.go
  • pkg/validation/identity_header_test.go
  • plugins/clusters/plugin.go
  • plugins/nodePools/plugin.go
  • test/helper.go
  • test/integration/caller_identity_test.go
  • test/integration/integration_test.go
💤 Files with no reviewable changes (1)
  • pkg/auth/auth_middleware_mock.go
✅ Files skipped from review due to trivial changes (6)
  • charts/Chart.yaml
  • pkg/validation/identity_header_test.go
  • Dockerfile
  • docs/config.md
  • charts/values.yaml
  • pkg/config/loader_test.go
🚧 Files skipped from review as they are similar to previous changes (21)
  • cmd/hyperfleet-api/environments/e_integration_testing.go
  • pkg/config/flags.go
  • pkg/validation/identity_header.go
  • pkg/services/node_pool.go
  • pkg/config/loader.go
  • test/integration/integration_test.go
  • docs/authentication.md
  • cmd/hyperfleet-api/environments/types.go
  • charts/templates/configmap.yaml
  • test/helper.go
  • test/integration/caller_identity_test.go
  • pkg/services/cluster.go
  • pkg/services/util.go
  • configs/config.yaml.example
  • plugins/nodePools/plugin.go
  • pkg/config/server.go
  • plugins/clusters/plugin.go
  • pkg/auth/identity_test.go
  • pkg/auth/context.go
  • cmd/hyperfleet-api/server/routes.go
  • pkg/auth/identity.go

Comment thread pkg/auth/context_test.go
@rh-amarin rh-amarin force-pushed the audit-info branch 4 times, most recently from 677987f to ee2fd5b Compare June 1, 2026 13:36
@rh-amarin
Copy link
Copy Markdown
Contributor Author

/retest

ctx, w, r, errors.CodeAuthNoCredentials,
fmt.Sprintf("Unable to get payload details from JWT token: %s", err),
)
identity, err := CallerIdentityFromRequest(ctx, r, m.cfg)
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.

just a nit: err ignored here, but logically nothing broken

@rafabene
Copy link
Copy Markdown
Contributor

rafabene commented Jun 1, 2026

/lgtm

@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented Jun 1, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: rafabene

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

The pull request process is described 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

@openshift-ci openshift-ci Bot added the approved label Jun 1, 2026
@openshift-merge-bot openshift-merge-bot Bot merged commit c2ef3b3 into openshift-hyperfleet:main Jun 1, 2026
11 checks passed
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