Skip to content
Open
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
32 changes: 32 additions & 0 deletions examples/gcp-projected-service-account-values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
providerConfig:
- name: jfrog-credentials-provider
artifactoryUrl: "<artifactory-url>"
matchImages:
- "*.<test.your-org.domain.io>"
defaultCacheDuration: 5h
tokenAttributes:
enabled: true
gcp:
enabled: true
google_service_account_email: "<google-service-account-email>"
jfrog_oidc_audience: "artifactory"
jfrog_oidc_provider_name: "<jfrog-oidc-provider-name>"

rbac:
create: true

serviceAccount:
create: true
annotations:
"iam.gke.io/gcp-service-account": "<google-service-account-email>"
"JFrogExchange": "true"

affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: credentialsProviderEnabled
operator: In
values:
- "true"
3 changes: 3 additions & 0 deletions helm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

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

## [1.0.1] - 12th Mar, 2026
* Added KEP-4412 - Pod Level Identity Support For JFrog Artifactory on GCP

## [1.0.0] - 23rd Feb, 2026
* Allow using an existing ServiceAccount when `serviceAccount.create=false`
* Fixed `defaultCacheDuration` for AWS
Expand Down
4 changes: 2 additions & 2 deletions helm/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ apiVersion: v2
name: jfrog-credential-provider
description: A Helm chart for JFrog Credential Provider supporting AWS, Azure, and GCP
type: application
version: 1.0.0
appVersion: "1.0.0"
version: 1.0.1
appVersion: "1.0.1"
keywords:
- jfrog
- credential-provider
Expand Down
26 changes: 26 additions & 0 deletions helm/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,29 @@ Default RBAC rules for azure with service account token projection
verbs: ["get", "list"]
{{- end }}


{{/*
Default RBAC rules for gcp with service account token projection
*/}}
{{- define "jfrog-credential-provider.defaultRBACRulesGcp" }}
- apiGroups: [""]
resources:
- {{ include "jfrog-credential-provider.jfrogGCPAudience" . | quote }}
verbs:
- request-serviceaccounts-token-audience
{{- end }}

{{/*
Fetching JFrog audience from values configuration
*/}}
{{- define "jfrog-credential-provider.jfrogGCPAudience" -}}
{{- $default := "artifactory" -}}
{{- $audience := $default -}}
{{- range .Values.providerConfig | default list }}
{{- if and (.tokenAttributes) (.gcp) (.tokenAttributes.enabled) (.gcp.enabled) }}
{{- $audience = (default $default .gcp.jfrog_oidc_audience) }}
{{- break -}}
{{- end }}
{{- end }}
{{- $audience -}}
{{- end }}
23 changes: 18 additions & 5 deletions helm/templates/configmap-provider.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ data:
"defaultCacheDuration": {{ .defaultCacheDuration | toJson }},
"apiVersion": "credentialprovider.kubelet.k8s.io/v1",
{{- end }}

{{- /* This is only supported for AWS and Azure at the moment */ -}}

{{- /* This is only supported for AWS, Azure and GCP at the moment */ -}}

{{- if and .tokenAttributes .tokenAttributes.enabled (eq $cloudProvider "aws") }}
"tokenAttributes": {
"serviceAccountTokenAudience": "sts.amazonaws.com",
Expand All @@ -38,16 +39,28 @@ data:
},
{{- else if and .tokenAttributes .tokenAttributes.enabled (eq $cloudProvider "azure") }}
tokenAttributes:
{{- if .azure.azure_app_audience }}
{{- if .azure.azure_app_audience }}
serviceAccountTokenAudience: {{ .azure.azure_app_audience }}
{{- else}}
{{- else}}
serviceAccountTokenAudience: api://AzureADTokenExchange
{{- end }}
{{- end }}
cacheType: ServiceAccount
requireServiceAccount: true
requiredServiceAccountAnnotationKeys:
- azure.workload.identity/client-id
- JFrogExchange
{{- else if and .tokenAttributes .tokenAttributes.enabled (eq $cloudProvider "gcp") }}
tokenAttributes:
{{- if .gcp.jfrog_oidc_audience }}
serviceAccountTokenAudience: {{ .gcp.jfrog_oidc_audience }}
{{- else}}
serviceAccountTokenAudience: "artifactory"
{{- end }}
cacheType: ServiceAccount
requireServiceAccount: true
requiredServiceAccountAnnotationKeys:
- iam.gke.io/gcp-service-account
- JFrogExchange
{{- end }}

