From 69e87fcc98d01a7e82449b751d44c069cefc8087 Mon Sep 17 00:00:00 2001 From: xianshujun Date: Thu, 29 Oct 2026 19:55:38 +0900 Subject: [PATCH 1/2] fix(adk): allow the Bedrock runtime origin in Harness egress Signed-off-by: xianshujun --- .../internal/translator/adkconfig/builder.go | 9 +++++++ .../translator/adkconfig/builder_test.go | 26 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/go/core/internal/translator/adkconfig/builder.go b/go/core/internal/translator/adkconfig/builder.go index 9e8b754214..2fd2f42543 100644 --- a/go/core/internal/translator/adkconfig/builder.go +++ b/go/core/internal/translator/adkconfig/builder.go @@ -320,6 +320,15 @@ func agentConfigDestinations(cfg *configv1.ADKAgentConfig, modelConfig *v1alpha3 if mistral := modelConfig.Spec.Mistral; mistral == nil || mistral.BaseURL == nil || *mistral.BaseURL == "" { destinations = append(destinations, "https://api.mistral.ai:443") } + case v1alpha3.ModelProviderBedrock: + // Bedrock's endpoint is derived from the region, which is not part of + // the serialized model, so the walk above never sees it. The bearer + // token is bound to this origin in CompileCredentials; without the + // matching allowlist entry CanonicalAccess rejects the credential and + // the revision cannot be admitted. + if bedrock := modelConfig.Spec.Bedrock; bedrock != nil { + destinations = append(destinations, "https://bedrock-runtime."+bedrock.Region+".amazonaws.com:443") + } case v1alpha3.ModelProviderOllama: // Ollama's endpoint is the provider's own field and is not part of the // serialized model, so the walk above never sees it. Unlike the diff --git a/go/core/internal/translator/adkconfig/builder_test.go b/go/core/internal/translator/adkconfig/builder_test.go index ebf8092be6..6ad69e8d32 100644 --- a/go/core/internal/translator/adkconfig/builder_test.go +++ b/go/core/internal/translator/adkconfig/builder_test.go @@ -278,6 +278,32 @@ func TestOllamaEgressDestination(t *testing.T) { }) } +// TestBedrockEgressDestination covers the provider whose endpoint is derived +// from the region on the ModelConfig's own Bedrock field rather than from the +// serialized model. CompileCredentials binds the bearer token to that regional +// runtime origin, so the origin has to be in the allowlist or CanonicalAccess +// rejects the credential and the revision cannot be admitted. +func TestBedrockEgressDestination(t *testing.T) { + model := &v1alpha3.ModelConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "bedrock", Namespace: "test"}, + Spec: v1alpha3.ModelConfigSpec{ + Provider: v1alpha3.ModelProviderBedrock, Model: "anthropic.claude-sonnet-4-5-20250929-v1:0", + APIKeySecret: "bedrock-auth", APIKeySecretKey: env.AWSBearerTokenBedrock.Name(), + Bedrock: &v1alpha3.BedrockConfig{Region: "us-east-1"}, + }, + } + collections := contextTestCollections(t, model) + result, err := NewBuilder(krt.TestingDummyContext{}, collections).Build(context.Background(), + &translator.HarnessInput{ + Harness: &translator.HarnessConfiguration{Spec: v1alpha3.HarnessSpec{Kagent: &v1alpha3.KagentHarness{}}}, + Root: &translator.AgentInput{ + Template: &translator.TemplateConfiguration{Name: "pi", Namespace: "test"}, + ResolvedModelConfig: resolvedModel(t, collections, "bedrock"), + }}) + require.NoError(t, err) + require.Contains(t, result.Egress, "https://bedrock-runtime.us-east-1.amazonaws.com:443") +} + func contextTestCollections(t *testing.T, models ...*v1alpha3.ModelConfig) translator.Collections { t.Helper() objects := make([]any, 0, 2*len(models)) From d587220942d4a55493ba99ebddf06ae55b61ea97 Mon Sep 17 00:00:00 2001 From: xianshujun Date: Fri, 30 Oct 2026 10:29:29 +0900 Subject: [PATCH 2/2] refactor(adk): derive the Bedrock runtime origin from one helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Credential compilation, the Claude and Codex egress lists, and the ADK egress allowlist each rebuilt https://bedrock-runtime..amazonaws.com on their own, so one site could drift from another — which is how the credential binding and the allowlist disagreed in #3408. models.BedrockRuntimeOrigin is now the single definition all four call. Signed-off-by: xianshujun --- go/adk/pkg/models/bedrock.go | 15 +++++++++++++++ go/adk/pkg/models/bedrock_test.go | 5 +++++ go/core/internal/translator/adkconfig/builder.go | 2 +- go/core/internal/translator/claude/compiler.go | 3 ++- go/core/internal/translator/codex/compiler.go | 3 ++- go/core/internal/translator/credentials.go | 2 +- 6 files changed, 26 insertions(+), 4 deletions(-) diff --git a/go/adk/pkg/models/bedrock.go b/go/adk/pkg/models/bedrock.go index 178a1a203b..835bab43a1 100644 --- a/go/adk/pkg/models/bedrock.go +++ b/go/adk/pkg/models/bedrock.go @@ -96,6 +96,21 @@ type BedrockConfig struct { GuardrailTrace string } +// BedrockRuntimeOrigin returns the HTTPS origin of Bedrock's runtime endpoint +// for a region. +// +// The endpoint is derived from the region rather than stored on the model +// spec, so every consumer has to reconstruct it: credential compilation binds +// the bearer token to this origin, the harness compilers allowlist it, and the +// ADK builder puts it on the agent's egress list. Copying the format at each +// site let the credential binding and the allowlist drift apart, and a request +// those two disagreed on was rejected before it left the pod. This is the one +// definition they all call; a consumer that needs the egress form appends +// ":443". +func BedrockRuntimeOrigin(region string) string { + return "https://bedrock-runtime." + region + ".amazonaws.com" +} + // DecodeBedrockRequestFields preserves provider JSON numbers using the AWS // document decoder, whose number type is understood by the Converse encoder. func DecodeBedrockRequestFields(raw []byte) (map[string]any, error) { diff --git a/go/adk/pkg/models/bedrock_test.go b/go/adk/pkg/models/bedrock_test.go index e0eef37c97..a26c1a5413 100644 --- a/go/adk/pkg/models/bedrock_test.go +++ b/go/adk/pkg/models/bedrock_test.go @@ -19,6 +19,11 @@ import ( "google.golang.org/genai" ) +func TestBedrockRuntimeOrigin(t *testing.T) { + require.Equal(t, "https://bedrock-runtime.us-east-1.amazonaws.com", BedrockRuntimeOrigin("us-east-1")) + require.Equal(t, "https://bedrock-runtime.eu-central-1.amazonaws.com", BedrockRuntimeOrigin("eu-central-1")) +} + func TestBedrockProviderJSONReachesConverseUnchanged(t *testing.T) { t.Setenv("AWS_BEARER_TOKEN_BEDROCK", "") raw := `{"thinking":{"budget_tokens":1024},"large":9007199254740993,"values":[0,false,null,0.25]}` diff --git a/go/core/internal/translator/adkconfig/builder.go b/go/core/internal/translator/adkconfig/builder.go index 2fd2f42543..7bc27e588b 100644 --- a/go/core/internal/translator/adkconfig/builder.go +++ b/go/core/internal/translator/adkconfig/builder.go @@ -327,7 +327,7 @@ func agentConfigDestinations(cfg *configv1.ADKAgentConfig, modelConfig *v1alpha3 // matching allowlist entry CanonicalAccess rejects the credential and // the revision cannot be admitted. if bedrock := modelConfig.Spec.Bedrock; bedrock != nil { - destinations = append(destinations, "https://bedrock-runtime."+bedrock.Region+".amazonaws.com:443") + destinations = append(destinations, models.BedrockRuntimeOrigin(bedrock.Region)+":443") } case v1alpha3.ModelProviderOllama: // Ollama's endpoint is the provider's own field and is not part of the diff --git a/go/core/internal/translator/claude/compiler.go b/go/core/internal/translator/claude/compiler.go index b3081f6fa6..cff3e16926 100644 --- a/go/core/internal/translator/claude/compiler.go +++ b/go/core/internal/translator/claude/compiler.go @@ -18,6 +18,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" + "github.com/kagent-dev/kagent/go/adk/pkg/models" configv1 "github.com/kagent-dev/kagent/go/api/gen/kagent/config/v1" "github.com/kagent-dev/kagent/go/api/v1alpha3" "github.com/kagent-dev/kagent/go/core/internal/egress" @@ -257,7 +258,7 @@ func (c *Compiler) provider(ctx context.Context, resolved *translator.ResolvedMo environment = append(environment, secretEnvironment(claudeconfig.AWSSessionTokenEnvName, secret.Name, claudeconfig.AWSSessionTokenEnvName)) } } - return environment, []string{"https://bedrock-runtime." + model.Spec.Bedrock.Region + ".amazonaws.com:443"}, nil + return environment, []string{models.BedrockRuntimeOrigin(model.Spec.Bedrock.Region) + ":443"}, nil case v1alpha3.ModelProviderAnthropicVertexAI: if model.Spec.AnthropicVertexAI == nil || strings.TrimSpace(model.Spec.AnthropicVertexAI.ProjectID) == "" || strings.TrimSpace(model.Spec.AnthropicVertexAI.Location) == "" { diff --git a/go/core/internal/translator/codex/compiler.go b/go/core/internal/translator/codex/compiler.go index e02da8e2d5..53fd8e4d7c 100644 --- a/go/core/internal/translator/codex/compiler.go +++ b/go/core/internal/translator/codex/compiler.go @@ -17,6 +17,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" + "github.com/kagent-dev/kagent/go/adk/pkg/models" configv1 "github.com/kagent-dev/kagent/go/api/gen/kagent/config/v1" "github.com/kagent-dev/kagent/go/api/v1alpha3" "github.com/kagent-dev/kagent/go/core/internal/egress" @@ -227,7 +228,7 @@ func (c *Compiler) compileProvider(ctx context.Context, resolved *translator.Res environment = append(environment, secretEnvironment(awsSessionTokenEnv, secret.Name, awsSessionTokenEnv)) } } - return codexconfig.Provider{Name: "amazon-bedrock"}, environment, []string{"https://bedrock-runtime." + region + ".amazonaws.com:443"}, nil + return codexconfig.Provider{Name: "amazon-bedrock"}, environment, []string{models.BedrockRuntimeOrigin(region) + ":443"}, nil default: return codexconfig.Provider{}, nil, nil, translator.NewValidationError("Codex does not support ModelConfig provider %q", model.Spec.Provider) } diff --git a/go/core/internal/translator/credentials.go b/go/core/internal/translator/credentials.go index 6c5a47d087..5dccda9a53 100644 --- a/go/core/internal/translator/credentials.go +++ b/go/core/internal/translator/credentials.go @@ -276,7 +276,7 @@ func modelCredentialTarget(resolved *ResolvedModelConfig) (name, endpoint, heade case v1alpha3.ModelProviderBedrock: name, header, prefix = env.AWSBearerTokenBedrock.Name(), "authorization", "Bearer " if spec.Bedrock != nil { - endpoint = fmt.Sprintf("https://bedrock-runtime.%s.amazonaws.com", spec.Bedrock.Region) + endpoint = models.BedrockRuntimeOrigin(spec.Bedrock.Region) } case v1alpha3.ModelProviderOllama: // Ollama Cloud is the only keyed Ollama endpoint, so a binding is