Skip to content

feat: add SigV4 auth generator for AWS Bedrock provider#233

Open
noalimoy wants to merge 1 commit into
opendatahub-io:mainfrom
noalimoy:feat/aws-bedrock-sigv4-auth
Open

feat: add SigV4 auth generator for AWS Bedrock provider#233
noalimoy wants to merge 1 commit into
opendatahub-io:mainfrom
noalimoy:feat/aws-bedrock-sigv4-auth

Conversation

@noalimoy
Copy link
Copy Markdown
Contributor

@noalimoy noalimoy commented May 4, 2026

Summary

Adds AWS SigV4 authentication support to the apikey-injection plugin, enabling requests to AWS Bedrock endpoints that require Signature Version 4 signing.

Closes #59

What this does

  • SigV4AuthGenerator — implements AuthHeadersGenerator, uses AWS SDK v2 to sign requests and produce Authorization, X-Amz-Date, X-Amz-Content-Sha256, and (optionally) X-Amz-Security-Token headers
  • Credentials enricher pattern — adds a dataEnrichers map to ApiKeyInjectionPlugin that injects per-request data (body, endpoint, path) into the credentials map before signing.
    the AuthHeadersGenerator interface stays unchanged and existing providers are unaffected
  • Endpoint threading — reads ExternalModel.spec.endpoint via the reconciler → modelInfoStoreCycleState, and extracts the AWS region from the hostname (e.g. bedrock-runtime.us-east-1.amazonaws.comus-east-1)

Design notes

  • SigV4 signing requires the request body and target endpoint, which aren't available from the Secret alone. The enricher injects these fields into the credentials map per-request, so the generator interface doesn't change.
  • The enricher map is extensible — any future provider that needs request-scoped data for auth can register its own enricher.
  • Aligned with Nir's feedback:
    this lives inside apikey-injection (not a separate plugin),
    sources endpoint from ExternalModel.spec.endpoint (not from :authority header),
    and will adapt naturally once ExternalProvider reconcilers land.

Changed files

