Skip to content

ROSAENG-64874: fix: Cedar schema validation and ARN normalization for non-privileged accounts - #297

Open
cdoan1 wants to merge 2 commits into
openshift-online:mainfrom
cdoan1:ROSAENG-64874-fix-cedar-schema-validation
Open

ROSAENG-64874: fix: Cedar schema validation and ARN normalization for non-privileged accounts#297
cdoan1 wants to merge 2 commits into
openshift-online:mainfrom
cdoan1:ROSAENG-64874-fix-cedar-schema-validation

Conversation

@cdoan1

@cdoan1 cdoan1 commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Description

  • Fix Cedar schema validation: Remove additionalAttributes: true from rosa.cedarschema.json — Cedar's JSON schema
    format doesn't support it, causing validation failures
  • Normalize STS assumed-role ARNs: Convert arn:aws:sts::ACCT:assumed-role/Role/session to arn:aws:iam::ACCT:role/Role
    so that admin checks, group lookups, and Cedar policy evaluation match the IAM ARN stored at provisioning time
  • Require adminArn for non-privileged accounts: The account creation endpoint now validates that non-privileged
    accounts provide an initial admin ARN and auto-provisions it
  • Fix FIPS + custom endpoint conflict: Skip FIPS endpoint configuration when a custom DynamoDB endpoint is set
    (LocalStack), since the AWS SDK rejects the combination
  • Fix mock AVP template resolution: Handle both principal == ?principal and bare ?principal forms in Cedar template
    text
  • Clean up admins on account deletion: Prevent stale admin entries from persisting if an account is re-provisioned
  • Add local e2e authz test harness: Podman Compose setup with LocalStack (DynamoDB + API Gateway), Keycloak (OIDC), and
    a comprehensive Ginkgo test suite covering the full account lifecycle

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)
  • CI/CD or tooling change

Testing

  • Unit tests pass (make test)
  • Integration tests pass (if applicable)
  • Manual verification completed

Checklist

  • My code follows the project's coding conventions
  • I have updated documentation as needed
  • I have added tests that prove my fix/feature works
  • All new and existing tests pass

Summary by CodeRabbit

  • New Features

    • Account provisioning now supports an initial administrator ARN and validates it for non-privileged accounts.
    • Policy responses now include the Cedar policy definition.
    • Added comprehensive local authorization testing with Keycloak, LocalStack, and Cedar services.
  • Bug Fixes

    • Improved authorization for assumed-role identities.
    • Account deletion now removes associated administrator records.
    • Fixed local AWS endpoint configuration compatibility.
  • Documentation

    • Added guidance for Cedar and Verified Permissions policy management, workflows, and troubleshooting.

cdoan1 and others added 2 commits August 8, 2026 17:29
…accounts

- Remove invalid additionalAttributes from Cedar schema (AVP PutSchema rejection)
- Add adminArn field to account provisioning to bootstrap initial admin
- Require adminArn for non-privileged accounts to prevent admin deadlock
- Export and use NormalizeAssumedRoleARN to match STS assumed-role ARNs
  against stored IAM role ARNs in both IsAdmin and Cedar buildAVPRequest
- Add unit tests for ARN normalization and accounts handler validation
- Add Cedar policy management docs and cedar.sh helper script

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ixes

- Fix FIPS + custom endpoint conflict in DynamoDB client
- Fix Cedar template resolution for `principal == ?principal` format
- Fix ARN normalization for group membership lookups
- Add AdminStore.DeleteAll to clean up stale admins on account deletion
- Include Cedar policy text in policy API responses
- Add LocalStack-based e2e compose with postgres, cedar-agent, and Keycloak
- Add Keycloak OIDC realm (rosa-e2e) with supervisor, admin, and user test accounts
- Add init scripts for LocalStack IAM/STS and Keycloak OIDC token provisioning
- Add full e2e authz test suite covering account lifecycle, Cedar policy CRUD,
  group/attachment management, authorization evaluation, and ARN normalization

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cdoan1 cdoan1 added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Aug 10, 2026
@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Aug 10, 2026
@openshift-ci-robot

openshift-ci-robot commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

@cdoan1: This pull request references ROSAENG-64874 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Description

  • Fix Cedar schema validation: Remove additionalAttributes: true from rosa.cedarschema.json — Cedar's JSON schema
    format doesn't support it, causing validation failures
  • Normalize STS assumed-role ARNs: Convert arn:aws:sts::ACCT:assumed-role/Role/session to arn:aws:iam::ACCT:role/Role
    so that admin checks, group lookups, and Cedar policy evaluation match the IAM ARN stored at provisioning time
  • Require adminArn for non-privileged accounts: The account creation endpoint now validates that non-privileged
    accounts provide an initial admin ARN and auto-provisions it
  • Fix FIPS + custom endpoint conflict: Skip FIPS endpoint configuration when a custom DynamoDB endpoint is set
    (LocalStack), since the AWS SDK rejects the combination
  • Fix mock AVP template resolution: Handle both principal == ?principal and bare ?principal forms in Cedar template
    text
  • Clean up admins on account deletion: Prevent stale admin entries from persisting if an account is re-provisioned
  • Add local e2e authz test harness: Podman Compose setup with LocalStack (DynamoDB + API Gateway), Keycloak (OIDC), and
    a comprehensive Ginkgo test suite covering the full account lifecycle

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)
  • CI/CD or tooling change

Testing

  • Unit tests pass (make test)
  • Integration tests pass (if applicable)
  • Manual verification completed

