Workload Identity Federation
Passwordless authentication to Snowflake — no passwords, private keys, or static credentials stored anywhere.
How It Works
- Kubernetes projects a short-lived OIDC token from the cluster’s ServiceAccount into the operator pod
- The gosnowflake driver reads the token from the projected volume path on each new connection
- The driver performs native WIF attestation exchange with Snowflake
- Snowflake validates the token against the configured OIDC issuer
- Kubelet automatically refreshes the token before expiry
Prerequisites
- Kubernetes cluster with OIDC issuer (EKS, GKE, AKS have this by default)
- Snowflake account with External OAuth support
- A SERVICE-type user in Snowflake
- Access to create Security Integrations
Create a SERVICE User
USE ROLE ACCOUNTADMIN;
CREATE USER IF NOT EXISTS SVC_SNOWPLANE
DEFAULT_ROLE = SNOWPLANE_ROLE
TYPE = SERVICE
COMMENT = 'Snowplane WIF service user';
GRANT ROLE SNOWPLANE_ROLE TO USER SVC_SNOWPLANE;
See Snowflake Role Setup for configuring role privileges.
Create a Security Integration
EKS (IRSA)
aws eks describe-cluster --name <cluster-name> \
--query "cluster.identity.oidc.issuer" --output text
CREATE SECURITY INTEGRATION IF NOT EXISTS SNOWPLANE_EKS_WIF
TYPE = EXTERNAL_OAUTH
ENABLED = TRUE
EXTERNAL_OAUTH_TYPE = CUSTOM
EXTERNAL_OAUTH_ISSUER = 'https://oidc.eks.us-east-1.amazonaws.com/id/EXAMPLE1234567890'
EXTERNAL_OAUTH_TOKEN_USER_MAPPING_CLAIM = 'sub'
EXTERNAL_OAUTH_SNOWFLAKE_USER_MAPPING_ATTRIBUTE = 'LOGIN_NAME'
EXTERNAL_OAUTH_ANY_ROLE_MODE = 'ENABLE'
EXTERNAL_OAUTH_AUDIENCE_LIST = ('https://<orgname>-<accountname>.snowflakecomputing.com')
EXTERNAL_OAUTH_JWS_KEYS_URL = 'https://oidc.eks.us-east-1.amazonaws.com/id/EXAMPLE1234567890/.well-known/openid-configuration';
GKE (Workload Identity)
gcloud container clusters describe <cluster-name> --zone <zone> \
--format="value(selfLink)"
CREATE SECURITY INTEGRATION IF NOT EXISTS SNOWPLANE_GKE_WIF
TYPE = EXTERNAL_OAUTH
ENABLED = TRUE
EXTERNAL_OAUTH_TYPE = CUSTOM
EXTERNAL_OAUTH_ISSUER = 'https://container.googleapis.com/v1/projects/<project>/locations/<zone>/clusters/<cluster>'
EXTERNAL_OAUTH_TOKEN_USER_MAPPING_CLAIM = 'sub'
EXTERNAL_OAUTH_SNOWFLAKE_USER_MAPPING_ATTRIBUTE = 'LOGIN_NAME'
EXTERNAL_OAUTH_ANY_ROLE_MODE = 'ENABLE'
EXTERNAL_OAUTH_AUDIENCE_LIST = ('https://<orgname>-<accountname>.snowflakecomputing.com')
EXTERNAL_OAUTH_JWS_KEYS_URL = 'https://container.googleapis.com/v1/projects/<project>/locations/<zone>/clusters/<cluster>/jwks';
AKS (Workload Identity)
az aks show -g <resource-group> -n <cluster-name> \
--query "oidcIssuerProfile.issuerUrl" -o tsv
CREATE SECURITY INTEGRATION IF NOT EXISTS SNOWPLANE_AKS_WIF
TYPE = EXTERNAL_OAUTH
ENABLED = TRUE
EXTERNAL_OAUTH_TYPE = CUSTOM
EXTERNAL_OAUTH_ISSUER = 'https://oidc.prod-aks.azure.com/<tenant-id>/'
EXTERNAL_OAUTH_TOKEN_USER_MAPPING_CLAIM = 'sub'
EXTERNAL_OAUTH_SNOWFLAKE_USER_MAPPING_ATTRIBUTE = 'LOGIN_NAME'
EXTERNAL_OAUTH_ANY_ROLE_MODE = 'ENABLE'
EXTERNAL_OAUTH_AUDIENCE_LIST = ('https://<orgname>-<accountname>.snowflakecomputing.com')
EXTERNAL_OAUTH_JWS_KEYS_URL = 'https://oidc.prod-aks.azure.com/<tenant-id>/discovery/v2.0/keys';
Map ServiceAccount to Snowflake User
The projected token’s sub claim is typically system:serviceaccount:<namespace>:<serviceaccount>.
ALTER USER SVC_SNOWPLANE SET LOGIN_NAME = 'system:serviceaccount:snowplane-system:snowplane';
Deploy Snowplane with WIF
Helm Values
# values-wif.yaml
serviceAccount:
create: true
annotations:
# EKS: eks.amazonaws.com/role-arn: arn:aws:iam::<account-id>:role/<role-name>
# GKE: iam.gke.io/gcp-service-account: <gsa>@<project>.iam.gserviceaccount.com
# AKS: azure.workload.identity/client-id: <client-id>
workloadIdentity:
enabled: true
audience: "https://<orgname>-<accountname>.snowflakecomputing.com"
expirationSeconds: 3600
mountPath: /var/run/secrets/snowflake
tokenFileName: token
helm upgrade --install snowplane charts/snowplane \
-n snowplane-system --create-namespace \
-f values-wif.yaml
ProviderConfig
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: ProviderConfig
metadata:
name: production
spec:
account: "orgname-accountname"
user: "SVC_SNOWPLANE"
role: "SNOWPLANE_ROLE"
warehouse: "COMPUTE_WH"
authenticationType: WorkloadIdentity
credentials: {}
workloadIdentity:
audience: "https://orgname-accountname.snowflakecomputing.com"
Troubleshooting
| Problem | Fix |
|---|---|
| Token file not found | Ensure workloadIdentity.enabled: true in Helm values |
| Token audience mismatch | Match audience in Helm values, ProviderConfig, and Snowflake integration |
| User mapping failure | Check sub claim matches Snowflake user’s LOGIN_NAME. Decode: cat /var/run/secrets/snowflake/token \| cut -d. -f2 \| base64 -d \| jq . |
| ProviderConfig NotReady | kubectl describe providerconfig production |
Security Considerations
- Token lifetime — Projected tokens are short-lived (default: 1 hour), kubelet refreshes automatically
- No static credentials — No passwords or private keys in Kubernetes Secrets
- Automatic rotation — Token refresh handled by kubelet, driver re-reads on each connection
- Multi-cloud providers — Native support for OIDC, AWS, GCP, Azure via the
providerfield - Least privilege — Combine WIF with a restricted
SNOWPLANE_ROLE - Namespace isolation — Use
spec.allowedNamespaceson ProviderConfig