Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions api/v1alpha1/backendsecurity_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,16 @@ type GCPServiceAccountImpersonationConfig struct {

// BackendSecurityPolicyGCPCredentials contains the supported authentication mechanisms to access GCP.
type BackendSecurityPolicyGCPCredentials struct {
// ProjectName is the GCP project name.
//
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
ProjectName string `json:"projectName"`
// Region is the GCP region associated with the policy.
//
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
Region string `json:"region"`
// WorkLoadIdentityFederationConfig is the configuration for the GCP Workload Identity Federation.
//
// +kubebuilder:validation:Required
Expand Down
5 changes: 5 additions & 0 deletions examples/basic/basic.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ metadata:
spec:
type: GCPCredentials
gcpCredentials:
projectName: GCP_PROJECT_NAME # Replace with your GCP project name
region: GCP_REGION # Replace with your GCP region
workLoadIdentityFederationConfig:
projectID: GCP_PROJECT_ID # Replace with your GCP project ID
workloadIdentityPoolName: GCP_WORKLOAD_IDENTITY_POOL # Replace with your workload identity pool name
Expand All @@ -187,6 +189,9 @@ spec:
clientSecret:
name: envoy-ai-gateway-basic-gcp-client-secret
namespace: default
serviceAccountImpersonation:
serviceAccountName: SERVICE_ACCOUNT_NAME # Replace with the service account name to impersonate
serviceAccountProjectName: GCP_SERVICE_ACCOUNT_PROJECT_NAME # Replace with the project name of the service account
---
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: Backend
Expand Down
21 changes: 18 additions & 3 deletions filterapi/filterconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,18 @@ type VersionedAPISchema struct {
type APISchemaName string

const (
APISchemaOpenAI APISchemaName = "OpenAI"
APISchemaAWSBedrock APISchemaName = "AWSBedrock"
// APISchemaOpenAI represents the standard OpenAI API schema.
APISchemaOpenAI APISchemaName = "OpenAI"
// APISchemaAWSBedrock represents the AWS Bedrock API schema.
APISchemaAWSBedrock APISchemaName = "AWSBedrock"
// APISchemaAzureOpenAI represents the Azure OpenAI API schema.
APISchemaAzureOpenAI APISchemaName = "AzureOpenAI"
// APISchemaGCPVertexAI represents the Google Cloud Gemini API schema.
// Used for Gemini models hosted on Google Cloud Vertex AI.
APISchemaGCPVertexAI APISchemaName = "GCPVertexAI"
// APISchemaGCPAnthropic represents the Google Cloud Anthropic API schema.
// Used for Claude models hosted on Google Cloud Vertex AI.
APISchemaGCPAnthropic APISchemaName = "GCPAnthropic"
)

// HeaderMatch is an alias for HTTPHeaderMatch of the Gateway API.
Expand Down Expand Up @@ -226,13 +235,19 @@ type AzureAuth struct {
AccessToken string `json:"accessToken"`
}

// GCPAuth defines the file containing GCP credential that will be mounted to the external proc.
// GCPAuth defines the GCP authentication configuration used to access Google Cloud AI services.
type GCPAuth struct {
// AccessToken is the access token as a literal string.
// This token is obtained through GCP Workload Identity Federation and service account impersonation.
// The token is automatically rotated by the BackendSecurityPolicy controller before expiration.
AccessToken string `json:"accessToken"`
// Region is the GCP region to use for the request.
// This is used in URL path templates when making requests to GCP Vertex AI endpoints.
// Examples: "us-central1", "europe-west4"
Region string `json:"region"`
// ProjectName is the GCP project name to use for the request.
// This is used in URL path templates when making requests to GCP Vertex AI endpoints.
// This should be the project where Vertex AI APIs are enabled.
ProjectName string `json:"projectName"`
}

Expand Down
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ require (
go.uber.org/zap v1.27.0
golang.org/x/exp v0.0.0-20250530174510-65e920069ea6
golang.org/x/oauth2 v0.30.0
google.golang.org/api v0.223.0
google.golang.org/genai v1.13.0
google.golang.org/grpc v1.73.0
google.golang.org/protobuf v1.36.6
k8s.io/api v0.33.1
Expand All @@ -47,6 +49,10 @@ require (
4d63.com/gochecknoglobals v0.2.2 // indirect
al.essio.dev/pkg/shellescape v1.5.1 // indirect
cel.dev/expr v0.23.1 // indirect
cloud.google.com/go v0.116.0 // indirect
cloud.google.com/go/auth v0.15.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
dario.cat/mergo v1.0.1 // indirect
github.com/4meepo/tagalign v1.4.2 // indirect
github.com/Abirdcfly/dupword v0.1.3 // indirect
Expand Down Expand Up @@ -214,8 +220,11 @@ require (
github.com/google/go-github/v56 v56.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/licensecheck v0.3.1 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/gordonklaus/ineffassign v0.1.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
Expand Down
18 changes: 18 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ cel.dev/expr v0.23.1/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M=
cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
Expand Down Expand Up @@ -612,6 +620,8 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 h1:SJ+NtwL6QaZ21U+IrK7d0gGgpjGGvd2kz+FzTHVzdqI=
github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2/go.mod h1:Tv1PlzqC9t8wNnpPdctvtSUOPUUg4SHeE6vR1Ir2hmg=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
Expand All @@ -620,7 +630,11 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
Expand Down Expand Up @@ -1527,9 +1541,13 @@ gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3m
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.223.0 h1:JUTaWEriXmEy5AhvdMgksGGPEFsYfUKaPEYXd4c3Wvc=
google.golang.org/api v0.223.0/go.mod h1:C+RS7Z+dDwds2b+zoAk5hN/eSfsiCn0UDrYof/M4d2M=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genai v1.13.0 h1:LRhwx5PU+bXhfnXyPEHu2kt9yc+MpvuYbajxSorOJjg=
google.golang.org/genai v1.13.0/go.mod h1:QPj5NGJw+3wEOHg+PrsWwJKvG6UC84ex5FR7qAYsN/M=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
Expand Down
34 changes: 34 additions & 0 deletions internal/apischema/gcp/gcp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright Envoy AI Gateway Authors
// SPDX-License-Identifier: Apache-2.0
// The full text of the Apache license is available in the LICENSE file at
// the root of the repo.

package gcp

import "google.golang.org/genai"

type GenerateContentRequest struct {
// Contains the multipart content of a message.
//
// https://github.com/googleapis/go-genai/blob/6a8184fcaf8bf15f0c566616a7b356560309be9b/types.go#L858
Contents []genai.Content `json:"contents"`
// Tool details of a tool that the model may use to generate a response.
//
// https://github.com/googleapis/go-genai/blob/6a8184fcaf8bf15f0c566616a7b356560309be9b/types.go#L1406
Tools []genai.Tool `json:"tools"`
// Optional. Tool config.
// This config is shared for all tools provided in the request.
//
// https://github.com/googleapis/go-genai/blob/6a8184fcaf8bf15f0c566616a7b356560309be9b/types.go#L1466
ToolConfig *genai.ToolConfig `json:"tool_config,omitempty"`
// Optional. Generation config.
// You can find API default values and more details at https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#generationconfig
// and https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/content-generation-parameters.
GenerationConfig *genai.GenerationConfig `json:"generation_config,omitempty"`
// Optional. Instructions for the model to steer it toward better performance.
// For example, "Answer as concisely as possible" or "Don't use technical
// terms in your response".
//
// https://github.com/googleapis/go-genai/blob/6a8184fcaf8bf15f0c566616a7b356560309be9b/types.go#L858
SystemInstruction *genai.Content `json:"system_instruction,omitempty"`
}
49 changes: 49 additions & 0 deletions internal/controller/backend_security_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,26 @@ func (c *BackendSecurityPolicyController) rotateCredential(ctx context.Context,
if err != nil {
return ctrl.Result{}, err
}
case aigv1a1.BackendSecurityPolicyTypeGCPCredentials:
if err = validateGCPCredentialsParams(bsp.Spec.GCPCredentials); err != nil {
return ctrl.Result{}, fmt.Errorf("invalid GCP credentials configuration: %w", err)
}

// For GCP, OIDC is currently the only supported authentication method.
// If additional methods are added, validate that OIDC is used before calling getBackendSecurityPolicyAuthOIDC.
oidc := getBackendSecurityPolicyAuthOIDC(bsp.Spec)

// Create the OIDC token provider that will be used to get tokens from the OIDC provider.
var oidcProvider tokenprovider.TokenProvider
oidcProvider, err = tokenprovider.NewOidcTokenProvider(ctx, c.client, oidc)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to initialize OIDC provider: %w", err)
}
rotator, err = rotators.NewGCPOIDCTokenRotator(c.client, c.logger, *bsp, preRotationWindow, oidcProvider)
if err != nil {
return ctrl.Result{}, err
}

default:
err = fmt.Errorf("backend security type %s does not support OIDC token exchange", bsp.Spec.Type)
c.logger.Error(err, "unsupported backend security type", "namespace", bsp.Namespace, "name", bsp.Name)
Expand Down Expand Up @@ -207,6 +227,10 @@ func getBackendSecurityPolicyAuthOIDC(spec aigv1a1.BackendSecurityPolicySpec) *e
return &spec.AzureCredentials.OIDCExchangeToken.OIDC
}
return nil
case aigv1a1.BackendSecurityPolicyTypeGCPCredentials:
if spec.GCPCredentials != nil {
return &spec.GCPCredentials.WorkLoadIdentityFederationConfig.WorkloadIdentityProvider.OIDCProvider.OIDC
}
}
return nil
}
Expand Down Expand Up @@ -238,3 +262,28 @@ func (c *BackendSecurityPolicyController) updateBackendSecurityPolicyStatus(ctx
c.logger.Error(err, "failed to update BackendSecurityPolicy status")
}
}

func validateGCPCredentialsParams(gcpCreds *aigv1a1.BackendSecurityPolicyGCPCredentials) error {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

shouldn't all of this condition be part of k8s CEL validation? (If so, it's redundant)

if gcpCreds == nil {
return fmt.Errorf("invalid backend security policy, gcp credentials cannot be nil")
}
if gcpCreds.ProjectName == "" {
return fmt.Errorf("invalid GCP credentials configuration: projectName cannot be empty")
}
if gcpCreds.Region == "" {
return fmt.Errorf("invalid GCP credentials configuration: region cannot be empty")
}

wifConfig := gcpCreds.WorkLoadIdentityFederationConfig
if wifConfig.ProjectID == "" {
return fmt.Errorf("invalid GCP Workload Identity Federation configuration: projectID cannot be empty")
}
if wifConfig.WorkloadIdentityPoolName == "" {
return fmt.Errorf("invalid GCP Workload Identity Federation configuration: workloadIdentityPoolName cannot be empty")
}
if wifConfig.WorkloadIdentityProvider.Name == "" {
return fmt.Errorf("invalid GCP Workload Identity Federation configuration: workloadIdentityProvider.name cannot be empty")
}

return nil
}
Loading