Checklist

  • My code follows the project's coding conventions
  • I have updated documentation as needed
  • I have added tests that prove my fix/feature works
  • All new and existing tests pass

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci openshift-ci Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Aug 10, 2026
@openshift-ci

openshift-ci Bot commented Aug 10, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: cdoan1

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 Indicates a PR has been approved by an approver from all required OWNERS files. label Aug 10, 2026
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Changes

The pull request adds a local authorization E2E environment using LocalStack, Postgres, Cedar Agent, and Keycloak. It adds account administrator provisioning, assumed-role ARN normalization, policy response data, cleanup behavior, and comprehensive authorization lifecycle tests.

Authorization and local E2E flow

Layer / File(s) Summary
Authorization contracts and principal handling
platform-api/pkg/authz/...
Authorization uses normalized IAM role ARNs for administrator lookup and Cedar principals. Cedar mock resolution supports equality and bare principal templates. Resource tag records reject unspecified attributes.
Account administration and policy responses
platform-api/pkg/handlers/..., platform-api/pkg/authz/authz.go, platform-api/pkg/authz/store/admins.go
Non-privileged account creation requires adminArn. Account deletion removes administrator records. Policy responses include persisted Cedar policy text. Unit tests cover request validation and administrator behavior.
Local authorization environment and startup
hack/..., scripts/..., Makefile, .gitignore
Compose provisions LocalStack, Postgres, Cedar Agent, and Keycloak. Initialization scripts create roles, accounts, credentials, and OIDC tokens. Make targets start and clean up the local E2E flow.
Authorization lifecycle validation
test/e2e-authz-local/..., docs/api/cedar-policies.md
The E2E suite validates provisioning, administrator management, Cedar policies, groups, authorization decisions, ARN normalization, deletion, re-provisioning, and privileged-account bypass. Documentation describes the policy-management flow and troubleshooting cases.

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

Sequence Diagram(s)

sequenceDiagram
  participant Makefile
  participant Compose
  participant LocalStack
  participant Keycloak
  participant run-e2e-authz-local.sh
  participant PlatformAPI
  participant Ginkgo
  Makefile->>Compose: start authorization services
  run-e2e-authz-local.sh->>LocalStack: initialize IAM, STS, and DynamoDB
  run-e2e-authz-local.sh->>Keycloak: initialize OIDC realm and tokens
  run-e2e-authz-local.sh->>PlatformAPI: build and start API
  Ginkgo->>PlatformAPI: execute local authorization tests
Loading

Suggested reviewers: typeid


Important

Pre-merge checks failed

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

❌ Failed checks (3 errors, 2 warnings)

Check name Status Explanation Resolution
No-Sensitive-Data-In-Logs ❌ Error The new local runner logs the full POSTGRES_DSN, including its password, and the initializer prints STS identity output containing the assumed-role session ID. Do not log connection strings or raw STS identity. Log only redacted endpoints and status; remove raw token output and full authorization request debug logs.
No-Hardcoded-Secrets ❌ Error The PR adds plaintext secrets in local E2E files, including the Keycloak client secret and user passwords, plus PostgreSQL and Keycloak admin passwords. Move E2E secrets to runtime environment or generated temporary credentials; remove literal values from hack/keycloak/rosa-e2e-realm.json, scripts/e2e-init-keycloak.sh, and compose YAML.
No-Injection-Vectors ❌ Error New E2E scripts write Keycloak and STS response values into shell files, then execute those files with source; quote/newline payloads can run commands. Store credentials as JSON and parse them without source/evaluation, or safely shell-escape every value before writing the file.
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Ai-Attribution ⚠️ Warning Both PR commits name Claude Opus 4.6 but use prohibited Co-Authored-By trailers; neither has an Assisted-by or Generated-by trailer. Replace the AI Co-Authored-By trailers in both PR commits with the required Red Hat-approved Assisted-by or Generated-by trailer.
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the core Cedar schema validation and ARN normalization fixes for non-privileged accounts.
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.
No-Weak-Crypto ✅ Passed Added-line and repository scans found no MD5, SHA-1, DES, 3DES, RC4, Blowfish, or ECB usage; changed Go files import no crypto packages and compare no secrets or tokens.
Container-Privileges ✅ Passed The added Compose and manifest lines contain no privileged:true, hostPID, hostNetwork, hostIPC, SYS_ADMIN, or allowPrivilegeEscalation:true; runtime Containerfiles are unchanged and use UID 65534.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

🧹 Nitpick comments (6)
platform-api/pkg/handlers/accounts_test.go (1)

155-179: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for an AddAdmin failure.

addAdminFn always returns nil in every test. No test drives the failure branch at platform-api/pkg/handlers/accounts.go Lines 97-98. That branch currently only logs and still returns 201. Add a test that returns an error from addAdminFn and asserts the expected status. The test then pins the chosen behavior for a partially provisioned account.

🤖 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 `@platform-api/pkg/handlers/accounts_test.go` around lines 155 - 179, Add a
test alongside TestAccounts_Create_NonPrivilegedWithAdminArn that makes
mockAuthzService.addAdminFn return an error, invokes h.Create with the same
non-privileged admin request, and asserts the status returned by the AddAdmin
failure branch. Keep the test focused on pinning the handler’s current
partial-provisioning behavior.
test/e2e-authz-local/authz_local_test.go (1)

88-105: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The suite is not repeatable against a persistent environment.

This test provisions account 111111111111 and expects 201 Created. The Account Re-provisioning context deletes account 333333333333 at Lines 491-499, so that account is repeatable. Account 111111111111 is never deleted. A second run against the same LocalStack instance receives 409 account-exists here, and every later context that depends on nonPrivilegedAccountID then fails.

