Using Google Cloud WorkforcePools with SAML

A sample demo of using GCP WorkForce Identity Federation using a local SAML IDP server.

Workforce Identity Federation allows you to manage GCP Resource through gcloud cli or Google Cloud Console using your own SSO system and Identity Provider. While you can import, map and and synchronize your users in your own Active Directory, SAML or OIDC based identity provider into Google Cloud Identity, you still need to synchronize users, groups and what not.

With WorkForce Identity, you still retain your users in your IDP but instead of synchronizing users, you will map and federate login and access to GCP resources. In this mode, you still use your own SSO system but access to the resource is using a federated identity binding.

Note, this is about Work_Force_ Federation (users). Work_Load_ federation is more services accessing GCP Resources ( WorkForce operates at the organization level while WorLoad is at the project level.

The sample SSO Servers here is NOT supported by google! Just in non-prod and even then just to test; remember to remove the configuration.

What this article shows is a trivial WorkForce Federation configuration using a standalone SAML SSO IDP server i wrote maybe 10years ago.

In this flow, we will configure your cloud organization to accept your own IDP that is running locally and access GCP through gcloud and Cloud Console.

For example, you can map your user to a principal:// (user) or use group claims to map to a group principalSet://

so a mapped user

[email protected] -> principal://$POOL_ID/subject/[email protected]

can then be bound to a GCP resource’s IAM:

gcloud projects add-iam-policy-binding  $PROJECT_ID   \
    --member='principal://$POOL_ID/subject/[email protected]' \

You’ll need access to a cloud org to create the configuration…

NOTE I do NOT really expect you to configure this end-to-end using a demo SSO provider here with the built-in certificates. While you can follow this end-to-end, atleast generate your own certificates (eg, server.crt, server.key and then use that public cert in iap_metadata.xml file). Once you i would recommend immediately disabling the configuration…or atleast


Workload Identity


To set this up, you need to be a domain admin and pick a project where we will access resources:

gcloud config set account [email protected]
gcloud config set project host_project_id

export GCLOUD_USER=`gcloud config get-value core/account`
export PROJECT_ID=`gcloud config get-value core/project`
export PROJECT_NUMBER=`gcloud projects describe $PROJECT_ID --format='value(projectNumber)'`

$ gcloud organizations list
    DISPLAY_NAME               ID  DIRECTORY_CUSTOMER_ID  673208781234              C023zw3bc

# if your org id is 673208781234
export POOL_ID=wfpool-saml
export PROVIDER_ID=wfprovider-saml
export LOCATION=global
export ORGANIZATION_ID=673208781234

gcloud config set billing/quota_project $BILLING_PROJECT_ID

gcloud organizations add-iam-policy-binding \
    --member "user:$GCLOUD_USER" --role roles/iam.workforcePoolAdmin 

Configure the SAML provider

The SAML IDP server we will use here will run locally with docker:

  • UI SAML SSO Server: this SSO server will use a UI login screen. We will use this for Console access redirect
  • CLI SAML SSO Server: this SSO server is purely command line and ‘Just issues” a SAML Assertion for you automatically. Simulates some other external process “just creating” a saml assertion which will get written to a file. This is used by gcloud. You can ofcourse use the GUI login and then “copy and paste” the encoded assertion to the file

We will be using the same idp_metadata.xml file with the same certificate here for simplicity

So first get both:

git clone
git clone

Edit /etc/hosts

Now create the pools and providers

gcloud beta iam workforce-pools create $POOL_ID \
    --location="global" --organization=$ORGANIZATION_ID  \
    --description="WorkForce Pool SAML" \
    --display-name="WorkForce Pool SAML" --billing-project=$BILLING_PROJECT_ID

gcloud iam workforce-pools providers create-saml  $PROVIDER_ID \
   --location=global  \
   --workforce-pool  $POOL_ID \
   --display-name "idp-us-employees-saml" \
   --description "IdP for Acme US employees SAML" \
    --idp-metadata-path="idp_metadata.xml" \
    --attribute-mapping="google.subject=assertion.subject,google.groups=assertion.attributes['mygroups']"  \
	--project $PROJECT_ID --billing-project=$BILLING_PROJECT_ID

Create bindings for the principal:// to some resources in a givne project

gcloud projects add-iam-policy-binding  $PROJECT_ID   \
    --member='principal://$POOL_ID/subject/[email protected]' \

gcloud projects add-iam-policy-binding  $PROJECT_ID    \
    --member='principal://$POOL_ID/subject/[email protected]' \

gcloud projects add-iam-policy-binding  $PROJECT_ID   \
    --member='principal://$POOL_ID/subject/[email protected]' \

gcloud projects add-iam-policy-binding  $PROJECT_ID  \
     --member='principal://$POOL_ID/subject/[email protected]'  \

Start Console IDP

Now start the IDP docker container

cd googleapps-sso
  docker run -t -p 28080:28080 \
    -v `pwd`:/app/:ro \
    --entrypoint=/app/ \
    salrashid123/appssso \
    --debug  \
    --cert_file=/app/server.crt \


and enter in your provider locations/global/workforcePools/$POOL_ID/providers/$PROVIDER_ID


Once you enter that in, you should get redirected to the SSO login. Enter in [email protected] and you should see a interstitial page here


Click continue…you should be on a modified console where you can view resources on

  • GCS


  • BQ


  • PubSub images/pubsub_audit_log.png

If you enabled audit logs, you’ll see access to the resources as the federated principal://


gcloud CLI

For gcloud CLI, we need someway to pump the SAML Assertion to a file or accessible via website:

In our case, we’ll pump it to a file

cd gcpcompat-saml
export SAML_TOKEN=`docker run -t -v $PWD:/app/:ro     --entrypoint=/app/     salrashid123/appssso     --debug      --cert_file=/app/server.crt     --key_file=/app/server.key [email protected]  --audience=$POOL_ID/providers/$PROVIDER_ID`


echo -n $SAML_TOKEN > /tmp/samlassertion.txt

Now create an ADC and gcloud config:

gcloud iam workforce-pools create-cred-config \
    locations/global/workforcePools/$POOL_ID/providers/$PROVIDER_ID \
    --output-file=sts-creds-saml.json \
    --credential-source-file="/tmp/samlassertion.txt" \
    --credential-source-type=text \
    --workforce-pool-user-project $BILLING_PROJECT_ID \
    --subject-token-type=urn:ietf:params:oauth:token-type:saml2 \

Now login to gcloud CLI

gcloud auth login --cred-file=saml-creds.json

You should see the principal:// for this user and access all the same resources as in the console

$ gcloud pubsub topics list --billing-project=$BILLING_PROJECT_ID

$ gcloud alpha bq  tables list --dataset=test --billing-project=$BILLING_PROJECT_ID


If you want to see the same STS flow using curl:

curl -s \
--data-urlencode "audience=//$LOCATION/workforcePools/$POOL_ID/providers/$PROVIDER_ID" \
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
--data-urlencode "requested_token_type=urn:ietf:params:oauth:token-type:access_token" \
--data-urlencode "scope=" \
--data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:saml2" \
--data-urlencode "subject_token=$SAML_TOKEN"  \
--data-urlencode "options={\"userProject\":\"$BILLING_PROJECT_ID\"}"  | jq -r '.access_token' 

### enter the STS TOKEN returned into an env-var
export STS_TOKEN="the_token"

Now use the STS_TOKEN in GCP API calls

curl  -H "X-Goog-User-Project: $PROJECT_NUMBER"   -H "Authorization: Bearer $STS_TOKEN"$PROJECT_ID/topics

curl  -H "X-Goog-User-Project: $PROJECT_NUMBER" \
  -H "Authorization: Bearer $STS_TOKEN" \$PROJECT_ID/datasets/test/tables 

You can also access resource using Application Default Credentials


# with curl
curl  -H "X-Goog-User-Project: $PROJECT_NUMBER"  \
    -H "Authorization: Bearer `gcloud auth application-default print-access-token`"$PROJECT_ID/topics

curl  -H "X-Goog-User-Project: $PROJECT_NUMBER"  \
   -H "Authorization: Bearer `gcloud auth print-access-token`"$PROJECT_ID/topics

# with cloud-sdk app; edit main.go and enter the projectID
cd client/
go run main.go

Mapping Group Attributes

The default tutorial here maps just a single user over:

[email protected] -> principal://$POOL_ID/subject/[email protected]

The more efficient way is to map a SAML attribute that identifies the groups the user is in and then bind the IAM permission to the group label.

In this you can configure a attribute mapping using a principalSet://

Remember we configured the provider with an attribute mappeing


What that means is that google will use the assertions Attribute value for groups to extract/enumerate the values

			<saml:Attribute Name="mygroups">

Meaning that the you can make a binding like this

gsutil iam ch \
   prinicpalSet://$POOL_ID/group/ssogroup:objectValue \


If you’ve made it this far, disable the config:

gcloud  iam workforce-pools providers update-saml  $PROVIDER_ID \
  --location=$LOCATION  --workforce-pool  $POOL_ID \
   --billing-project=$BILLING_PROJECT_ID --disabled

 gcloud beta iam workforce-pools  update   $POOL_ID \
     --location="global"  --billing-project=$BILLING_PROJECT_ID --disabled 

### if you want to reenable, use curl, the update via gcloud doens't support the fieldmask

curl -X PATCH -H "Authorization: Bearer `gcloud auth print-access-token`" \
  -H "X-Goog-User-Project: $BILLING_PROJECT_ID" -d '{"disabled": false}'  \
   -H "content-type: application/json" \

curl -X PATCH -H "Authorization: Bearer `gcloud auth print-access-token`" \
   -H "X-Goog-User-Project: $BILLING_PROJECT_ID" -d '{"disabled": false}'  \
    -H "content-type: application/json"  \

Thats it! i’m headed to a winery now


Sample SAML Assertion

<?xml version="1.0" ?>
<samlp:Response Destination="$POOL_ID/providers/$PROVIDER_ID" ID="_3489c1e22804c977323419a535f36fa" InResponseTo="_3ed7d72d358088134ec5efc0b7f4aef" IssueInstant="2022-08-26T10:51:16Z" Version="2.0" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
	<ds:Signature xmlns:ds="">
			<ds:CanonicalizationMethod Algorithm=""/>
			<ds:SignatureMethod Algorithm=""/>
			<ds:Reference URI="#_3489c1e22804c977323419a535f36fa">
					<ds:Transform Algorithm=""/>
				<ds:DigestMethod Algorithm=""/>


		<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
	<saml:Assertion ID="_e0fbbfdc800f225dfad2e02007ec1db" IssueInstant="2022-08-26T10:51:16Z" Version="2.0">
			<saml:NameID>[email protected]</saml:NameID>
			<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
				<saml:SubjectConfirmationData InResponseTo="_3ed7d72d358088134ec5efc0b7f4aef" NotOnOrAfter="2022-08-26T11:41:16Z" Recipient="$POOL_ID/providers/$PROVIDER_ID"/>
		<saml:Conditions NotBefore="2022-08-26T10:51:16Z" NotOnOrAfter="2022-08-26T11:41:16Z">
		<saml:AuthnStatement AuthnInstant="2022-08-26T10:51:16Z" SessionIndex="_e0fbbfdc800f225dfad2e02007ec1db">
			<saml:Attribute Name="mygroups">


View Github