Skip to content

Commit 32bfdf9

Browse files
authored
Pod Level Identity Support For JFrog Artifactory on GCP (#54)
Signed-off-by: oumk <oumk@jfrog.com>
1 parent e3e1256 commit 32bfdf9

8 files changed

Lines changed: 96 additions & 18 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
providerConfig:
2+
- name: jfrog-credentials-provider
3+
artifactoryUrl: "<artifactory-url>"
4+
matchImages:
5+
- "*.<test.your-org.domain.io>"
6+
defaultCacheDuration: 5h
7+
tokenAttributes:
8+
enabled: true
9+
gcp:
10+
enabled: true
11+
google_service_account_email: "<google-service-account-email>"
12+
jfrog_oidc_audience: "artifactory"
13+
jfrog_oidc_provider_name: "<jfrog-oidc-provider-name>"
14+
15+
rbac:
16+
create: true
17+
18+
serviceAccount:
19+
create: true
20+
annotations:
21+
"iam.gke.io/gcp-service-account": "<google-service-account-email>"
22+
"JFrogExchange": "true"
23+
24+
affinity:
25+
nodeAffinity:
26+
requiredDuringSchedulingIgnoredDuringExecution:
27+
nodeSelectorTerms:
28+
- matchExpressions:
29+
- key: credentialsProviderEnabled
30+
operator: In
31+
values:
32+
- "true"

helm/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
All notable changes to this Helm chart will be documented in this file.
44

5-
## [1.0.1] - 18th Mar, 2026
5+
## [1.0.1] - 25th Mar, 2026
66
* Added support for disabling auto-upgrade of binary through `autoUpgrade`
77
* Added support for `aws_region` for `assume_role` authentication method
8+
* Added KEP-4412 - Pod Level Identity Support For JFrog Artifactory on GCP
89

910
## [1.0.0] - 23rd Feb, 2026
1011
* Allow using an existing ServiceAccount when `serviceAccount.create=false`

helm/templates/_helpers.tpl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,29 @@ Default RBAC rules for azure with service account token projection
127127
verbs: ["get", "list"]
128128
{{- end }}
129129

130+
131+
{{/*
132+
Default RBAC rules for gcp with service account token projection
133+
*/}}
134+
{{- define "jfrog-credential-provider.defaultRBACRulesGcp" }}
135+
- apiGroups: [""]
136+
resources:
137+
- {{ include "jfrog-credential-provider.jfrogGCPAudience" . | quote }}
138+
verbs:
139+
- request-serviceaccounts-token-audience
140+
{{- end }}
141+
142+
{{/*
143+
Fetching JFrog audience from values configuration
144+
*/}}
145+
{{- define "jfrog-credential-provider.jfrogGCPAudience" -}}
146+
{{- $default := "artifactory" -}}
147+
{{- $audience := $default -}}
148+
{{- range .Values.providerConfig | default list }}
149+
{{- if and (.tokenAttributes) (.gcp) (.tokenAttributes.enabled) (.gcp.enabled) }}
150+
{{- $audience = (default $default .gcp.jfrog_oidc_audience) }}
151+
{{- break -}}
152+
{{- end }}
153+
{{- end }}
154+
{{- $audience -}}
155+
{{- end }}

helm/templates/configmap-provider.yaml

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ data:
2424
"defaultCacheDuration": {{ .defaultCacheDuration | toJson }},
2525
"apiVersion": "credentialprovider.kubelet.k8s.io/v1",
2626
{{- end }}
27-
28-
{{- /* This is only supported for AWS and Azure at the moment */ -}}
27+
2928
{{- if and .tokenAttributes .tokenAttributes.enabled (eq $cloudProvider "aws") }}
3029
"tokenAttributes": {
3130
"serviceAccountTokenAudience": "sts.amazonaws.com",
@@ -38,16 +37,28 @@ data:
3837
},
3938
{{- else if and .tokenAttributes .tokenAttributes.enabled (eq $cloudProvider "azure") }}
4039
tokenAttributes:
41-
{{- if .azure.azure_app_audience }}
40+
{{- if .azure.azure_app_audience }}
4241
serviceAccountTokenAudience: {{ .azure.azure_app_audience }}
43-
{{- else}}
42+
{{- else}}
4443
serviceAccountTokenAudience: api://AzureADTokenExchange
45-
{{- end }}
44+
{{- end }}
4645
cacheType: ServiceAccount
4746
requireServiceAccount: true
4847
requiredServiceAccountAnnotationKeys:
4948
- azure.workload.identity/client-id
5049
- JFrogExchange
50+
{{- else if and .tokenAttributes .tokenAttributes.enabled (eq $cloudProvider "gcp") }}
51+
tokenAttributes:
52+
{{- if .gcp.jfrog_oidc_audience }}
53+
serviceAccountTokenAudience: {{ .gcp.jfrog_oidc_audience }}
54+
{{- else}}
55+
serviceAccountTokenAudience: "artifactory"
56+
{{- end }}
57+
cacheType: ServiceAccount
58+
requireServiceAccount: true
59+
requiredServiceAccountAnnotationKeys:
60+
- iam.gke.io/gcp-service-account
61+
- JFrogExchange
5162
{{- end }}
5263

5364
{{- if eq $cloudProvider "aws" }}

helm/templates/configmap-setup.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ data:
3131
export JFROG_CREDENTIAL_PROVIDER_BINARY_DIR="/home/kubernetes/bin"
3232
export KUBELET_CREDENTIAL_PROVIDER_CONFIG_PATH="/etc/srv/kubernetes/cri_auth_config.yaml"
3333
{{- end }}
34-
3534
3635
{{- range .Values.providerConfig }}
3736
JFROG_CONFIG_FILE="jfrog-provider"

helm/templates/role.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{{- $cloudProvider := include "jfrog-credential-provider.cloudProvider" . }}
2-
{{- if and .Values.rbac.create (or (eq $cloudProvider "azure") (eq $cloudProvider "aws")) }}
2+
{{- if and .Values.rbac.create (or (eq $cloudProvider "azure") (eq $cloudProvider "aws") (eq $cloudProvider "gcp")) }}
33
apiVersion: rbac.authorization.k8s.io/v1
44
kind: ClusterRole
55
metadata:
@@ -12,6 +12,8 @@ rules:
1212
{{- include "jfrog-credential-provider.defaultRBACRulesAWS" . | nindent 2 }}
1313
{{- else if (eq $cloudProvider "azure") }}
1414
{{- include "jfrog-credential-provider.defaultRBACRulesAzure" . | nindent 2 }}
15+
{{- else if (eq $cloudProvider "gcp") }}
16+
{{- include "jfrog-credential-provider.defaultRBACRulesGcp" . | nindent 2 }}
1517
{{- end }}
1618
{{- range .Values.rbac.role.additionalRules }}
1719
- {{- toYaml . | nindent 4 }}

helm/templates/rolebinding.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{{- $cloudProvider := include "jfrog-credential-provider.cloudProvider" . }}
2-
{{- if and .Values.rbac.create (or (eq $cloudProvider "aws") (eq $cloudProvider "azure")) }}
2+
{{- if and .Values.rbac.create (or (eq $cloudProvider "aws") (eq $cloudProvider "azure") (eq $cloudProvider "gcp")) }}
33
apiVersion: rbac.authorization.k8s.io/v1
44
kind: ClusterRoleBinding
55
metadata:

internal/provider/provider.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ func cloudProviderAuth(svc *service.Service, ctx context.Context, logs *logger.L
143143
return rtUsername, rtToken
144144
case utils.CloudProviderGoogle:
145145
logs.Debug("Detected Google cloud provider")
146-
rtUsername, rtToken = handleGoogleAuth(svc, ctx, logs, artifactoryUrl)
146+
rtUsername, rtToken = handleGoogleAuth(svc, ctx, logs, artifactoryUrl, request)
147147
return rtUsername, rtToken
148148
default:
149149
logs.Exit("ERROR in JFrog Credentials provider, cloud_provider value should be either aws, azure, or google", 1)
@@ -256,6 +256,7 @@ func handleAzureAuth(svc *service.Service, ctx context.Context, logs *logger.Log
256256
logs.Info(fmt.Sprintf("getting envs - azureAppClientId: %s, azureNodepoolClientId: %s, azureAppTenantId: %s, azureAppAudience: %s, jfrogOidcProviderName: %s",
257257
azureAppClientId, azureNodepoolClientId, azureAppTenantId, azureAppAudience, jfrogOidcProviderName))
258258
}
259+
logs.Info("Service Account Token obtained using Node Identity (VM Service Account)")
259260
// Get Azure OIDC token
260261
token, err = handlers.GetAzureOIDCToken(svc, ctx, azureAppTenantId, azureAppClientId, azureNodepoolClientId, azureAppAudience)
261262
} else {
@@ -265,6 +266,7 @@ func handleAzureAuth(svc *service.Service, ctx context.Context, logs *logger.Log
265266
logs.Info(fmt.Sprintf("getting envs - azureAppClientId: %s, azureAppAudience: %s, jfrogOidcProviderName: %s",
266267
azureAppClientId, azureAppAudience, jfrogOidcProviderName))
267268
}
269+
logs.Info("Service Account Token obtained using Pod Identity (Kubernetes Workload Identity)")
268270
token = request.ServiceAccountToken
269271
}
270272
if err != nil {
@@ -280,31 +282,37 @@ func handleAzureAuth(svc *service.Service, ctx context.Context, logs *logger.Log
280282
return rtUsername, rtToken
281283
}
282284

283-
func handleGoogleAuth(svc *service.Service, ctx context.Context, logs *logger.Logger, artifactoryUrl string) (string, string) {
285+
func handleGoogleAuth(svc *service.Service, ctx context.Context, logs *logger.Logger, artifactoryUrl string, request utils.CredentialProviderRequest) (string, string) {
284286
// get required env variables
285287
googleServiceAccountEmail := utils.GetEnvs(logs, "google_service_account_email", "")
286288
jfrogOidcProviderAudience := utils.GetEnvs(logs, "jfrog_oidc_audience", "")
287289
jfrogOidcProviderName := utils.GetEnvs(logs, "jfrog_oidc_provider_name", "")
288-
290+
var token string
291+
var err error
289292
if googleServiceAccountEmail == "" || jfrogOidcProviderAudience == "" || jfrogOidcProviderName == "" {
290293
logs.Exit("ERROR in JFrog Credentials provider, environment variables missing: google_service_account_email, jfrog_oidc_audience, jfrog_oidc_provider_name", 1)
291294
} else {
292295
logs.Info(fmt.Sprintf("getting envs - googleServiceAccountEmail: %s, jfrogOidcProviderAudience: %s, jfrogOidcProviderName: %s",
293296
googleServiceAccountEmail, jfrogOidcProviderAudience, jfrogOidcProviderName))
294297
}
295298

296-
// Get Google OIDC token
297-
token, err := handlers.GetGoogleOIDCToken(svc, ctx, googleServiceAccountEmail, jfrogOidcProviderAudience)
298-
if err != nil {
299-
logs.Exit("ERROR in GetGoogleOIDCToken :"+err.Error(), 1)
299+
if request.ServiceAccountAnnotations["JFrogExchange"] == "true" {
300+
logs.Info("Service Account Token obtained using Pod Identity (Kubernetes Workload Identity)")
301+
token = request.ServiceAccountToken
302+
} else {
303+
// Get Google OIDC token
304+
logs.Info("Service Account Token obtained using Node Identity (VM Service Account)")
305+
token, err = handlers.GetGoogleOIDCToken(svc, ctx, googleServiceAccountEmail, jfrogOidcProviderAudience)
306+
if err != nil {
307+
logs.Exit("ERROR in GetGoogleOIDCToken :"+err.Error(), 1)
308+
}
300309
}
301310

302311
// Exchange Google OIDC token with JFrog Artifactory token
303312
rtUsername, rtToken, err := handlers.ExchangeOidcArtifactoryToken(svc, ctx, token, artifactoryUrl, jfrogOidcProviderName, jfrogOidcProviderAudience)
304313
if err != nil {
305314
logs.Exit("ERROR in JFrog Credentials provider, error in createArtifactoryToken :"+err.Error(), 1)
306315
}
307-
308316
return rtUsername, rtToken
309317
}
310318

@@ -326,6 +334,5 @@ func generateAndOutputResponse(logs *logger.Logger, request utils.CredentialProv
326334
if err != nil {
327335
logs.Exit("Error marshaling JSON :"+err.Error(), 1)
328336
}
329-
330337
os.Stdout.Write(jsonBytes)
331338
}

0 commit comments

Comments
 (0)