Add an AfterAll that deletes nonPrivilegedAccountID, or make this step tolerate an existing account.

🤖 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/e2e-authz-local/authz_local_test.go` around lines 88 - 105, Make the
Account Provisioning context repeatable by adding an AfterAll cleanup that
deletes nonPrivilegedAccountID through the existing client and verifies the
cleanup succeeds. Keep the provisioning assertions unchanged and use the same
deletion pattern as the Account Re-provisioning context.
scripts/e2e-init-localstack.sh (1)

13-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

CUSTOMER_ACCOUNT is unused.

The script sets CUSTOMER_ACCOUNT at Line 14 and never reads it. CustomerAdminRole is created with a literal role name at Line 75, and the ARN at Line 85 uses SUPERVISOR_ACCOUNT only. Remove the variable, or use it where the customer account ID is needed.

🤖 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 `@scripts/e2e-init-localstack.sh` around lines 13 - 14, Remove the unused
CUSTOMER_ACCOUNT assignment from the script, unless the customer account ID is
required by a later resource; in that case, replace the relevant
customer-account literal with CUSTOMER_ACCOUNT. Leave SUPERVISOR_ACCOUNT and its
existing ARN usage unchanged.

Source: Linters/SAST tools

platform-api/pkg/handlers/accounts.go (1)

70-74: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Validate the format of adminArn.

The check only rejects an empty value. A caller can send any string, for example not-an-arn. That value is stored as the administrator principal. store.NormalizeAssumedRoleARN then leaves it unchanged, so the administrator check can never match it, and the account has no usable administrator. Validate that adminArn is an IAM role or user ARN before you create the account.

♻️ Proposed validation
 	if !req.Privileged && req.AdminArn == "" {
 		h.writeError(w, http.StatusBadRequest, "missing-admin-arn", "adminArn is required for non-privileged accounts")
 		return
 	}
+
+	if req.AdminArn != "" && !adminARNPattern.MatchString(req.AdminArn) {
+		h.writeError(w, http.StatusBadRequest, "invalid-admin-arn", "adminArn must be an IAM role or user ARN")
+		return
+	}

Add the anchored pattern at package scope:

var adminARNPattern = regexp.MustCompile(`^arn:aws[a-zA-Z-]*:iam::\d{12}:(role|user)/[\w+=,.@/-]+$`)
🤖 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 `@platform-api/pkg/handlers/accounts.go` around lines 70 - 74, Validate
req.AdminArn against an anchored IAM role/user ARN pattern at package scope
before account creation, while retaining the existing required-value check for
non-privileged accounts. Reject malformed values with the handler’s bad-request
error path, using the adminARNPattern symbol and ensuring both role and user
ARNs with valid 12-digit account IDs are accepted.
platform-api/pkg/handlers/authz.go (1)

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

Document that policy is absent from list responses.

CreatePolicy, GetPolicy, and UpdatePolicy populate CedarPolicy. ListPolicies at Lines 189-198 does not. omitempty hides the field, so a client cannot tell whether the policy text is empty or simply not returned by the list endpoint. Add a comment on the field, or populate it in ListPolicies as well.

🤖 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 `@platform-api/pkg/handlers/authz.go` at line 43, Document the `CedarPolicy`
field’s endpoint-specific behavior in its struct comment: `CreatePolicy`,
`GetPolicy`, and `UpdatePolicy` populate it, while `ListPolicies` intentionally
omits it and `omitempty` hides the field. Do not change list response population
unless that behavior is intended to change.
hack/podman-compose.e2e-authz.yaml (1)

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

Pin the container image tags.

localstack/localstack-pro:latest and docker.io/permitio/cedar-agent:latest float. A new upstream release can break the local suite with no repository change, and failures are then hard to reproduce. Pin both to an explicit version, as the file already does for postgres:16-alpine and keycloak:26.0.

Also applies to: 43-43

🤖 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 `@hack/podman-compose.e2e-authz.yaml` at line 26, Replace the floating latest
tags for the LocalStack and Cedar Agent images in the compose configuration with
explicit, fixed version tags, matching the existing pinned-image style used by
postgres and keycloak.
🤖 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 `@docs/api/cedar-policies.md`:
- Line 22: Update the flow-diagram fenced code block in the Cedar policies
documentation to specify the `text` language identifier, leaving the diagram
content unchanged.
- Line 117: Correct the Step 2 heading by replacing the misspelled “CedaÏter”
with “Cedar,” leaving the rest of the heading unchanged.

In `@platform-api/pkg/authz/authz.go`:
- Around line 403-408: Update the account deletion flow around
adminStore.DeleteAll to return the cleanup error immediately when it fails,
preventing deletion of the account record and allowing the caller to retry.
Preserve the existing warning log, and only continue to delete the account after
DeleteAll succeeds.
- Around line 197-205: Update the authorization context construction to use the
already normalized principalARN for context.principalArn, alongside the
principal EntityIdentifier, so assumed-role callers consistently evaluate
against IAM role ARNs. Do not use req.CallerARN for this context field unless a
separate raw-ARN attribute is explicitly documented.

In `@platform-api/pkg/authz/store/admins.go`:
- Around line 165-174: Update AdminStore.DeleteAll to iterate through every page
returned by the underlying administrator query, continuing with LastEvaluatedKey
or the established SDK paginator until no pages remain, and remove each admin
from every page. Add a test covering multiple pages and verify all administrator
records are deleted.
- Around line 119-135: Update NormalizeAssumedRoleARN in
platform-api/pkg/authz/store/admins.go: require both a non-empty role name and
session name in the assumed-role resource before converting it to an IAM role
ARN; otherwise return the original ARN unchanged. Update
platform-api/pkg/authz/store/admins_test.go lines 42-45 to retain and verify the
no-session ARN remains unnormalized.

In `@platform-api/pkg/handlers/accounts.go`:
- Around line 96-103: Update the account-creation handler around
authorizer.AddAdmin so an AddAdmin failure is returned to the caller instead of
merely logged and proceeding with 201 Created. On failure, invoke
h.authorizer.DisableAccount for req.AccountID to roll back the newly created
account, then return the appropriate error response; preserve the successful
logging and response path when AddAdmin succeeds.

In `@scripts/e2e-init-keycloak.sh`:
- Around line 82-87: Both init scripts create secret-bearing credential files
with permissive default modes. In scripts/e2e-init-keycloak.sh at lines 82-87,
touch $CREDENTIALS_FILE and chmod it to 600 before the heredoc writes the OIDC
tokens; apply the same touch-and-chmod-600 preparation in
scripts/e2e-init-localstack.sh at lines 125-129 before writing the STS
credentials.
- Around line 54-72: Update fetch_token so its informational echo messages,
including the fetching notice and token-length diagnostic, are written to
stderr; keep only the raw access token on stdout for command substitution into
SUPERVISOR_TOKEN and other callers.

In `@scripts/run-e2e-authz-local.sh`:
- Around line 19-38: The local runner scripts must stop embedding or exposing
PostgreSQL credentials. In scripts/run-e2e-authz-local.sh lines 19-38, require
the secret-backed POSTGRES_DSN environment variable without a credential-bearing
default and replace the full DSN output with non-sensitive connection metadata.
In scripts/run-e2e-authz.sh line 29, remove the credential-bearing command-line
DSN and configure the API using POSTGRES_DSN from the environment.

In `@test/e2e-authz-local/authz_local_test.go`:
- Around line 80-85: Update the policies-list handler used by the
privilegedAccountID request to return a defined, non-error response when no
policy store exists, then revise the test to assert that intended status and
body/code instead of expecting http.StatusInternalServerError with
“internal-error”.

---

Nitpick comments:
In `@hack/podman-compose.e2e-authz.yaml`:
- Line 26: Replace the floating latest tags for the LocalStack and Cedar Agent
images in the compose configuration with explicit, fixed version tags, matching
the existing pinned-image style used by postgres and keycloak.

In `@platform-api/pkg/handlers/accounts_test.go`:
- Around line 155-179: Add a test alongside
TestAccounts_Create_NonPrivilegedWithAdminArn that makes
mockAuthzService.addAdminFn return an error, invokes h.Create with the same
non-privileged admin request, and asserts the status returned by the AddAdmin
failure branch. Keep the test focused on pinning the handler’s current
partial-provisioning behavior.

In `@platform-api/pkg/handlers/accounts.go`:
- Around line 70-74: Validate req.AdminArn against an anchored IAM role/user ARN
pattern at package scope before account creation, while retaining the existing
required-value check for non-privileged accounts. Reject malformed values with
the handler’s bad-request error path, using the adminARNPattern symbol and
ensuring both role and user ARNs with valid 12-digit account IDs are accepted.

In `@platform-api/pkg/handlers/authz.go`:
- Line 43: Document the `CedarPolicy` field’s endpoint-specific behavior in its
struct comment: `CreatePolicy`, `GetPolicy`, and `UpdatePolicy` populate it,
while `ListPolicies` intentionally omits it and `omitempty` hides the field. Do
not change list response population unless that behavior is intended to change.

In `@scripts/e2e-init-localstack.sh`:
- Around line 13-14: Remove the unused CUSTOMER_ACCOUNT assignment from the
script, unless the customer account ID is required by a later resource; in that
case, replace the relevant customer-account literal with CUSTOMER_ACCOUNT. Leave
SUPERVISOR_ACCOUNT and its existing ARN usage unchanged.

In `@test/e2e-authz-local/authz_local_test.go`:
- Around line 88-105: Make the Account Provisioning context repeatable by adding
an AfterAll cleanup that deletes nonPrivilegedAccountID through the existing
client and verifies the cleanup succeeds. Keep the provisioning assertions
unchanged and use the same deletion pattern as the Account Re-provisioning
context.
🪄 Autofix

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: Repository: openshift-online/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 28f11a72-b3cf-47a5-aba7-aec4d5d4ac50

📥 Commits

Reviewing files that changed from the base of the PR and between a1bbdf1 and 6cfef8f.

📒 Files selected for processing (20)
  • .gitignore
  • Makefile
  • docs/api/cedar-policies.md
  • hack/keycloak/rosa-e2e-realm.json
  • hack/podman-compose.e2e-authz.yaml
  • platform-api/pkg/authz/authz.go
  • platform-api/pkg/authz/client/dynamodb.go
  • platform-api/pkg/authz/client/mock_avp.go
  • platform-api/pkg/authz/schema/rosa.cedarschema.json
  • platform-api/pkg/authz/store/admins.go
  • platform-api/pkg/authz/store/admins_test.go
  • platform-api/pkg/handlers/accounts.go
  • platform-api/pkg/handlers/accounts_test.go
  • platform-api/pkg/handlers/authz.go
  • scripts/e2e-init-keycloak.sh
  • scripts/e2e-init-localstack.sh
  • scripts/run-e2e-authz-local.sh
  • scripts/run-e2e-authz.sh
  • test/e2e-authz-local/authz_local_test.go
  • test/e2e-authz-local/suite_test.go


## Authorization Flow

```

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language to the fenced block.