File What
auth/sigv4_auth_generator.go SigV4 signing logic + region extraction
plugin.go dataEnrichers map + enrichBedrockCredentials
plugin_test.go Table-driven Bedrock tests (happy path, session token, missing endpoint, missing creds)
auth/sigv4_auth_generator_test.go Unit tests for signing, region parsing, error cases
common/provider/provider.go AWSBedrock = "aws-bedrock" constant
common/state/state-keys.go EndpointKey constant
model-provider-resolver/* Thread endpoint from CR → store → CycleState

How has this been tested

  • Unit tests for SigV4AuthGenerator: verifies header format, body hash, session token, region extraction, and error paths
  • Integration tests in plugin_test.go: full ProcessRequest flow with secret store, enricher, and SigV4 signing
  • model-provider-resolver plugin test: validates endpoint is written to CycleState
  • All existing tests pass (go test ./...)

Summary by CodeRabbit

  • New Features

    • Added AWS Bedrock provider support with SigV4 request signing for secure provider authentication
    • Added endpoint propagation so external model providers can specify configurable endpoints
  • Tests

    • Added comprehensive tests for SigV4 authentication, region extraction, and Bedrock integration behaviors

@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented May 4, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

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

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

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

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Enterprise

Run ID: 59f6930c-8ec1-4bcc-8601-35899ad94d49

📥 Commits

Reviewing files that changed from the base of the PR and between fadd059 and ab745b6.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (11)
  • go.mod
  • pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go
  • pkg/plugins/apikey-injection/auth/sigv4_auth_generator_test.go
  • pkg/plugins/apikey-injection/plugin.go
  • pkg/plugins/apikey-injection/plugin_test.go
  • pkg/plugins/common/provider/provider.go
  • pkg/plugins/common/state/state-keys.go
  • pkg/plugins/model-provider-resolver/external_model_reconciler.go
  • pkg/plugins/model-provider-resolver/model_store.go
  • pkg/plugins/model-provider-resolver/plugin.go
  • pkg/plugins/model-provider-resolver/plugin_test.go
✅ Files skipped from review due to trivial changes (2)
  • pkg/plugins/model-provider-resolver/plugin.go
  • pkg/plugins/model-provider-resolver/model_store.go
🚧 Files skipped from review as they are similar to previous changes (8)
  • pkg/plugins/model-provider-resolver/plugin_test.go
  • pkg/plugins/common/state/state-keys.go
  • pkg/plugins/model-provider-resolver/external_model_reconciler.go
  • pkg/plugins/common/provider/provider.go
  • go.mod
  • pkg/plugins/apikey-injection/plugin.go
  • pkg/plugins/apikey-injection/auth/sigv4_auth_generator_test.go
  • pkg/plugins/apikey-injection/plugin_test.go

📝 Walkthrough

Walkthrough

Adds AWS Bedrock SigV4 support and plumbing to propagate per-request endpoint and request-body data for signing. Introduces a new SigV4AuthGenerator that computes SigV4 headers (Authorization, X-Amz-Date, X-Amz-Content-Sha256, optional X-Amz-Security-Token) using aws-sdk-go-v2. The apikey-injection plugin gains a data-enricher mechanism and registers a Bedrock enricher that injects _request_body, _endpoint, and _path. The model-provider-resolver now stores and writes ExternalModel spec.endpoint into cycle state. Adds AWSBedrock provider constant and state.EndpointKey. Unit tests for signing and region parsing were added. go.mod includes aws-sdk and smithy indirect deps.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Security Observations

  • CWE-434 (Unrestricted Upload / Unbounded Memory): enrichBedrockCredentials marshals and stores the full request body (_request_body) in memory. Action: compute payload hash via streaming or enforce/configure a strict max body size and truncate/refuse larger requests.

  • CWE-601 (Open Redirect / Untrusted Host Use): ExternalModel.spec.endpoint is used verbatim to build HTTPS requests. Action: validate endpoints against a strict allowlist/regex (e.g., ^bedrock-runtime.[a-z0-9-]+.amazonaws.com$), reject IP addresses, and parse via net/url before use.

  • CWE-20 / CWE-1025 (Improper Input Validation): regionFromEndpoint extracts region via naive dot-splitting. Action: replace with regex-based parsing and validate extracted region against known AWS region patterns or a whitelist.

  • CWE-345 (Insufficient Verification of Data Authenticity): No check that the endpoint belongs to the declared provider/tenant. Action: enforce that ExternalModel.spec.provider matches allowed providers for the endpoint and/or add ownership verification.

  • CWE-532 (Exposure of Sensitive Information): Credentials may be exposed via logs or errors. Action: ensure all logging avoids credential values (redact fields), and do not include secrets in returned error messages.

  • CWE-295 (Improper Certificate Validation) — contextual: dynamic endpoints increase TLS risk. Action: ensure standard TLS verification is enforced for outbound requests; do not disable cert checks.

Prioritized action items:

  1. Enforce strict endpoint hostname validation (allowlist/regex) and parse with net/url.
  2. Replace full in-memory body storage with streaming hash or enforce a configurable max size.
  3. Harden region parsing with regex + validation against known AWS regions.
  4. Audit logging and error paths to guarantee credentials are never logged or returned.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding SigV4 authentication support for AWS Bedrock provider, which is the core objective of the PR.
Linked Issues check ✅ Passed Code implements all primary objectives from #59: reads AWS credentials, computes SigV4 signatures via aws-sdk-go-v2, injects required headers (Authorization, X-Amz-Date, X-Amz-Content-Sha256, X-Amz-Security-Token), derives region from endpoint, and supports temporary credentials.
Out of Scope Changes check ✅ Passed All changes directly support SigV4 auth implementation: auth generator, credentials enricher, endpoint threading, state keys, and provider constants. No unrelated refactoring or feature creep detected.

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


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
go.mod (1)

19-19: ⚡ Quick win

github.com/aws/aws-sdk-go-v2 should not be // indirect.

pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go directly imports github.com/aws/aws-sdk-go-v2/aws and github.com/aws/aws-sdk-go-v2/aws/signer/v4, both of which are packages within the root github.com/aws/aws-sdk-go-v2 module. The // indirect annotation is only correct for transitive dependencies not directly imported in any module source file. Running go mod tidy will strip // indirect from this entry.

-	github.com/aws/aws-sdk-go-v2 v1.41.7 // indirect
+	github.com/aws/aws-sdk-go-v2 v1.41.7
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@go.mod` at line 19, The go.mod entry for github.com/aws/aws-sdk-go-v2 is
marked as // indirect but
pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go directly imports
github.com/aws/aws-sdk-go-v2/aws and github.com/aws/aws-sdk-go-v2/aws/signer/v4;
remove the incorrect // indirect annotation by updating the module requirements
(run `go mod tidy` or manually remove the // indirect comment) so
github.com/aws/aws-sdk-go-v2 is recorded as a direct dependency, ensuring the
imports in sigv4_auth_generator.go are reflected in go.mod.
pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go (2)

93-96: ⚡ Quick win

strings.NewReader(body) is an unnecessary allocation — SignHTTP never reads r.Body when an explicit payloadHash is supplied.

v4.SignHTTP uses the bodyHash argument directly for the canonical request; r.Body is only accessed for streaming (unsigned-payload). Pass http.NoBody instead.

♻️ Proposed refactor
-	req, err := http.NewRequest(http.MethodPost, "https://"+endpoint+path, strings.NewReader(body))
+	req, err := http.NewRequest(http.MethodPost, "https://"+endpoint+path, http.NoBody)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go` around lines 93 -
96, Replace the unnecessary allocation of strings.NewReader(body) when
constructing the HTTP request for signing with http.NoBody since v4.SignHTTP
uses the supplied payloadHash and does not read req.Body in this code path;
update the request creation in the block that builds req (the code that
currently calls http.NewRequest with strings.NewReader(body)) to pass
http.NoBody, leaving the payloadHash / bodyHash usage and the subsequent call to
v4.SignHTTP unchanged.

38-39: ⚡ Quick win

defaultPath = "/v1/chat/completions" is OpenAI-compatible — wrong default for native Bedrock InvokeModel/Converse paths.

Native Bedrock API paths follow /model/{modelId}/invoke or /model/{modelId}/converse. If the :path pseudo-header is absent, every native Bedrock request silently falls back to an incorrect path and receives a 404 with no hint that the fallback was triggered. Either remove the default and return an error when _path is empty, or document explicitly that this generator targets the Bedrock OpenAI-compatible endpoint only.

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

In `@pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go` around lines 38 -
39, The current defaultPath = "/v1/chat/completions" is OpenAI-compatible and
causes native Bedrock requests to silently fall back to the wrong path; remove
this hardcoded OpenAI path and make the generator fail fast when the :path
pseudo-header is empty. Specifically, eliminate or set defaultPath to an empty
string and update the sigv4 header generation logic that reads the :path (the
code referencing defaultPath/_path) to return an explicit error when _path is
blank (or document that the generator only targets the OpenAI-compatible
endpoint and validate accordingly). Ensure error text clearly references the
missing :path so callers can correct requests instead of receiving a silent 404.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/plugins/apikey-injection/auth/sigv4_auth_generator_test.go`:
- Around line 112-126: Add two test cases to the existing table-driven tests in
sigv4_auth_generator_test.go: one case named like "empty _request_body returns
error" with credentials including "_request_body": "" (and valid
aws-access-key-id and aws-secret-access-key) and wantErrContains set to a
substring asserting the generator rejects an empty request body; and a second
case named like "endpoint with scheme returns invalid AWS endpoint" with
"_endpoint": "https://bedrock-runtime.us-east-1.amazonaws.com" and
wantErrContains set to "invalid AWS endpoint". Place these entries alongside the
existing cases so the test harness (the same Test... function and the existing
name/wantErrContains handling) will exercise and assert the new failure paths.

In `@pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go`:
- Around line 93-96: The request builder blindly prepends "https://" to endpoint
(used in the http.NewRequest calls) which breaks when endpoint already includes
a scheme; update regionFromEndpoint to either validate and reject endpoints
containing a scheme or normalize the endpoint by stripping any "http://" or
"https://" (or use url.Parse to extract Host) so that the http.NewRequest calls
(the lines constructing "https://"+endpoint+path and the similar block at
118-123) always get a bare host; ensure you normalize once in regionFromEndpoint
and reuse the cleaned endpoint variable where req is created to avoid
duplicate-scheme URLs.
- Around line 65-68: The code is missing an empty-string check for
enrichedBodyField: in the lookup of body from credentialsData (variable body,
enrichedBodyField, credentialsData in sigv4_auth_generator.go) ensure you
validate both presence and non-empty value (i.e., !ok || body == "") and return
a formatted error like the other validators indicating the required field is
missing/empty (preserve the existing error wording pattern and hint about the
enricher).

In `@pkg/plugins/apikey-injection/plugin.go`:
- Around line 38-42: Update the credentialsEnricherFunc type to return
(map[string]string, error) instead of just map[string]string, and modify all
implementations and call sites (e.g., where credentialsEnricherFunc is
implemented and invoked in this file) to propagate and surface serialization and
missing-field errors instead of silently skipping additions; specifically,
return the json.Marshal error when marshaling request.Body fails and return an
explicit error when CycleState lacks an endpoint, then have the caller check the
error and propagate it to GenerateAuthHeaders so downstream messages reflect the
real cause.

In `@pkg/plugins/model-provider-resolver/external_model_reconciler.go`:
- Around line 67-74: The reconciler is incorrectly reading endpoint from
ExternalModel.spec (using unstructured.NestedString on obj.Object) which is
missing — update the reconcile logic that builds externalModelInfo: first
extract and validate externalProviderRefs from ExternalModel.spec (check the
boolean returns from unstructured.NestedX calls), fetch the referenced
ExternalProvider CR, and then read endpoint, provider and auth from that
ExternalProvider's spec (not from obj.Object); ensure you do not discard the
boolean ok values from unstructured.NestedString/NestedMap and return/record an
error if required fields (endpoint/provider/auth) are missing so
externalModelInfo.endpoint is never empty.

---

Nitpick comments:
In `@go.mod`:
- Line 19: The go.mod entry for github.com/aws/aws-sdk-go-v2 is marked as //
indirect but pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go directly
imports github.com/aws/aws-sdk-go-v2/aws and
github.com/aws/aws-sdk-go-v2/aws/signer/v4; remove the incorrect // indirect
annotation by updating the module requirements (run `go mod tidy` or manually
remove the // indirect comment) so github.com/aws/aws-sdk-go-v2 is recorded as a
direct dependency, ensuring the imports in sigv4_auth_generator.go are reflected
in go.mod.

In `@pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go`:
- Around line 93-96: Replace the unnecessary allocation of
strings.NewReader(body) when constructing the HTTP request for signing with
http.NoBody since v4.SignHTTP uses the supplied payloadHash and does not read
req.Body in this code path; update the request creation in the block that builds
req (the code that currently calls http.NewRequest with strings.NewReader(body))
to pass http.NoBody, leaving the payloadHash / bodyHash usage and the subsequent
call to v4.SignHTTP unchanged.
- Around line 38-39: The current defaultPath = "/v1/chat/completions" is
OpenAI-compatible and causes native Bedrock requests to silently fall back to
the wrong path; remove this hardcoded OpenAI path and make the generator fail
fast when the :path pseudo-header is empty. Specifically, eliminate or set
defaultPath to an empty string and update the sigv4 header generation logic that
reads the :path (the code referencing defaultPath/_path) to return an explicit
error when _path is blank (or document that the generator only targets the
OpenAI-compatible endpoint and validate accordingly). Ensure error text clearly
references the missing :path so callers can correct requests instead of
receiving a silent 404.
🪄 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: d8f35154-b2b3-4a4e-b3e6-8d9ae8e1ab8c

📥 Commits

Reviewing files that changed from the base of the PR and between 74db5d3 and 6d32c7a.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (11)
  • go.mod
  • pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go
  • pkg/plugins/apikey-injection/auth/sigv4_auth_generator_test.go
  • pkg/plugins/apikey-injection/plugin.go
  • pkg/plugins/apikey-injection/plugin_test.go
  • pkg/plugins/common/provider/provider.go
  • pkg/plugins/common/state/state-keys.go
  • pkg/plugins/model-provider-resolver/external_model_reconciler.go
  • pkg/plugins/model-provider-resolver/model_store.go
  • pkg/plugins/model-provider-resolver/plugin.go
  • pkg/plugins/model-provider-resolver/plugin_test.go

Comment on lines +112 to +126
{
name: "empty access key returns error",
credentials: map[string]string{
"aws-access-key-id": "",
"aws-secret-access-key": "secret",
"_request_body": "{}",
"_endpoint": "bedrock-runtime.us-east-1.amazonaws.com",
},
wantErrContains: "aws-access-key-id",
},
{
name: "empty credentials returns error",
credentials: map[string]string{},
wantErrContains: "aws-access-key-id",
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing test case for _request_body present-but-empty, and endpoint containing a URL scheme.

Two reachable paths are untested:

  • _request_body: "" — currently passes validation silently (the bug flagged in the generator); once fixed, a test should assert the error.
  • _endpoint: "https://bedrock-runtime.us-east-1.amazonaws.com" — with a URL scheme — should return an "invalid AWS endpoint" error once the scheme guard is added.
➕ Suggested additional test cases
+		{
+			name: "empty request body returns error",
+			credentials: map[string]string{
+				"aws-access-key-id":     "AKIA...",
+				"aws-secret-access-key": "secret",
+				"_request_body":         "",
+				"_endpoint":             "bedrock-runtime.us-east-1.amazonaws.com",
+			},
+			wantErrContains: "_request_body",
+		},
+		{
+			name: "endpoint with URL scheme returns error",
+			credentials: map[string]string{
+				"aws-access-key-id":     "AKIA...",
+				"aws-secret-access-key": "secret",
+				"_request_body":         "{}",
+				"_endpoint":             "https://bedrock-runtime.us-east-1.amazonaws.com",
+			},
+			wantErrContains: "invalid AWS endpoint",
+		},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/plugins/apikey-injection/auth/sigv4_auth_generator_test.go` around lines
112 - 126, Add two test cases to the existing table-driven tests in
sigv4_auth_generator_test.go: one case named like "empty _request_body returns
error" with credentials including "_request_body": "" (and valid
aws-access-key-id and aws-secret-access-key) and wantErrContains set to a
substring asserting the generator rejects an empty request body; and a second
case named like "endpoint with scheme returns invalid AWS endpoint" with
"_endpoint": "https://bedrock-runtime.us-east-1.amazonaws.com" and
wantErrContains set to "invalid AWS endpoint". Place these entries alongside the
existing cases so the test harness (the same Test... function and the existing
name/wantErrContains handling) will exercise and assert the new failure paths.

Comment on lines +65 to +68
body, ok := credentialsData[enrichedBodyField]
if !ok {
return nil, fmt.Errorf("credentials missing required field %s (enricher not applied?)", enrichedBodyField)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

_request_body empty-string check is missing — inconsistent with all other field validators.

Every other required field uses !ok || value == "", but the body only checks !ok. An empty _request_body silently proceeds, computes the SHA-256 of "" (e3b0c44...), and produces a well-formed but wrong signed request that AWS will reject with a 400, making the bug hard to trace back to this point.

🐛 Proposed fix
-	body, ok := credentialsData[enrichedBodyField]
-	if !ok {
+	body, ok := credentialsData[enrichedBodyField]
+	if !ok || body == "" {
 		return nil, fmt.Errorf("credentials missing required field %s (enricher not applied?)", enrichedBodyField)
 	}
📝 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
body, ok := credentialsData[enrichedBodyField]
if !ok {
return nil, fmt.Errorf("credentials missing required field %s (enricher not applied?)", enrichedBodyField)
}
body, ok := credentialsData[enrichedBodyField]
if !ok || body == "" {
return nil, fmt.Errorf("credentials missing required field %s (enricher not applied?)", enrichedBodyField)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go` around lines 65 -
68, The code is missing an empty-string check for enrichedBodyField: in the
lookup of body from credentialsData (variable body, enrichedBodyField,
credentialsData in sigv4_auth_generator.go) ensure you validate both presence
and non-empty value (i.e., !ok || body == "") and return a formatted error like
the other validators indicating the required field is missing/empty (preserve
the existing error wording pattern and hint about the enricher).

Comment thread pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go
Comment thread pkg/plugins/apikey-injection/plugin.go Outdated
Comment on lines +67 to +74
endpoint, _, _ := unstructured.NestedString(obj.Object, "spec", "endpoint")
credsName, _, _ := unstructured.NestedString(obj.Object, "spec", "credentialRef", "name")

// targetModel is the model that will be used in the request body when getting inference requests.
info := &externalModelInfo{
provider: provider,
targetModel: targetModel,
endpoint: endpoint,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether ExternalModel has a spec.endpoint field in its API type definition.
fd -i 'externalmodel_types.go' --exec cat {}

Repository: opendatahub-io/ai-gateway-payload-processing

Length of output: 4391


🏁 Script executed:

#!/bin/bash
# Check the external_model_reconciler.go file around the problematic lines
fd -i 'external_model_reconciler.go' --exec head -n 90 {} | tail -n 35

Repository: opendatahub-io/ai-gateway-payload-processing

Length of output: 1592


🏁 Script executed:

#!/bin/bash
# Find and examine the ExternalProvider type definition to confirm endpoint field exists
fd -i 'externalprovider_types.go' --exec cat {}

Repository: opendatahub-io/ai-gateway-payload-processing

Length of output: 4098


Critical: Endpoint will always be empty — reconciler reads from non-existent ExternalModel.spec.endpoint instead of referenced ExternalProvider.

ExternalModel.spec contains only externalProviderRefs; it has no endpoint, provider, or targetModel fields. Line 67 reads from spec.endpoint which doesn't exist, and unstructured.NestedString() silently returns ("", false, nil) for missing fields. The code discards the boolean return value (_), hiding this failure completely. An empty endpoint propagates to the model store and breaks all Bedrock SigV4 signing at runtime with "credentials missing required field _endpoint" — no log-level indication at the reconciler.

The Endpoint field is defined on ExternalProvider (spec.endpoint, kubebuilder-validated required). The reconciler must:

  1. Extract externalProviderRefs from ExternalModel.spec
  2. Fetch the referenced ExternalProvider CR
  3. Read endpoint, provider, and auth from the ExternalProvider, not from ExternalModel

Additionally, discarding the boolean return value violates the GO SECURITY guideline: CR spec fields must be validated before use (CWE-754: Improper Check for Unusual Conditions).

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

In `@pkg/plugins/model-provider-resolver/external_model_reconciler.go` around
lines 67 - 74, The reconciler is incorrectly reading endpoint from
ExternalModel.spec (using unstructured.NestedString on obj.Object) which is
missing — update the reconcile logic that builds externalModelInfo: first
extract and validate externalProviderRefs from ExternalModel.spec (check the
boolean returns from unstructured.NestedX calls), fetch the referenced
ExternalProvider CR, and then read endpoint, provider and auth from that
ExternalProvider's spec (not from obj.Object); ensure you do not discard the
boolean ok values from unstructured.NestedString/NestedMap and return/record an
error if required fields (endpoint/provider/auth) are missing so
externalModelInfo.endpoint is never empty.

@noalimoy
Copy link
Copy Markdown
Contributor Author

noalimoy commented May 4, 2026

Hi @nirrozenbaum

while adding E2E coverage for aws-bedrock, I noticed the api-translation plugin doesn't have a translator registered for it — so the request flow fails at the translation step with "unsupported provider".

since aws-bedrock uses the same OpenAI-compatible endpoint (/v1/chat/completions) as bedrock-openai
the only difference being the auth method (SigV4 vs Bearer)
should I register it with the existing BedrockOpenAITranslator?

or if native Bedrock endpoints (/model/{id}/converse) are planned for this provider, I'll skip it and leave E2E coverage for a follow-up once a dedicated translator is implemented.

What do you think?

@chaitanya1731
Copy link
Copy Markdown
Contributor

/group-test

@nirrozenbaum
Copy link
Copy Markdown
Contributor

/cc: @yossiovadia

Copy link
Copy Markdown
Contributor

@yossiovadia yossiovadia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Noa, a few things to address before this can merge.

1. Missing api-translation registration

aws-bedrock isn't registered in the api-translation plugin. Since api-translation runs before apikey-injection in the plugin chain, any aws-bedrock request will fail at translation before reaching the signing logic. This needs either a translator registration in this PR or a clear plan for when it lands.

2. Endpoint source will change with #183/#184

The reconciler reads spec.endpoint from the maas.opendatahub.io ExternalModel. PRs #183/#184 (currently in review) move endpoint to ExternalProvider — a separate CRD. Once those merge, the endpoint threading here will need to change (read from the provider store instead of the model reconciler).

Since this PR can't work end-to-end without the translator anyway, it might be easier to rebase after #183/#184 land rather than reworking the wiring twice.

3. Region extraction from hostname

regionFromEndpoint parses region from the hostname (bedrock-runtime.us-east-1.amazonaws.comus-east-1). This works for standard endpoints but breaks for VPC endpoints or custom configurations. In #234 we aligned with Nir on using the config map for provider-specific settings like region — that would be more robust than hostname parsing.

4. Body-signing serialization assumption

The enricher does json.Marshal(request.Body) to get the bytes for SigV4 signing. The framework independently serializes the body when sending to the upstream. If the two serializations differ (key ordering, whitespace), AWS will reject with SignatureDoesNotMatch. Go's json.Marshal sorts map keys, so it likely matches, but this is an implicit coupling worth documenting in a comment.

5. Silent error in enricher

enrichBedrockCredentials returns map[string]string with no error. If json.Marshal fails, the _request_body field is silently missing, and the downstream error message ("enricher not applied?") is misleading. Consider returning an error or at minimum logging the marshal failure.

6. bedrockService hardcoded

Ties into Nir's "no hardcoding" feedback on #234. The service signing name could come from the provider config map rather than a constant. Not blocking for this PR but worth noting for alignment.

@nirrozenbaum
Copy link
Copy Markdown
Contributor

adding also Noy to review:
/cc @noyitz

@openshift-ci openshift-ci Bot requested a review from noyitz May 11, 2026 06:04
@noalimoy
Copy link
Copy Markdown
Contributor Author

thanks @yossiovadia

1. Missing api-translation registration
already raised this before the review — see my above comment. waiting for a decision on whether to reuse BedrockOpenAITranslator , add a dedicated translator, or hold off.

2. Endpoint source will change
aware of #183/#184 and the ExternalProvider redesign. asked the same question on the issue
so should I wait for those to land, or handle the rewiring as a follow-up PR?

3. Region extraction from hostname
will align once #183/#184 merge - region will move to the provider config map.

4 & 5. Body-signing + silent error in enricher
good points — fixed.
enricher now returns (map[string]string, error) and reports marshal/endpoint failures explicitly. also added a comment documenting the json.Marshal sorted-keys behavior.

6. bedrockService hardcoded
same as 3 — will move to config-driven once the new design lands.

@noalimoy noalimoy force-pushed the feat/aws-bedrock-sigv4-auth branch from 6d32c7a to fadd059 Compare May 12, 2026 09:08
@github-actions github-actions Bot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. labels May 12, 2026
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Large PR detected

Your PR is large. Please consider breaking it into multiple PRs.

The do-not-merge/hold label has been added and can be removed by the reviewers based on their judgement.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go`:
- Line 93: The request is created with http.NewRequest which doesn't propagate
context; change the call to http.NewRequestWithContext(ctx, http.MethodPost,
"https://"+endpoint+path, strings.NewReader(body)) so the context is
honored—ensure a context.Context named ctx is available in the scope (add a ctx
parameter to the enclosing method if needed) and keep the existing error
handling for req, err unchanged; update the instantiation that currently assigns
to req, err to use NewRequestWithContext and propagate ctx through any
downstream request lifecycle.
🪄 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: 193e15fd-1703-491d-a2b8-9ec4628628af

📥 Commits

Reviewing files that changed from the base of the PR and between 6d32c7a and fadd059.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (11)
  • go.mod
  • pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go
  • pkg/plugins/apikey-injection/auth/sigv4_auth_generator_test.go
  • pkg/plugins/apikey-injection/plugin.go
  • pkg/plugins/apikey-injection/plugin_test.go
  • pkg/plugins/common/provider/provider.go
  • pkg/plugins/common/state/state-keys.go
  • pkg/plugins/model-provider-resolver/external_model_reconciler.go
  • pkg/plugins/model-provider-resolver/model_store.go
  • pkg/plugins/model-provider-resolver/plugin.go
  • pkg/plugins/model-provider-resolver/plugin_test.go
✅ Files skipped from review due to trivial changes (5)
  • pkg/plugins/common/state/state-keys.go
  • pkg/plugins/model-provider-resolver/model_store.go
  • pkg/plugins/model-provider-resolver/plugin_test.go
  • go.mod
  • pkg/plugins/apikey-injection/plugin_test.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • pkg/plugins/model-provider-resolver/plugin.go
  • pkg/plugins/model-provider-resolver/external_model_reconciler.go
  • pkg/plugins/common/provider/provider.go
  • pkg/plugins/apikey-injection/plugin.go
  • pkg/plugins/apikey-injection/auth/sigv4_auth_generator_test.go

path = defaultPath
}

req, err := http.NewRequest(http.MethodPost, "https://"+endpoint+path, strings.NewReader(body))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether non-context request builders are still used in this file.
rg -n 'http\.NewRequest\(' pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go

Repository: opendatahub-io/ai-gateway-payload-processing

Length of output: 188


🏁 Script executed:

# Read the function containing line 93 to understand its signature and context availability
sed -n '80,115p' pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go

Repository: opendatahub-io/ai-gateway-payload-processing

Length of output: 1132


🏁 Script executed:

# Also check the full function signature to see if context is a parameter
rg -B 20 'http\.NewRequest\(' pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go | head -30

Repository: opendatahub-io/ai-gateway-payload-processing

Length of output: 629


🏁 Script executed:

# Check imports to see what's available
head -20 pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go

Repository: opendatahub-io/ai-gateway-payload-processing

Length of output: 697


🏁 Script executed:

# Get the complete function signature
sed -n '50,100p' pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go

Repository: opendatahub-io/ai-gateway-payload-processing

Length of output: 2121


🏁 Script executed:

# Also search for the function definition
rg -B 30 'http\.NewRequest\(' pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go | grep -E '(func|GenerateAuthHeaders)'

Repository: opendatahub-io/ai-gateway-payload-processing

Length of output: 70


🏁 Script executed:

# Check file structure to understand context
head -50 pkg/plugins/apikey-injection/auth/sigv4_auth_generator.go | grep -E '(type|package|controller|kubernetes)'

Repository: opendatahub-io/ai-gateway-payload-processing

Length of output: 134


🏁 Script executed:

# Check if this is used in a controller context
rg -l 'GenerateAuthHeaders' --type go | head -5

Repository: opendatahub-io/ai-gateway-payload-processing

Length of output: 371


Use context-bound request construction at Line 93

http.NewRequest bypasses context propagation. Switch to http.NewRequestWithContext for proper lifecycle handling.

Proposed fix
-	req, err := http.NewRequest(http.MethodPost, "https://"+endpoint+path, strings.NewReader(body))
+	req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "https://"+endpoint+path, strings.NewReader(body))
📝 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
req, err := http.NewRequest(http.MethodPost, "https://"+endpoint+path, strings.NewReader(body))
req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "https://"+endpoint+path, strings.NewReader(body))
🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 93-93: net/http.NewRequest must not be called. use net/http.NewRequestWithContext

(noctx)

🤖 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/plugins/apikey-injection/auth/sigv4_auth_generator.go` at line 93, The
request is created with http.NewRequest which doesn't propagate context; change
the call to http.NewRequestWithContext(ctx, http.MethodPost,
"https://"+endpoint+path, strings.NewReader(body)) so the context is
honored—ensure a context.Context named ctx is available in the scope (add a ctx
parameter to the enclosing method if needed) and keep the existing error
handling for req, err unchanged; update the instantiation that currently assigns
to req, err to use NewRequestWithContext and propagate ctx through any
downstream request lifecycle.

- Add SigV4AuthGenerator implementing AuthHeadersGenerator, using
  AWS SDK v2 signer to produce Authorization, X-Amz-Date,
  X-Amz-Content-Sha256, and X-Amz-Security-Token headers
- Introduce dataEnrichers map on ApiKeyInjectionPlugin to enrich
  the credentials map with per-request data (body, endpoint) before
  signing, keeping the AuthHeadersGenerator interface unchanged
- Thread ExternalModel.spec.endpoint through reconciler →
  modelInfoStore → CycleState → enricher → SigV4 signer, and
  extract the AWS region from the endpoint hostname
- Add provider constant aws-bedrock and CycleState key endpoint
- Add unit tests for SigV4 signing, region extraction, credential
  enrichment, session token handling, and missing-endpoint error path
@noalimoy noalimoy force-pushed the feat/aws-bedrock-sigv4-auth branch from fadd059 to ab745b6 Compare May 13, 2026 09:51
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Large PR detected

Your PR is large. Please consider breaking it into multiple PRs.

The do-not-merge/hold label has been added and can be removed by the reviewers based on their judgement.

@rhods-ci-bot
Copy link
Copy Markdown

@noalimoy: The following test has Succeeded:

OCI Artifact Browser URL

View in Artifact Browser

Inspecting Test Artifacts Manually

To inspect your test artifacts manually, follow these steps:

  1. Install ORAS (see the ORAS installation guide).
  2. Download artifacts with the following commands:
mkdir -p oras-artifacts
cd oras-artifacts
oras pull quay.io/opendatahub/odh-ci-artifacts:ai-gateway-group-test-gfwdz

@noalimoy noalimoy requested a review from yossiovadia May 13, 2026 21:11
@openshift-ci openshift-ci Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label May 14, 2026
@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented May 14, 2026

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.

@yossiovadia
Copy link
Copy Markdown
Contributor

Hey @noalimoy , have not forgotten about this one.
I'll revisit it once the relatively big new CRD changes would be fully merged.

@nirrozenbaum
Copy link
Copy Markdown
Contributor

@noalimoy can we rebase this one and move the PR from draft to ready state?
we can start iterating on this. the CRD changes are making nice progress

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

Labels

do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add AWS Bedrock provider with SigV4 authentication

5 participants