{{- if eq $cloudProvider "aws" }}
Expand Down
1 change: 0 additions & 1 deletion helm/templates/configmap-setup.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ data:
export JFROG_CREDENTIAL_PROVIDER_BINARY_DIR="/home/kubernetes/bin"
export KUBELET_CREDENTIAL_PROVIDER_CONFIG_PATH="/etc/srv/kubernetes/cri_auth_config.yaml"
{{- end }}


{{- range .Values.providerConfig }}
JFROG_CONFIG_FILE="jfrog-provider"
Expand Down
4 changes: 3 additions & 1 deletion helm/templates/role.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{{- $cloudProvider := include "jfrog-credential-provider.cloudProvider" . }}
{{- if and .Values.rbac.create (or (eq $cloudProvider "azure") (eq $cloudProvider "aws")) }}
{{- if and .Values.rbac.create (or (eq $cloudProvider "azure") (eq $cloudProvider "aws") (eq $cloudProvider "gcp")) }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
Expand All @@ -12,6 +12,8 @@ rules:
{{- include "jfrog-credential-provider.defaultRBACRulesAWS" . | nindent 2 }}
{{- else if (eq $cloudProvider "azure") }}
{{- include "jfrog-credential-provider.defaultRBACRulesAzure" . | nindent 2 }}
{{- else if (eq $cloudProvider "gcp") }}
{{- include "jfrog-credential-provider.defaultRBACRulesGcp" . | nindent 2 }}
{{- end }}
{{- range .Values.rbac.role.additionalRules }}
- {{- toYaml . | nindent 4 }}
Expand Down
2 changes: 1 addition & 1 deletion helm/templates/rolebinding.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{{- $cloudProvider := include "jfrog-credential-provider.cloudProvider" . }}
{{- if and .Values.rbac.create (or (eq $cloudProvider "aws") (eq $cloudProvider "azure")) }}
{{- if and .Values.rbac.create (or (eq $cloudProvider "aws") (eq $cloudProvider "azure") (eq $cloudProvider "gcp")) }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
Expand Down
4 changes: 3 additions & 1 deletion helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,6 @@ serviceAccount:
create: true
name: ""
annotations: {}

## Example for GCP Pod Identity
# "iam.gke.io/gcp-service-account": "<GCP Service Account ID>@<PROJECT ID>.iam.gserviceaccount.com"
# "JFrogExchange": "true"
25 changes: 16 additions & 9 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func cloudProviderAuth(svc *service.Service, ctx context.Context, logs *logger.L
return rtUsername, rtToken
case utils.CloudProviderGoogle:
logs.Debug("Detected Google cloud provider")
rtUsername, rtToken = handleGoogleAuth(svc, ctx, logs, artifactoryUrl)
rtUsername, rtToken = handleGoogleAuth(svc, ctx, logs, artifactoryUrl, request)
return rtUsername, rtToken
default:
logs.Exit("ERROR in JFrog Credentials provider, cloud_provider value should be either aws, azure, or google", 1)
Expand Down Expand Up @@ -256,6 +256,7 @@ func handleAzureAuth(svc *service.Service, ctx context.Context, logs *logger.Log
logs.Info(fmt.Sprintf("getting envs - azureAppClientId: %s, azureNodepoolClientId: %s, azureAppTenantId: %s, azureAppAudience: %s, jfrogOidcProviderName: %s",
azureAppClientId, azureNodepoolClientId, azureAppTenantId, azureAppAudience, jfrogOidcProviderName))
}
logs.Info("Service Account Token obtained using Node Identity (VM Service Account)")
// Get Azure OIDC token
token, err = handlers.GetAzureOIDCToken(svc, ctx, azureAppTenantId, azureAppClientId, azureNodepoolClientId, azureAppAudience)
} else {
Expand All @@ -265,6 +266,7 @@ func handleAzureAuth(svc *service.Service, ctx context.Context, logs *logger.Log
logs.Info(fmt.Sprintf("getting envs - azureAppClientId: %s, azureAppAudience: %s, jfrogOidcProviderName: %s",
azureAppClientId, azureAppAudience, jfrogOidcProviderName))
}
logs.Info("Service Account Token obtained using Pod Identity (Kubernetes Workload Identity)")
token = request.ServiceAccountToken
}
if err != nil {
Expand All @@ -280,31 +282,37 @@ func handleAzureAuth(svc *service.Service, ctx context.Context, logs *logger.Log
return rtUsername, rtToken
}

func handleGoogleAuth(svc *service.Service, ctx context.Context, logs *logger.Logger, artifactoryUrl string) (string, string) {
func handleGoogleAuth(svc *service.Service, ctx context.Context, logs *logger.Logger, artifactoryUrl string, request utils.CredentialProviderRequest) (string, string) {
// get required env variables
googleServiceAccountEmail := utils.GetEnvs(logs, "google_service_account_email", "")
jfrogOidcProviderAudience := utils.GetEnvs(logs, "jfrog_oidc_audience", "")
jfrogOidcProviderName := utils.GetEnvs(logs, "jfrog_oidc_provider_name", "")

var token string
var err error
if googleServiceAccountEmail == "" || jfrogOidcProviderAudience == "" || jfrogOidcProviderName == "" {
logs.Exit("ERROR in JFrog Credentials provider, environment variables missing: google_service_account_email, jfrog_oidc_audience, jfrog_oidc_provider_name", 1)
} else {
logs.Info(fmt.Sprintf("getting envs - googleServiceAccountEmail: %s, jfrogOidcProviderAudience: %s, jfrogOidcProviderName: %s",
googleServiceAccountEmail, jfrogOidcProviderAudience, jfrogOidcProviderName))
}

// Get Google OIDC token
token, err := handlers.GetGoogleOIDCToken(svc, ctx, googleServiceAccountEmail, jfrogOidcProviderAudience)
if err != nil {
logs.Exit("ERROR in GetGoogleOIDCToken :"+err.Error(), 1)
if request.ServiceAccountAnnotations["JFrogExchange"] == "true" {
logs.Info("Service Account Token obtained using Pod Identity (Kubernetes Workload Identity)")
token = request.ServiceAccountToken
} else {
// Get Google OIDC token
logs.Info("Service Account Token obtained using Node Identity (VM Service Account)")
token, err = handlers.GetGoogleOIDCToken(svc, ctx, googleServiceAccountEmail, jfrogOidcProviderAudience)
if err != nil {
logs.Exit("ERROR in GetGoogleOIDCToken :"+err.Error(), 1)
}
}

// Exchange Google OIDC token with JFrog Artifactory token
rtUsername, rtToken, err := handlers.ExchangeOidcArtifactoryToken(svc, ctx, token, artifactoryUrl, jfrogOidcProviderName, jfrogOidcProviderAudience)
if err != nil {
logs.Exit("ERROR in JFrog Credentials provider, error in createArtifactoryToken :"+err.Error(), 1)
}

return rtUsername, rtToken
}

Expand All @@ -326,6 +334,5 @@ func generateAndOutputResponse(logs *logger.Logger, request utils.CredentialProv
if err != nil {
logs.Exit("Error marshaling JSON :"+err.Error(), 1)
}

os.Stdout.Write(jsonBytes)
}
Loading