Set the flow-diagram fence language to text. This resolves markdownlint MD040.

🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 22-22: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 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 `@docs/api/cedar-policies.md` at line 22, Update the flow-diagram fenced code
block in the Cedar policies documentation to specify the `text` language
identifier, leaving the diagram content unchanged.

Source: Linters/SAST tools




### Step 2: Create a CedaÏter policy (admin caller)

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the Step 2 heading.

Replace CedaÏter with Cedar.

🤖 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 `@docs/api/cedar-policies.md` at line 117, Correct the Step 2 heading by
replacing the misspelled “CedaÏter” with “Cedar,” leaving the rest of the
heading unchanged.

Source: Linters/SAST tools

Comment on lines +197 to +205
// Build principal — normalize STS assumed-role ARNs to IAM role ARNs
// so they match the IAM ARN used when attaching policies.
principalARN := store.NormalizeAssumedRoleARN(req.CallerARN)
if principalARN != req.CallerARN {
a.logger.Info("normalized assumed-role ARN for Cedar evaluation", "original", req.CallerARN, "normalized", principalARN)
}
principal := &avptypes.EntityIdentifier{
EntityType: aws.String("ROSA::Principal"),
EntityId: aws.String(req.CallerARN),
EntityId: aws.String(principalARN),

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check how principalArn context attribute is used in Cedar policies, schema, and docs.
fd -t f -e md -e json -e cedar . | xargs rg -n 'principalArn|principalAccount' -C 3
rg -n 'principalArn' --type=go -C 3

Repository: openshift-online/rosa-hyperfleet-api

Length of output: 7351


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- authz.go relevant sections ---'
sed -n '140,240p' platform-api/pkg/authz/authz.go
sed -n '380,420p' platform-api/pkg/authz/authz.go

printf '%s\n' '--- normalization implementation and callers ---'
rg -n 'NormalizeAssumedRoleARN|principalArn' platform-api/pkg --type go -C 4

printf '%s\n' '--- Cedar policy documentation and condition-key tests ---'
sed -n '420,445p' docs/authz.md
cat -n platform-api/pkg/authz/testdata/policies/07-condition-keys/principal-arn-condition.cedar
cat -n platform-api/pkg/authz/testdata/policies/07-condition-keys/principal-arn-condition.json

Repository: openshift-online/rosa-hyperfleet-api

Length of output: 19593


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- normalization implementation ---'
sed -n '84,145p' platform-api/pkg/authz/store/admins.go

printf '%s\n' '--- buildAVPRequest tests and CallerARN fixtures ---'
rg -n 'buildAVPRequest|CallerARN|principalArn|assumed-role' platform-api/pkg/authz --type go -C 5

printf '%s\n' '--- all policy references to principal entity and context ARN ---'
rg -n 'ROSA::Principal|context\.principalArn|principalArn' --glob '*.cedar' --glob '*.md' --glob '*.json' .

Repository: openshift-online/rosa-hyperfleet-api

Length of output: 20058


Normalize context.principalArn for assumed-role callers

principal uses NormalizeAssumedRoleARN(req.CallerARN), but context.principalArn uses the raw ARN. Policies that match IAM role ARNs will not match STS assumed-role callers. Set context.principalArn to principalARN, or document a separate raw ARN attribute.

🤖 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 `@platform-api/pkg/authz/authz.go` around lines 197 - 205, Update the
authorization context construction to use the already normalized principalARN
for context.principalArn, alongside the principal EntityIdentifier, so
assumed-role callers consistently evaluate against IAM role ARNs. Do not use
req.CallerARN for this context field unless a separate raw-ARN attribute is
explicitly documented.

Comment on lines +403 to +408
// Clean up all admins for this account so stale entries don't carry over
// if the account is re-provisioned with a different adminArn.
if err := a.adminStore.DeleteAll(ctx, accountID); err != nil {
a.logger.Warn("failed to clean up admins during account deletion", "error", err, "account_id", accountID)
}

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not delete the account when admin cleanup fails.

DeleteAll removes admin records one by one and returns on the first failure (see platform-api/pkg/authz/store/admins.go:165-177). A partial failure therefore leaves admin records for accountID in the admins table. The account row is then deleted anyway. If the account is later re-provisioned with a different adminArn, the stale admins keep admin access, which is the exact case this cleanup is meant to prevent. The local suite asserts this guarantee in test/e2e-authz-local/authz_local_test.go Lines 522-528.

Return the error so the caller can retry, and delete the account record only after cleanup succeeds.

🔒 Proposed fix
 	// Clean up all admins for this account so stale entries don't carry over
 	// if the account is re-provisioned with a different adminArn.
 	if err := a.adminStore.DeleteAll(ctx, accountID); err != nil {
-		a.logger.Warn("failed to clean up admins during account deletion", "error", err, "account_id", accountID)
+		a.logger.Error("failed to clean up admins during account deletion", "error", err, "account_id", accountID)
+		return fmt.Errorf("failed to clean up admins for account %s: %w", accountID, err)
 	}
📝 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
// Clean up all admins for this account so stale entries don't carry over
// if the account is re-provisioned with a different adminArn.
if err := a.adminStore.DeleteAll(ctx, accountID); err != nil {
a.logger.Warn("failed to clean up admins during account deletion", "error", err, "account_id", accountID)
}
// Clean up all admins for this account so stale entries don't carry over
// if the account is re-provisioned with a different adminArn.
if err := a.adminStore.DeleteAll(ctx, accountID); err != nil {
a.logger.Error("failed to clean up admins during account deletion", "error", err, "account_id", accountID)
return fmt.Errorf("failed to clean up admins for account %s: %w", accountID, err)
}
🤖 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 `@platform-api/pkg/authz/authz.go` around lines 403 - 408, Update the account
deletion flow around adminStore.DeleteAll to return the cleanup error
immediately when it fails, preventing deletion of the account record and
allowing the caller to retry. Preserve the existing warning log, and only
continue to delete the account after DeleteAll succeeds.

Comment on lines +119 to +135
func NormalizeAssumedRoleARN(arn string) string {
if !strings.Contains(arn, ":assumed-role/") {
return arn
}
parts := strings.SplitN(arn, ":", 6)
if len(parts) < 6 {
return arn
}
resource := parts[5]
if !strings.HasPrefix(resource, "assumed-role/") {
return arn
}
segments := strings.SplitN(strings.TrimPrefix(resource, "assumed-role/"), "/", 2)
roleName := segments[0]
parts[2] = "iam"
parts[5] = "role/" + roleName
return strings.Join(parts, ":")

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 5 --type go 'type AuthzRequest|CallerARN|Authorize\(' platform-api

Repository: openshift-online/rosa-hyperfleet-api

Length of output: 50392


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- authz call paths ---'
rg -n -C 8 'func \(.*\) IsAdmin|func IsAdmin|NormalizeAssumedRoleARN|CallerARN|HeaderCallerARN|Principal' \
  platform-api/pkg/authz platform-api/pkg/middleware platform-api/pkg/handlers/authz.go

printf '%s\n' '--- relevant source sections ---'
sed -n '1,230p' platform-api/pkg/authz/store/admins.go
sed -n '1,90p' platform-api/pkg/middleware/identity.go
sed -n '620,680p' platform-api/pkg/handlers/authz.go
sed -n '1,90p' platform-api/pkg/authz/authz.go

printf '%s\n' '--- tests for admin and normalization behavior ---'
sed -n '1,180p' platform-api/pkg/authz/store/admins_test.go
rg -n -C 8 'IsAdmin|Admin|Authorize' platform-api/pkg/authz/store/*_test.go platform-api/pkg/authz/*_test.go

Repository: openshift-online/rosa-hyperfleet-api

Length of output: 50393


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- admin store implementation ---'
cat -n platform-api/pkg/authz/store/admins.go | sed -n '1,180p'

printf '%s\n' '--- normalization and admin tests ---'
cat -n platform-api/pkg/authz/store/admins_test.go | sed -n '1,130p'
rg -n -C 4 'IsAdmin\(|adminStore|NormalizeAssumedRoleARN' platform-api/pkg/authz --glob '*_test.go'

printf '%s\n' '--- routes and middleware ordering ---'
rg -n -C 5 'Identity\(|NewAuthz|Authz\{|RequireAdmin|HeaderCallerARN|CheckAuthorization' platform-api/pkg/server platform-api/pkg/middleware platform-api/pkg/handlers --glob '*.go' --glob '!**/*_test.go'

printf '%s\n' '--- direct callers of Authorize and IsAdmin ---'
rg -n '(\.Authorize|\.IsAdmin)\(' platform-api --glob '*.go' --glob '!**/*_test.go'

Repository: openshift-online/rosa-hyperfleet-api

Length of output: 22792


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- privileged middleware behavior ---'
cat -n platform-api/pkg/middleware/privileged.go | sed -n '1,150p'

printf '%s\n' '--- authorization route setup ---'
cat -n platform-api/pkg/server/server.go | sed -n '140,165p'
cat -n platform-api/pkg/handlers/authz.go | sed -n '628,682p'

printf '%s\n' '--- deterministic normalization probe ---'
python3 - <<'PY'
def normalize(arn):
    if ":assumed-role/" not in arn:
        return arn
    parts = arn.split(":", 5)
    if len(parts) < 6:
        return arn
    resource = parts[5]
    if not resource.startswith("assumed-role/"):
        return arn
    segments = resource.removeprefix("assumed-role/").split("/", 1)
    role_name = segments[0]
    parts[2] = "iam"
    parts[5] = "role/" + role_name
    return ":".join(parts)

cases = [
    "arn:aws:sts::123456789012:assumed-role/MyRole",
    "arn:aws:sts::123456789012:assumed-role/MyRole/session",
    "arn:aws:sts::123456789012:assumed-role//session",
    "arn:aws:sts::123456789012:assumed-role/MyRole/",
]
for value in cases:
    print(f"{value!r} -> {normalize(value)!r}")
PY

Repository: openshift-online/rosa-hyperfleet-api

Length of output: 8063


Reject assumed-role ARNs without a session name.

Authorize checks the supplied principal against the admin store before Cedar authorization. The /api/v0/authz/check endpoint accepts that principal from the request body, so assumed-role/MyRole can normalize to a stored IAM role ARN and trigger the admin bypass. Require non-empty role and session components before normalization, and update platform-api/pkg/authz/store/admins_test.go#L42-L45 to retain the no-session ARN.

📍 Affects 2 files
  • platform-api/pkg/authz/store/admins.go#L119-L135 (this comment)
  • platform-api/pkg/authz/store/admins_test.go#L42-L45
🤖 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 `@platform-api/pkg/authz/store/admins.go` around lines 119 - 135, Update
NormalizeAssumedRoleARN in platform-api/pkg/authz/store/admins.go: require both
a non-empty role name and session name in the assumed-role resource before
converting it to an IAM role ARN; otherwise return the original ARN unchanged.
Update platform-api/pkg/authz/store/admins_test.go lines 42-45 to retain and
verify the no-session ARN remains unnormalized.

Comment on lines +96 to +103
if req.AdminArn != "" && !req.Privileged {
if err := h.authorizer.AddAdmin(ctx, req.AccountID, req.AdminArn, callerARN); err != nil {
h.logger.Error("failed to add initial admin", "error", err, "account_id", req.AccountID, "admin_arn", req.AdminArn)
} else {
h.logger.Info("initial admin added", "account_id", req.AccountID, "admin_arn", req.AdminArn)
}
}

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not return 201 when AddAdmin fails.

The handler rejects a non-privileged request without adminArn at Lines 70-73. That rule makes the initial administrator mandatory. Here, an AddAdmin failure is only logged, and the handler still returns 201 Created. The result is a non-privileged account with no administrator. No principal can then manage policies, groups, or admins for that account, and the caller receives no error.

Fail the request when AddAdmin fails. Roll back the account with DisableAccount so the caller can retry.

🐛 Proposed fix
 	if req.AdminArn != "" && !req.Privileged {
 		if err := h.authorizer.AddAdmin(ctx, req.AccountID, req.AdminArn, callerARN); err != nil {
 			h.logger.Error("failed to add initial admin", "error", err, "account_id", req.AccountID, "admin_arn", req.AdminArn)
-		} else {
-			h.logger.Info("initial admin added", "account_id", req.AccountID, "admin_arn", req.AdminArn)
+			if derr := h.authorizer.DisableAccount(ctx, req.AccountID); derr != nil {
+				h.logger.Error("failed to roll back account after admin bootstrap failure", "error", derr, "account_id", req.AccountID)
+			}
+			h.writeError(w, http.StatusInternalServerError, "admin-bootstrap-failed", "Failed to add the initial administrator")
+			return
 		}
+		h.logger.Info("initial admin added", "account_id", req.AccountID, "admin_arn", req.AdminArn)
 	}
📝 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 req.AdminArn != "" && !req.Privileged {
if err := h.authorizer.AddAdmin(ctx, req.AccountID, req.AdminArn, callerARN); err != nil {
h.logger.Error("failed to add initial admin", "error", err, "account_id", req.AccountID, "admin_arn", req.AdminArn)
} else {
h.logger.Info("initial admin added", "account_id", req.AccountID, "admin_arn", req.AdminArn)
}
}
if req.AdminArn != "" && !req.Privileged {
if err := h.authorizer.AddAdmin(ctx, req.AccountID, req.AdminArn, callerARN); err != nil {
h.logger.Error("failed to add initial admin", "error", err, "account_id", req.AccountID, "admin_arn", req.AdminArn)
if derr := h.authorizer.DisableAccount(ctx, req.AccountID); derr != nil {
h.logger.Error("failed to roll back account after admin bootstrap failure", "error", derr, "account_id", req.AccountID)
}
h.writeError(w, http.StatusInternalServerError, "admin-bootstrap-failed", "Failed to add the initial administrator")
return
}
h.logger.Info("initial admin added", "account_id", req.AccountID, "admin_arn", req.AdminArn)
}
🤖 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 `@platform-api/pkg/handlers/accounts.go` around lines 96 - 103, Update the
account-creation handler around authorizer.AddAdmin so an AddAdmin failure is
returned to the caller instead of merely logged and proceeding with 201 Created.
On failure, invoke h.authorizer.DisableAccount for req.AccountID to roll back
the newly created account, then return the appropriate error response; preserve
the successful logging and response path when AddAdmin succeeds.

Comment on lines +54 to +72
fetch_token() {
local username="$1"
local password="$2"
local label="$3"

echo " Fetching token for $label ($username)..."
TOKEN_RESPONSE=$(curl -sf -X POST "$TOKEN_URL" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET" \
-d "username=$username" \
-d "password=$password" \
-d "scope=openid")

ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
echo " token length: ${#ACCESS_TOKEN} chars"
echo "$ACCESS_TOKEN"
}

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 | 🔴 Critical | ⚡ Quick win

fetch_token writes log text to stdout, so the captured token is corrupted.

Line 77 captures the whole stdout of fetch_token into SUPERVISOR_TOKEN. fetch_token writes three things to stdout: the message at Line 59, the token length at Line 70, and the token at Line 71. The captured value is therefore all three lines joined, not the access token. Lines 84-86 then write that multi-line value into $CREDENTIALS_FILE. Every consumer that builds an Authorization: Bearer header from these variables sends an invalid token.

Send the informational messages to stderr.

🐛 Proposed fix
 fetch_token() {
     local username="$1"
     local password="$2"
     local label="$3"
+    local TOKEN_RESPONSE ACCESS_TOKEN
 
-    echo "  Fetching token for $label ($username)..."
+    echo "  Fetching token for $label ($username)..." >&2
     TOKEN_RESPONSE=$(curl -sf -X POST "$TOKEN_URL" \
         -H "Content-Type: application/x-www-form-urlencoded" \
         -d "grant_type=password" \
         -d "client_id=$CLIENT_ID" \
         -d "client_secret=$CLIENT_SECRET" \
         -d "username=$username" \
         -d "password=$password" \
         -d "scope=openid")
 
     ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
-    echo "    token length: ${`#ACCESS_TOKEN`} chars"
+    echo "    token length: ${`#ACCESS_TOKEN`} chars" >&2
     echo "$ACCESS_TOKEN"
 }

Also applies to: 77-79

🤖 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 `@scripts/e2e-init-keycloak.sh` around lines 54 - 72, Update fetch_token so its
informational echo messages, including the fetching notice and token-length
diagnostic, are written to stderr; keep only the raw access token on stdout for
command substitution into SUPERVISOR_TOKEN and other callers.

Comment on lines +82 to +87
cat > "$CREDENTIALS_FILE" <<EOF
export KEYCLOAK_ISSUER_URL="$ISSUER_URL"
export SUPERVISOR_OIDC_TOKEN="$SUPERVISOR_TOKEN"
export CUSTOMER_ADMIN_OIDC_TOKEN="$ADMIN_TOKEN"
export CUSTOMER_USER_OIDC_TOKEN="$USER_TOKEN"
EOF

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.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Both init scripts write secrets to /tmp with default permissions. Each script uses cat > "$CREDENTIALS_FILE", which creates the file with the process umask. The default path is under /tmp, which every local user can read. The shared root cause is the missing mode restriction before the redirection.

  • scripts/e2e-init-keycloak.sh#L82-L87: create $CREDENTIALS_FILE with touch and chmod 600 before the heredoc writes the three OIDC bearer tokens.
  • scripts/e2e-init-localstack.sh#L125-L129: create $CREDENTIALS_FILE with touch and chmod 600 before the heredoc writes the STS secret access key and session token.
📍 Affects 2 files
  • scripts/e2e-init-keycloak.sh#L82-L87 (this comment)
  • scripts/e2e-init-localstack.sh#L125-L129
🤖 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 `@scripts/e2e-init-keycloak.sh` around lines 82 - 87, Both init scripts create
secret-bearing credential files with permissive default modes. In
scripts/e2e-init-keycloak.sh at lines 82-87, touch $CREDENTIALS_FILE and chmod
it to 600 before the heredoc writes the OIDC tokens; apply the same
touch-and-chmod-600 preparation in scripts/e2e-init-localstack.sh at lines
125-129 before writing the STS credentials.

Comment on lines +19 to +38
POSTGRES_DSN="${POSTGRES_DSN:-postgres://rosa:rosa@localhost:5432/hyperfleet?sslmode=disable}"
CREDENTIALS_FILE="/tmp/e2e-localstack-credentials.env"
KEYCLOAK_CREDENTIALS_FILE="/tmp/e2e-keycloak-credentials.env"

cleanup() {
echo "Cleaning up..."
if [[ -f "$PIDFILE" ]] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
kill "$(cat "$PIDFILE")" 2>/dev/null || true
wait "$(cat "$PIDFILE")" 2>/dev/null || true
fi
rm -f "$PIDFILE" "$CREDENTIALS_FILE" "$KEYCLOAK_CREDENTIALS_FILE"
}
trap cleanup EXIT INT TERM

echo "=== Local Authz E2E Tests ==="
echo "LocalStack: $LOCALSTACK_ENDPOINT"
echo "DynamoDB: $DYNAMODB_ENDPOINT"
echo "Cedar Agent: $CEDAR_AGENT_ENDPOINT"
echo "Keycloak: $KEYCLOAK_URL"
echo "Postgres: $POSTGRES_DSN"

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not embed or print PostgreSQL credentials.

The local runner prints the full POSTGRES_DSN, including any password supplied by an operator. Both runners also commit DSNs with embedded passwords. Use a required secret-backed environment variable and print only non-sensitive connection metadata.

  • scripts/run-e2e-authz-local.sh#L19-L38: remove the credential-bearing default and do not print POSTGRES_DSN.
  • scripts/run-e2e-authz.sh#L29-L29: remove the credential-bearing command-line DSN and use the API POSTGRES_DSN environment configuration.

As per coding guidelines, flag hardcoded passwords, URLs with embedded credentials, and logging that may expose passwords.

📍 Affects 2 files
  • scripts/run-e2e-authz-local.sh#L19-L38 (this comment)
  • scripts/run-e2e-authz.sh#L29-L29
🤖 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 `@scripts/run-e2e-authz-local.sh` around lines 19 - 38, The local runner
scripts must stop embedding or exposing PostgreSQL credentials. In
scripts/run-e2e-authz-local.sh lines 19-38, require the secret-backed
POSTGRES_DSN environment variable without a credential-bearing default and
replace the full DSN output with non-sensitive connection metadata. In
scripts/run-e2e-authz.sh line 29, remove the credential-bearing command-line DSN
and configure the API using POSTGRES_DSN from the environment.

Source: Coding guidelines

Comment on lines +80 to +85
It("should not have policy store (privileged accounts bypass Cedar)", func() {
resp, err := client.Get("/api/v0/authz/policies", privilegedAccountID)
Expect(err).NotTo(HaveOccurred())
Expect(resp.StatusCode).To(Equal(http.StatusInternalServerError))
Expect(string(resp.Body)).To(ContainSubstring("internal-error"))
})

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

Do not assert a 500 response as expected behavior.

The test requires 500 internal-error when a privileged account lists policies. A privileged account has no policy store, which is a known and expected state, not a server fault. Encoding 500 as the contract prevents a later fix from returning 200 with an empty list or a 4xx with a clear code, because this test would then fail.

Change the handler to return a defined response for the no-policy-store case, and assert that response here.

🤖 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/e2e-authz-local/authz_local_test.go` around lines 80 - 85, Update the
policies-list handler used by the privilegedAccountID request to return a
defined, non-error response when no policy store exists, then revise the test to
assert that intended status and body/code instead of expecting
http.StatusInternalServerError with “internal-error”.

@openshift-ci

openshift-ci Bot commented Aug 13, 2026

Copy link
Copy Markdown

PR needs rebase.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@openshift-ci openshift-ci Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants