This guide describes how to use Workload Identity Federation with Dex.
To authenticate to Google Cloud, you can let the workload exchange its environment-specific credentials for short-lived Google Cloud credentials by using Workload Identity Federation.
So you don’t need to maintain the GCP service account key in your local environment forever.
1. Dex
Dex is an open source identity service that can be authenticated with other apps. If you don’t have an OIDC provider already, you are able to create one by Dex very easily.
There are many ways to startup a Dex server, but installing it into kubernetes cluster is probably the simplest way, eg, k8s manifest.
1.1 Prepare a LDAP server
Dex need a User database as its backend, you can refer to this page to install your own LDAP server.
1.2 Start up Dex server
Just make some changes to the configuration of Dex, to use openldap as a connector.
Assume that you have the Domain dex.example.com
for the Dex server, this domain don’t need to be public accessible if you upload your jwks.json
file to workload identity provider.
kind: ConfigMap
apiVersion: v1
metadata:
name: dex
namespace: dex
data:
config.yaml: |
issuer: https://dex.example.com
storage:
type: kubernetes
config:
inCluster: true
web:
http: 0.0.0.0:8080
connectors:
- type: ldap
id: ldap
name: LDAP
config:
host: 192.168.1.1:389
insecureNoSSL: true
bindDN: cn=admin,dc=example,dc=com
bindPW: huihui258
usernamePrompt: SSO Username
userSearch:
baseDN: ou=devops,dc=example,dc=com
filter: "(objectClass=posixAccount)"
username: uid
idAttr: DN
emailAttr: mail
nameAttr: sn
groupSearch:
baseDN: ou=groups,dc=example,dc=com
filter: "(objectClass=posixGroup)"
userMatchers:
- userAttr: uid
groupAttr: memberUid
nameAttr: cn
oauth2:
skipApprovalScreen: true
passwordConnector: ldap
staticClients:
- id: example-app
redirectURIs:
- 'https://gcp.googleapis.com/projects/226123987652/locations/global/workloadIdentityPools/test'
name: 'Example App'
secret: ZXhhbXBsZS1hcHAtc2VjcmV0
enablePasswordDB: true
staticPasswords:
- email: "[email protected]"
hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
username: "admin"
userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
Test Dex is ready:
curl -s https://dex.example.com/.well-known/openid-configuration
2. Setup workload identity federation on GCP
2.1 Create workload identity
Retrieve jwks file from Dex:
curl -L -o jwks.json https://dex.example.com/keys
Create workload identity with gcloud
gcloud iam workload-identity-pools create test --location="global" \
--description="Access gcp with Dex" --display-name=test
gcloud iam workload-identity-pools providers create-oidc dex --location="global" \
--workload-identity-pool=test --issuer-uri="https://dex.example.com" \
--attribute-mapping="google.subject=assertion.sub,google.groups=assertion.groups" \
--jwk-json-path="cluster-jwks.json"
2.2 Grant permissions on compute instances
First create a GCP service account and grant roles/compute.viewer
to the SA, then you are able to use workload identity pool to impersonate this SA to get access to GCP service, eg compute instances here.
gcloud iam service-accounts create compute-viewer
gcloud projects add-iam-policy-binding projects/<your project name> --role=roles/compute.viewer \
--member="compute-viewer@<your project name>.iam.gserviceaccount.com" --condition=None
gcloud iam service-accounts add-iam-policy-binding \
compute-viewer@<your project name>.iam.gserviceaccount.com \
--member="principalSet://iam.googleapis.com/projects/<Project Number>/locations/global/workloadIdentityPools/test/group/qa" \
--role=roles/iam.workloadIdentityUser
2.3 Get credential configuration of workload identity
To create a credential configuration file with gcloud
gcloud iam workload-identity-pools create-cred-config \
projects/<Project Number>/locations/global/workloadIdentityPools/test/providers/dex \
--credential-source-file=/path/to/id/token \
--credential-source-type=text \
--service-account=compute-viewer@<your project name>.iam.gserviceaccount.com \
--output-file=credential-configuration.json
The credential configuration file lets the client, eg curl
, to determine the following:
- Where to obtain external credentials from
- Which workload identity pool and provider to use
- Which GCP service account to impersonate
3. Access GCP service with Dex ID token
The workload identity pool is ready now, you just need to use the ID token from Dex to authenticate with GCP
3.1 Obtain ID token from Dex
Use your ldap user to get a ID token from the Dex API, and store the token to /path/to/id/token
DEX_HOST="https://dex.example.com"
OIDC_CONFIG=$(curl -s $DEX_HOST/.well-known/openid-configuration)
TOKEN_ENDPOINT=$(echo $OIDC_CONFIG | jq -r .token_endpoint)
# those are in configmap of Dex
CLIENT_ID="example-app"
CLIENT_SECRET="ZXhhbXBsZS1hcHAtc2VjcmV0"
USERNAME="alice" # LDAP user
read -s -p "Enter LDAP Password: " PASSWORD
echo
# Fetch ID Token
ACCESS_TOKEN=$(curl -s -X POST $TOKEN_ENDPOINT \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET" \
-d "username=$USERNAME" \
-d "password=$PASSWORD" \
-d "scope=openid email groups" \
| jq -r .access_token)
echo $ACCESS_TOKEN > /path/to/id/token
USERINFO_ENDPOINT=$(echo $OIDC_CONFIG | jq -r .userinfo_endpoint)
# For test
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" $USERINFO_ENDPOINT
3.2 List compute instances
Use the above ID token to exchange access token of GCP, then use it to list compute instances in your project.
#!/bin/bash
DEX_TOKEN_PATH=/path/to/id/token
WORKLOAD_IDENTITY_CREDENTIALS=credential-configuration.json
project_id=<your project name>
zone=us-central1-a
# Exchanges OIDC jwt token with GCP security Token Service to get federated access token
TOKEN_EXCHANGE_RESPONSE=$(curl -X POST https://sts.googleapis.com/v1/token \
-H 'Content-Type: application/json; charset=utf-8' \
--data-binary @- << EOF
{
"audience": "$(cat $WORKLOAD_IDENTITY_CREDENTIALS | jq -r '.audience')",
"grantType": "urn:ietf:params:oauth:grant-type:token-exchange",
"scope": "openid https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/userinfo.email",
"subjectToken": "$(cat $DEX_TOKEN_PATH)",
"requestedTokenType": "urn:ietf:params:oauth:token-type:access_token",
"subjectTokenType": "urn:ietf:params:oauth:token-type:jwt"
}
EOF
)
echo "$TOKEN_EXCHANGE_RESPONSE" | jq
# Obtain a service account token via the service account impersonation API
IMPERSONATION_URL="$(cat $WORKLOAD_IDENTITY_CREDENTIALS | jq -r '.service_account_impersonation_url')"
ACCESS_TOKEN_RESPONSE=$(curl -X POST "$IMPERSONATION_URL" -H "Authorization: Bearer $(echo "$TOKEN_EXCHANGE_RESPONSE" | jq -r '.access_token')" \
-H "Content-Type: application/json" --data '{"scope": ["https://www.googleapis.com/auth/cloud-platform", "openid", "https://www.googleapis.com/auth/userinfo.email"]}')
echo $ACCESS_TOKEN_RESPONSE | jq
# Use the access token to call the Google API
curl -H "Authorization: Bearer $(echo "$ACCESS_TOKEN_RESPONSE" | jq -r '.accessToken')" https://www.googleapis.com/oauth2/v3/tokeninfo
curl "https://oauth2.googleapis.com/tokeninfo?access_token=$(echo $ACCESS_TOKEN_RESPONSE | jq -r '.accessToken')"
# Use the access token to list compute instances
curl -H "Authorization: Bearer $(echo "$ACCESS_TOKEN_RESPONSE" | jq -r '.accessToken')" "https://compute.googleapis.com/compute/v1/projects/$project_id/zones/$zone/instances"
Ok, That’s all.