Skip to content

Commit da86fc6

Browse files
authored
feat: support gcp service account key file and gcp doc (#1090)
**Description** Added the support for GCP static service account credential file for an easier way to show case how to access GenAI models on GCP VertexAI. **Related Issues/PRs (if applicable)** Fixes #1015 Closes #609 --------- Signed-off-by: Dan Sun <[email protected]>
1 parent a6917fe commit da86fc6

22 files changed

+1332
-106
lines changed

api/v1alpha1/backendsecurity_policy.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ type GCPServiceAccountImpersonationConfig struct {
180180
}
181181

182182
// BackendSecurityPolicyGCPCredentials contains the supported authentication mechanisms to access GCP.
183+
// +kubebuilder:validation:XValidation:rule="(has(self.credentialsFile) && !has(self.workloadIdentityFederationConfig)) || (has(self.workloadIdentityFederationConfig) && !has(self.credentialsFile))",message="Exactly one of GCPWorkloadIdentityFederationConfig or GCPCredentialsFile must be specified"
183184
type BackendSecurityPolicyGCPCredentials struct {
184185
// ProjectName is the GCP project name.
185186
//
@@ -192,10 +193,15 @@ type BackendSecurityPolicyGCPCredentials struct {
192193
// +kubebuilder:validation:MinLength=1
193194
Region string `json:"region"`
194195

196+
// CredentialsFile specifies the service account credentials file to use for the GCP provider.
197+
//
198+
// +optional
199+
CredentialsFile *GCPCredentialsFile `json:"credentialsFile,omitempty"`
200+
195201
// WorkloadIdentityFederationConfig is the configuration for the GCP Workload Identity Federation.
196202
//
197-
// +kubebuilder:validation:Required
198-
WorkloadIdentityFederationConfig GCPWorkloadIdentityFederationConfig `json:"workloadIdentityFederationConfig"`
203+
// +optional
204+
WorkloadIdentityFederationConfig *GCPWorkloadIdentityFederationConfig `json:"workloadIdentityFederationConfig,omitempty"`
199205
}
200206

201207
// BackendSecurityPolicyAzureCredentials contains the supported authentication mechanisms to access Azure.
@@ -285,3 +291,11 @@ type AWSOIDCExchangeToken struct {
285291
// +kubebuilder:validation:MinLength=1
286292
AwsRoleArn string `json:"awsRoleArn"`
287293
}
294+
295+
// GCPCredentialsFile specifies the service account key json file to authenticate with GCP provider.
296+
type GCPCredentialsFile struct {
297+
// SecretRef is the reference to the credential file.
298+
//
299+
// The secret should contain the GCP service account credentials file keyed on "service_account.json".
300+
SecretRef *gwapiv1.SecretObjectReference `json:"secretRef"`
301+
}

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 30 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/basic/gcp_vertex.yaml

Lines changed: 84 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ spec:
1818
group: gateway.envoyproxy.io
1919
---
2020
apiVersion: aigateway.envoyproxy.io/v1alpha1
21+
kind: AIServiceBackend
22+
metadata:
23+
name: envoy-ai-gateway-basic-gcp-anthropic
24+
namespace: default
25+
spec:
26+
schema:
27+
name: GCPAnthropic
28+
backendRef:
29+
name: envoy-ai-gateway-basic-gcp-us-east5
30+
kind: Backend
31+
group: gateway.envoyproxy.io
32+
---
33+
apiVersion: aigateway.envoyproxy.io/v1alpha1
2134
kind: BackendSecurityPolicy
2235
metadata:
2336
name: envoy-ai-gateway-basic-gcp-credentials
@@ -27,24 +40,16 @@ spec:
2740
- group: aigateway.envoyproxy.io
2841
kind: AIServiceBackend
2942
name: envoy-ai-gateway-basic-gcp
43+
- group: aigateway.envoyproxy.io
44+
kind: AIServiceBackend
45+
name: envoy-ai-gateway-basic-gcp-anthropic
3046
type: GCPCredentials
3147
gcpCredentials:
3248
projectName: GCP_PROJECT_NAME # Replace with your GCP project name
3349
region: GCP_REGION # Replace with your GCP region
34-
workloadIdentityFederationConfig:
35-
projectID: GCP_PROJECT_ID # Replace with your GCP project ID
36-
workloadIdentityPoolName: GCP_WORKLOAD_IDENTITY_POOL # Replace with your workload identity pool name
37-
workloadIdentityProviderName: GCP_IDENTITY_PROVIDER_NAME # Replace with the identity provider configured with GCP
38-
serviceAccountImpersonation:
39-
serviceAccountName: SERVICE_ACCOUNT_NAME # Replace with the service account name to impersonate
40-
oidcExchangeToken:
41-
oidc:
42-
provider:
43-
issuer: GCP_OIDC_PROVIDER_ISSUER # Replace with your OIDC provider issuer
44-
clientID: GCP_OIDC_CLIENT_ID # Replace with your OIDC client ID
45-
clientSecret:
46-
name: envoy-ai-gateway-basic-gcp-client-secret
47-
namespace: default
50+
credentialsFile:
51+
secretRef:
52+
name: envoy-ai-gateway-basic-gcp-service-account-key-file
4853
---
4954
apiVersion: gateway.envoyproxy.io/v1alpha1
5055
kind: Backend
@@ -57,6 +62,17 @@ spec:
5762
hostname: us-central1-aiplatform.googleapis.com
5863
port: 443
5964
---
65+
apiVersion: gateway.envoyproxy.io/v1alpha1
66+
kind: Backend
67+
metadata:
68+
name: envoy-ai-gateway-basic-gcp-us-east5
69+
namespace: default
70+
spec:
71+
endpoints:
72+
- fqdn:
73+
hostname: us-east5-aiplatform.googleapis.com
74+
port: 443
75+
---
6076
apiVersion: gateway.networking.k8s.io/v1alpha3
6177
kind: BackendTLSPolicy
6278
metadata:
@@ -71,10 +87,62 @@ spec:
7187
wellKnownCACertificates: "System"
7288
hostname: us-central1-aiplatform.googleapis.com
7389
---
90+
apiVersion: gateway.networking.k8s.io/v1alpha3
91+
kind: BackendTLSPolicy
92+
metadata:
93+
name: envoy-ai-gateway-basic-gcp-us-east5-tls
94+
namespace: default
95+
spec:
96+
targetRefs:
97+
- group: 'gateway.envoyproxy.io'
98+
kind: Backend
99+
name: envoy-ai-gateway-basic-gcp-us-east5
100+
validation:
101+
wellKnownCACertificates: "System"
102+
hostname: us-east5-aiplatform.googleapis.com
103+
---
74104
apiVersion: v1
75105
kind: Secret
76106
metadata:
77-
name: envoy-ai-gateway-basic-gcp-client-secret
107+
name: envoy-ai-gateway-basic-gcp-service-account-key-file
78108
namespace: default
79109
stringData:
80-
client-secret: "GCP_OIDC_CLIENT_SECRET" # Replace with your OIDC client secret
110+
service_account.json: "{}" # Replace with your service account json key file
111+
---
112+
apiVersion: aigateway.envoyproxy.io/v1alpha1
113+
kind: AIGatewayRoute
114+
metadata:
115+
name: envoy-ai-gateway-basic-gcp
116+
namespace: default
117+
spec:
118+
parentRefs:
119+
- name: envoy-ai-gateway-basic
120+
kind: Gateway
121+
group: gateway.networking.k8s.io
122+
rules:
123+
- matches:
124+
- headers:
125+
- type: Exact
126+
name: x-ai-eg-model
127+
value: gemini-2.5-flash
128+
backendRefs:
129+
- name: envoy-ai-gateway-basic-gcp
130+
---
131+
apiVersion: aigateway.envoyproxy.io/v1alpha1
132+
kind: AIGatewayRoute
133+
metadata:
134+
name: envoy-ai-gateway-basic-gcp-anthropic
135+
namespace: default
136+
spec:
137+
parentRefs:
138+
- name: envoy-ai-gateway-basic
139+
kind: Gateway
140+
group: gateway.networking.k8s.io
141+
rules:
142+
- matches:
143+
- headers:
144+
- type: Exact
145+
name: x-ai-eg-model
146+
value: claude-3-7-sonnet@20250219
147+
backendRefs:
148+
- name: envoy-ai-gateway-basic-gcp-anthropic

internal/controller/backend_security_policy.go

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -163,20 +163,46 @@ func (c *BackendSecurityPolicyController) rotateCredential(ctx context.Context,
163163
if err = validateGCPCredentialsParams(bsp.Spec.GCPCredentials); err != nil {
164164
return ctrl.Result{}, fmt.Errorf("invalid GCP credentials configuration: %w", err)
165165
}
166-
167-
// For GCP, OIDC is currently the only supported authentication method.
168-
// If additional methods are added, validate that OIDC is used before calling getBackendSecurityPolicyAuthOIDC.
169166
oidc := getBackendSecurityPolicyAuthOIDC(bsp.Spec)
167+
if oidc != nil {
168+
// Create the OIDC token provider that will be used to get tokens from the OIDC provider.
169+
var oidcProvider tokenprovider.TokenProvider
170+
oidcProvider, err = tokenprovider.NewOidcTokenProvider(ctx, c.client, oidc)
171+
if err != nil {
172+
return ctrl.Result{}, fmt.Errorf("failed to initialize OIDC provider: %w", err)
173+
}
174+
rotator, err = rotators.NewGCPOIDCTokenRotator(c.client, c.logger, *bsp, preRotationWindow, oidcProvider)
175+
if err != nil {
176+
return ctrl.Result{}, err
177+
}
178+
} else if credentialFile := bsp.Spec.GCPCredentials.CredentialsFile; credentialFile != nil {
179+
secretNamespace := bsp.Namespace
180+
if credentialFile.SecretRef.Namespace != nil {
181+
secretNamespace = string(*credentialFile.SecretRef.Namespace)
182+
}
183+
secretName := string(credentialFile.SecretRef.Name)
184+
var secret *corev1.Secret
185+
secret, err = rotators.LookupSecret(ctx, c.client, secretNamespace, secretName)
186+
if err != nil {
187+
c.logger.Error(err, "failed to lookup gcp service account key secret", "namespace", secretNamespace, "name", secretName)
188+
return ctrl.Result{}, err
189+
}
190+
serviceAccountKeyJSON, exists := secret.Data[rotators.GCPServiceAccountJSON]
191+
if !exists {
192+
return ctrl.Result{}, fmt.Errorf("missing gcp service account key %s", rotators.GCPServiceAccountJSON)
193+
}
194+
var tokenProvider tokenprovider.TokenProvider
195+
tokenProvider, err = tokenprovider.NewGCPTokenProvider(ctx, serviceAccountKeyJSON)
196+
if err != nil {
197+
return ctrl.Result{}, err
198+
}
170199

171-
// Create the OIDC token provider that will be used to get tokens from the OIDC provider.
172-
var oidcProvider tokenprovider.TokenProvider
173-
oidcProvider, err = tokenprovider.NewOidcTokenProvider(ctx, c.client, oidc)
174-
if err != nil {
175-
return ctrl.Result{}, fmt.Errorf("failed to initialize OIDC provider: %w", err)
176-
}
177-
rotator, err = rotators.NewGCPOIDCTokenRotator(c.client, c.logger, *bsp, preRotationWindow, oidcProvider)
178-
if err != nil {
179-
return ctrl.Result{}, err
200+
rotator, err = rotators.NewGCPTokenRotator(c.client, c.kube, c.logger, bsp.Namespace, bsp.Name, preRotationWindow, tokenProvider)
201+
if err != nil {
202+
return ctrl.Result{}, err
203+
}
204+
} else {
205+
return ctrl.Result{}, fmt.Errorf("one of service account key json file or oidc must be defined, namespace %s name %s", bsp.Namespace, bsp.Name)
180206
}
181207

182208
default:
@@ -258,7 +284,7 @@ func getBackendSecurityPolicyAuthOIDC(spec aigv1a1.BackendSecurityPolicySpec) *e
258284
}
259285
return nil
260286
case aigv1a1.BackendSecurityPolicyTypeGCPCredentials:
261-
if spec.GCPCredentials != nil {
287+
if spec.GCPCredentials != nil && spec.GCPCredentials.WorkloadIdentityFederationConfig != nil {
262288
return &spec.GCPCredentials.WorkloadIdentityFederationConfig.OIDCExchangeToken.OIDC
263289
}
264290
}
@@ -339,16 +365,17 @@ func validateGCPCredentialsParams(gcpCreds *aigv1a1.BackendSecurityPolicyGCPCred
339365
}
340366

341367
wifConfig := gcpCreds.WorkloadIdentityFederationConfig
342-
if wifConfig.ProjectID == "" {
343-
return fmt.Errorf("invalid GCP Workload Identity Federation configuration: projectID cannot be empty")
344-
}
345-
if wifConfig.WorkloadIdentityPoolName == "" {
346-
return fmt.Errorf("invalid GCP Workload Identity Federation configuration: workloadIdentityPoolName cannot be empty")
347-
}
348-
if wifConfig.WorkloadIdentityProviderName == "" {
349-
return fmt.Errorf("invalid GCP Workload Identity Federation configuration: workloadIdentityProvider.name cannot be empty")
368+
if wifConfig != nil {
369+
if wifConfig.ProjectID == "" {
370+
return fmt.Errorf("invalid GCP Workload Identity Federation configuration: projectID cannot be empty")
371+
}
372+
if wifConfig.WorkloadIdentityPoolName == "" {
373+
return fmt.Errorf("invalid GCP Workload Identity Federation configuration: workloadIdentityPoolName cannot be empty")
374+
}
375+
if wifConfig.WorkloadIdentityProviderName == "" {
376+
return fmt.Errorf("invalid GCP Workload Identity Federation configuration: workloadIdentityProvider.name cannot be empty")
377+
}
350378
}
351-
352379
return nil
353380
}
354381

@@ -365,6 +392,9 @@ func getBSPGeneratedSecretName(bsp *aigv1a1.BackendSecurityPolicy) string {
365392
return ""
366393
}
367394
case aigv1a1.BackendSecurityPolicyTypeGCPCredentials:
395+
if bsp.Spec.GCPCredentials.WorkloadIdentityFederationConfig == nil {
396+
return ""
397+
}
368398
case aigv1a1.BackendSecurityPolicyTypeAPIKey:
369399
return "" // APIKey does not require rotation.
370400
default:

0 commit comments

Comments
 (0)