Binary that just returns a Service Accounts access_token for use with GCP Credential Libraries where the key is accessed using direct calls to a Trusted Platform Module (TPM).
While not running on a GCP platform like GCE, Cloud Run, GCF or GKE, Service Account authentication usually (with exception of workload federation or impersonation) requires direct access to its RSA Private key..
What this repo offers two ways to generate the access_token while the RSA service account or mtls key is embedded on a TPM and then use it to issue GCP access_tokens
The protocol itself for service account authentication is described in AIP-4111: Self-signed JWT with Scopes. Basically service account authentication involves locally signing a JWT and using that directly as an access_token.
There are several ways to embed a GCP Service Account into a TPM.
- download a Google ServiceAccount's json file and embed the private part to the TPM or
- Generate a Key on the TPM and then import the public part to GCP
- remote seal the service accounts RSA Private key remotely, encrypt it with the remote TPM's Endorsement Key and load it
These are described here: oauth2 TPM TokenSource
This specific demo here will use option (1) which is the easiest but ultimately, you just need a reference handle to the TPM which all three options can provide.
NOTE While this repo is a CLI, you can acquire an embedded service account's token for use with a library as an oauth2 TPM TokenSource
This repo also allow you to embed an mTLS certificate into a TPM for use with GCP Workload Federation with x509 certificates.
for mTLS certificates, the you can
- create a private key on the TPM and issue a CSR to sign by a CA
- create a private key, sign it by a CA and then embed the private key on the TPM
to use mTLS, you need to please see GCP Workload Identity Federation using x509 certificates
- Trusted Platform Module (TPM) recipes with tpm2_tools and go-tpm
- GCP golang TPMTokenSource
- Python: Cloud Auth Library using Trusted Platform Module (TPM)
- TPM: GCP Workload Identity Federation using x509 certificates
- mTLS with TPM bound private key
- TPM Remote Attestation protocol using go-tpm and gRPC
- golang-jwt for Trusted Platform Module (TPM)
- tpmcopy: Transfer RSA|ECC|AES|HMAC key to a remote Trusted Platform Module (TPM)
as an side, you can also embed AWS credentials to hardware:
- AWS SDK Credentials and Request Signing using Trusted Platform Modules (TPM), HSM, PKCS-11 and Vault
NOTE: this repo is not supported by google
You can set the following options on usage:
| Option | Description |
|---|---|
--tpm-path |
path to the TPM device (default: /dev/tpm0) |
--persistentHandle |
Persistent Handle for the HMAC key (default: 0x81010002) |
--keyfilepath |
Path to the TPM HMAC credential file (default: ``) |
--parentPass |
Passphrase for the owner handle (will use TPM_PARENT_AUTH env var) |
--keyPass |
Passphrase for the key handle (will use TPM_KEY_AUTH env var) |
--pcrs |
"PCR Bound slot:value (increasing order, comma separated)" |
--rawOutput |
Return just the token, nothing else |
--useEKParent |
Use endorsement keys (rsa_ek or ecc_ek as parent (default: ``) |
--tpm-session-encrypt-with-name |
hex encoded TPM object 'name' to use with an encrypted session |
| Option | Description |
|---|---|
--useOauthToken |
issue oauth2 token (default:false) |
--svcAccountEmail |
(required) Service Account Email |
--identityToken |
Generate Google OIDC token |
--audience |
Audience for the id_token |
--scopes |
"comma separated scopes (default https://www.googleapis.com/auth/cloud-platform)" |
--expireIn |
"How many seconds the token is valid for" |
| Option | Description |
|---|---|
--useMTLS |
Use mtls workload federation(default: false) |
--projectNumber |
Project Number for mTLS (default: ) |
--poolID |
workload identity pool id for mTLS (default: ) |
--providerID |
workload identity pool id for mTLS (default: ) |
--pubCert |
workload identity public certificate for mTLS (default: ) |
since we're importing an external RSA key into a TPM, we'll need a service account json file.
On your laptop, run
export PROJECT_ID=`gcloud config get-value core/project`
gcloud iam service-accounts create tpm-sa --display-name "TPM Service Account"
export SERVICE_ACCOUNT_EMAIL=tpm-sa@$PROJECT_ID.iam.gserviceaccount.com
gcloud iam service-accounts keys create tpm-svc-account.json --iam-account=$SERVICE_ACCOUNT_EMAILcopy the tpm-svc-account.json to the system hosting the TPM.
On the TPM device, prepare the key and then use tpm2_tools to create a primary and import the service account into it.
## prepare they key
## extract just the private key from the json keyfile
cat tpm-svc-account.json | jq -r '.private_key' > /tmp/f.json
openssl rsa -in /tmp/f.json -out /tmp/key_rsa.pem
## if you want to test using a software TPM instead:
## rm -rf /tmp/myvtpm && mkdir /tmp/myvtpm
## swtpm_setup --tpmstate /tmp/myvtpm --tpm2 --create-ek-cert
## swtpm socket --tpmstate dir=/tmp/myvtpm --tpm2 --server type=tcp,port=2321 --ctrl type=tcp,port=2322 --flags not-need-init,startup-clear --log level=5
## export TPM2TOOLS_TCTI="swtpm:port=2321"
## create the primary
### the specific primary here happens to be the h2 template described later on but you are free to define any template and policy
printf '\x00\x00' > unique.dat
tpm2_createprimary -C o -G ecc -g sha256 -c primary.ctx -a "fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|restricted|decrypt" -u unique.dat
# import
tpm2_import -C primary.ctx -G rsa2048:rsassa:null -g sha256 -i /tmp/key_rsa.pem -u key.pub -r key.prv
tpm2_flushcontext -t
tpm2_load -C primary.ctx -u key.pub -r key.prv -c key.ctx
tpm2_flushcontext -tDelete the svc account json and the extracted formats; theyr'e no longer needed
You can either evict (save) the key to a persistent_handle or if you have tpm2-tss-engine installed, save the key to a PEM file
Evict
## to load
tpm2_evictcontrol -C o -c key.ctx 0x81010002note, Range for OWNER hierarchy is :81 00 80 00 – 81 00 FF FF from Section 2.3.1 Key Handle Assignments of Registry of Reserved TPM 2.0: Handles and Localities
PEM
tpm2_encodeobject -C primary.ctx -u key.pub -r key.prv -o private.pemThe TPM encrypted service account private key looks like:
-----BEGIN TSS2 PRIVATE KEY-----
MIICNQYGZ4EFCgEDoAMBAf8CBEAAAAEEggEaARgAAQALAAQAQAAAABAAFAALCAAA
AQABAQDqKVruwZ6amTB9OFXwOqNkl7Zaxh0jD1AXbnD9uvnk0z18tGOHxzsP6lsm
LJ8ywnMkomdbDP78dZlHEC3sn/7ustRUTwHb9UV/gc875gMJ0qsrbRajsH1J7tQB
S4ezEf8MKoBi9ogUx7g21z7cytiK46nr08J3yyZHvXVuCklncXBD8TM9ZlHVdDeM
ICMOzXg6d0fL0UvujGPSIEYnqbmY4DlpI0RudMAsOtActbo7Dq7xuiSBcW9slxxS
e18mO6/3IJANKVlHkynpjTEkzzchKR5brCoteukcLhSPTlSNmkvzBOXbDTyRhrrs
8HEyufQGc4MGLjStpTFNsOHy1xqnBIIBAAD+ACCa/b/fswSisyrTKwiDXPQh34iP
zBY1tFOd6vnC0/ve1wAQPG4ZuRWMOklDUbmDx4Lw8WG9dGQFNOFaQKCQhLUphFTs
bT12jDRmW87F7IPlJYbziyj6+4YVS0Ni1EoDJPlXpoveSE9AWONnqkqzTn9mlURI
ZGiTieMzKxfKxy7g/iwW8p0gkDuq/wR1zL6NScfD6HsEzGdpLHb3gVe8Y2VAwjb2
RLNfC7oAZv2rmq5OhKYTzcpCvO7rfL7X6lez4+ql9a04Jz3ui+QBGPSKO7KN0nir
qbW/+koHwS95LxjewjZ9aThg7tkaqAjlUqAZlayvvFDG1kjIhuDmN/0=
-----END TSS2 PRIVATE KEY-----At this point, the embedded RSA key on the TPM is authorized for access GCP.
On the machine with the TPM, specify the PROJECT_ID and the default persistent handle. You should see an access token
You can either build the binary or acquire it from the Releases page
go build -o gcp-adc-tpm cmd/main.go
# with persistentHandle
gcp-adc-tpm --persistentHandle=0x81010002 --svcAccountEmail="tpm-sa@$PROJECT_ID.iam.gserviceaccount.com"
# with keyfile
gcp-adc-tpm --keyfilepath=/path/to/private.pem --svcAccountEmail="tpm-sa@$PROJECT_ID.iam.gserviceaccount.com"
{
"access_token": "ya29.c.c0AY_VpZjqp...redacted",
"expires_in": 3599,
"token_type": "Bearer"
}The json provided there can populate a generic oauth2.Token which you can use in any GCP Library.
For example,
sts := oauth2.StaticTokenSource(tok)
storageClient, err := storage.NewClient(ctx, option.WithTokenSource(sts))You can also invoke this binary as a full TokenSource as well: see
golang: https://github.com/salrashid123/gcp_process_credentials_gopython: https://github.com/salrashid123/gcp_process_credentials_pyjava: https://github.com/salrashid123/gcp_process_credentials_javanode: https://github.com/salrashid123/gcp_process_credentials_node
for gcloud cli, you could apply the token directly using --access-token-file:
gcp-adc-tpm --persistentHandle=0x81010002 --svcAccountEmail="tpm-sa@$PROJECT_ID.iam.gserviceaccount.com" | jq -r '.access_token' > token.txt
gcloud storage ls --access-token-file=token.txtThe default token this utility returns is a JWT AccessToken with Scopes described in AIP4111: Self-signed JWT. This is a custom flow for Google Cloud APIs and is not an Oauth2 Token.
If you want to acquire an actual oauth2 token as described here request, then just set --useOauthToken flag
gcp-adc-tpm --keyfilepath=/path/to/private.pem \
--svcAccountEmail="tpm-sa@$PROJECT_ID.iam.gserviceaccount.com -useOauthToken This uitlity can also genrate GCP OIDC TOken using the TPM based key.
gcp-adc-tpm --keyfilepath=/path/to/private.pem \
--audience=foo --identityToken --serviceAccountEmail=tpm-sa@$PROJECT_ID.iam.gserviceaccount.com \if you want to create a service account key which has a PCR policy attached to it:
tpm2_startauthsession -S session.dat
tpm2_policypcr -S session.dat -l sha256:23 -L policy.dat
tpm2_flushcontext session.dat
printf '\x00\x00' > unique.dat
tpm2_createprimary -C o -G ecc -g sha256 -c primary.ctx -a "fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|restricted|decrypt" -u unique.dat
tpm2_import -C primary.ctx -G rsa2048:rsassa:null -g sha256 -i /tmp/key_rsa.pem -u key.pub -r key.prv -L policy.dat
tpm2_flushcontext -t
tpm2_load -C primary.ctx -u key.pub -r key.prv -c key.ctx
tpm2_evictcontrol -C o -c key.ctx 0x81010002
tpm2_encodeobject -C primary.ctx -u key.pub -r key.prv -o private.pem
tpm2_flushcontext -tThen run it and specify the pcr back to construct the policy against:
gcp-adc-tpm --persistentHandle=0x81010002 \
--svcAccountEmail="tpm-sa@$PROJECT_ID.iam.gserviceaccount.com" \
--pcrs=23:0000000000000000000000000000000000000000000000000000000000000000to test the negative, you can alter the PCR value. For me it was
$ tpm2_pcrread sha256:23
sha256:
23: 0xC78009FDF07FC56A11F122370658A353AAA542ED63E44C4BC15FF4CD105AB33C
$ tpm2_pcrextend 23:sha256=0xC78009FDF07FC56A11F122370658A353AAA542ED63E44C4BC15FF4CD105AB33CSo now try to get an access token, you'll see an error:
The following means the claimed pcr value in --pcrs= switch do not match what is current set on the PCR=23
gcp-adc-tpm --persistentHandle=0x81010002 \
--svcAccountEmail="tpm-sa@$PROJECT_ID.iam.gserviceaccount.com" \
--pcrs=23:0000000000000000000000000000000000000000000000000000000000000000
gcp-tpm-process-credential: Error getting credentials gcp-adc-tpm: Error signing tpmjwt: error getting session TPM_RC_VALUE (parameter 1): value is out of range or is not correct for the contextexit status 1The following means the claimed pcr value in --pcrs= does match what is current set on the PCR=23 but the key itself used a different value in binding policy
gcp-adc-tpm --persistentHandle=0x81010002 \
--svcAccountEmail="tpm-sa@$PROJECT_ID.iam.gserviceaccount.com" \
--pcrs=23:C78009FDF07FC56A11F122370658A353AAA542ED63E44C4BC15FF4CD105AB33C
gcp-tpm-process-credential: Error getting credentials gcp-adc-tpm: Error signing tpmjwt: can't Sign: TPM_RC_POLICY_FAIL (session 1): a policy check failedexit status 1
if you want to create a service account key which has a Password policy attached to it:
printf '\x00\x00' > unique.dat
tpm2_createprimary -C o -G ecc -g sha256 -c primary.ctx -a "fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|restricted|decrypt" -u unique.dat
tpm2_import -C primary.ctx -G rsa2048:rsassa:null -p testpwd -g sha256 -i /tmp/key_rsa.pem -u key.pub -r key.prv
tpm2_flushcontext -t
tpm2_load -C primary.ctx -u key.pub -r key.prv -c key.ctx
tpm2_evictcontrol -C o -c key.ctx 0x81010002
tpm2_encodeobject -C primary.ctx -u key.pub -r key.prv -o private.pem
tpm2_flushcontext -tNow run without the password, you'll see an error
gcp-adc-tpm --persistentHandle=0x81010002 \
--svcAccountEmail="tpm-sa@$PROJECT_ID.iam.gserviceaccount.com"
Error signing tpmjwt: can't Sign: TPM_RC_AUTH_FAIL (session 1): the authorization HMAC check failed and DA counter incrementedexit status 1 Now run and specify the password
gcp-adc-tpm --persistentHandle=0x81010002 \
--svcAccountEmail="tpm-sa@$PROJECT_ID.iam.gserviceaccount.com" --keyPass=testpwdIf you want to use gcloud to authenticate using this provider:
with env-var:
$ export CLOUDSDK_AUTH_ACCESS_TOKEN=`/path/to/gcp-adc-tpm --keyfilepath path/to/tpm_private_key.pem --svcAccountEmail="tpm-sa@$PROJECT_ID.iam.gserviceaccount.com"" | jq -r '.access_token'`
$ gcloud auth print-access-token
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY2....or with file: --access-token-file
echo /path/to/gcp-adc-tpm --keyfilepath path/to/tpm_private_key.pem --svcAccountEmail="tpm-sa@$PROJECT_ID.iam.gserviceaccount.com"" | jq -r '.access_token' > /tmp/token.txt
gcloud auth print-access-token --access-token-file=/tmp/token.txtNote that the token is static and non-refreshable through gcloud. Each token generated is new and has a TTL of 1hour.
Also note that issuing identity token is not supported
If you used option 3 above to transfer the service account key from your laptop (local) to TPM-A (tpm-a being the system where you will run the metadata server):
you can use tpm2_duplicate or the utility here tpmcopy: Transfer RSA|ECC|AES|HMAC key to a remote Trusted Platform Module (TPM) tool. Note that the 'parent' key is set to Endorsement RSA which needs to get initialized on tpm-a first. Furthermore, by default key is bound by pcr_duplicateselect policy which must get fulfilled.
The following examples shows how to use this cli if you transferred the key using pcr or password policy as well as if you saved the transferred key as PEM or persistent handle
start two tpms to simulate two different system
## TPM A
rm -rf /tmp/myvtpm && mkdir /tmp/myvtpm
/usr/share/swtpm/swtpm-create-user-config-files
swtpm_setup --tpmstate /tmp/myvtpm --tpm2 --create-ek-cert
swtpm socket --tpmstate dir=/tmp/myvtpm --tpm2 --server type=tcp,port=2321 --ctrl type=tcp,port=2322 --flags not-need-init,startup-clear --log level=2
## in new window
export TPMA="127.0.0.1:2321"
export TPM2TOOLS_TCTI="swtpm:port=2321"
tpm2_flushcontext -t && tpm2_flushcontext -s && tpm2_flushcontext -lWith service account key saved as PEM key file
### on TPM A
tpmcopy --mode publickey --parentKeyType=rsa_ek -tpmPublicKeyFile=/tmp/public.pem --tpm-path=$TPMA
### copy public.pem to Local
### local
tpmcopy --mode duplicate --keyType=rsa --secret=/tmp/key_rsa.pem --rsaScheme=rsassa \
--hashScheme=sha256 --password=bar -tpmPublicKeyFile=/tmp/public.pem -out=/tmp/out.json
### copy /tmp/out.json to TPM-A
### TPM-A
tpmcopy --mode import --parentKeyType=rsa_ek --in=/tmp/out.json --out=/tmp/tpmkey.pem --tpm-path=$TPMA
### run
gcp-adc-tpm --keyfilepath=/tmp/tpmkey.pem \
--svcAccountEmail="tpm-sa@$PROJECT_ID.iam.gserviceaccount.com" \
--useEKParent=rsa_ek --keyPass=bar --tpm-path=127.0.0.1:2321With service account key saved as a PersistentHandle
tpmcopy --mode publickey --parentKeyType=rsa_ek -tpmPublicKeyFile=/tmp/public.pem --tpm-path=$TPMA
### copy public.pem to Local
### local
tpmcopy --mode duplicate --keyType=rsa --secret=/tmp/key_rsa.pem --rsaScheme=rsassa \
--hashScheme=sha256 --password=bar -tpmPublicKeyFile=/tmp/public.pem -out=/tmp/out.json
### copy /tmp/out.json to TPM-A
### TPM-A
tpmcopy --mode import --parentKeyType=rsa_ek \
--in=/tmp/out.json --out=/tmp/tpmkey.pem \
--pubout=/tmp/pub.dat --privout=/tmp/priv.dat \
--parentpersistentHandle=0x81008000 --tpm-path=$TPMA
tpmcopy --mode evict \
--persistentHandle=0x81008001 \
--in=/tmp/tpmkey.pem --tpm-path=$TPMA
# tpm2_createek -c ek.ctx -G rsa -u ek.pub
# tpm2_flushcontext -t && tpm2_flushcontext -s && tpm2_flushcontext -l
# tpm2 startauthsession --session session.ctx --policy-session
# tpm2 policysecret --session session.ctx --object-context endorsement
# tpm2_load -C ek.ctx -c key.ctx -u pub.dat -r priv.dat --auth session:session.ctx
# tpm2_evictcontrol -c key.ctx 0x81008001
# tpm2_flushcontext -t && tpm2_flushcontext -s && tpm2_flushcontext -l
### run
gcp-adc-tpm --keyfilepath=/tmp/tpmkey.pem \
--svcAccountEmail="tpm-sa@$PROJECT_ID.iam.gserviceaccount.com" \
--useEKParent=rsa_ek --keyPass=bar --persistentHandle 0x81008001 --tpm-path=$TPMAEnsure TPM-A as a PCR you want to bind to
$ tpm2_pcrread sha256:23
sha256:
23: 0x0000000000000000000000000000000000000000000000000000000000000000
$ tpm2_pcrextend 23:sha256=0x0000000000000000000000000000000000000000000000000000000000000000
$ tpm2_pcrread sha256:23
sha256:
23: 0xF5A5FD42D16A20302798EF6ED309979B43003D2320D9F0E8EA9831A92759FB4BWith service account key saved as PEM key file
### TPM-A
tpmcopy --mode publickey --parentKeyType=rsa_ek -tpmPublicKeyFile=/tmp/public.pem --tpm-path=$TPMA
### copy public.pem to local
### local
tpmcopy --mode duplicate --keyType=rsa --secret=/tmp/key_rsa.pem --rsaScheme=rsassa \
--hashScheme=sha256 --pcrValues=23:F5A5FD42D16A20302798EF6ED309979B43003D2320D9F0E8EA9831A92759FB4B \
-tpmPublicKeyFile=/tmp/public.pem -out=/tmp/out.json
### copy out.json to TPM-A
### TPM-A
tpmcopy --mode import --parentKeyType=rsa_ek --in=/tmp/out.json --out=/tmp/tpmkey.pem --tpm-path=$TPMA
### run
gcp-adc-tpm --keyfilepath=/tmp/tpmkey.pem \
--svcAccountEmail="tpm-sa@$PROJECT_ID.iam.gserviceaccount.com" \
--useEKParent=rsa_ek --pcrs=23:F5A5FD42D16A20302798EF6ED309979B43003D2320D9F0E8EA9831A92759FB4B --tpm-path=127.0.0.1:2321With service account key saved as a PersistentHandle
### TPM-A
tpmcopy --mode publickey --parentKeyType=rsa_ek -tpmPublicKeyFile=/tmp/public.pem --tpm-path=$TPMA
### copy public.pem to TPM-A
### TPM-A
tpmcopy --mode duplicate --keyType=rsa --secret=/tmp/key_rsa.pem --rsaScheme=rsassa \
--hashScheme=sha256 --pcrValues=23:F5A5FD42D16A20302798EF6ED309979B43003D2320D9F0E8EA9831A92759FB4B \
-tpmPublicKeyFile=/tmp/public.pem -out=/tmp/out.json
### copy out.json to TPM-B
### TPM-B
tpmcopy --mode import --parentKeyType=rsa_ek --in=/tmp/out.json \
--parent=0x81008000 --out=/tmp/tpmkey.pem --tpm-path=$TPMA
tpmcopy --mode evict \
--persistentHandle=0x81008001 \
--in=/tmp/tpmkey.pem --tpm-path=$TPMA
### or using tpm2_tools:
# tpm2_createek -c ek.ctx -G rsa -u ek.pub
# tpm2_flushcontext -t && tpm2_flushcontext -s && tpm2_flushcontext -l
# tpm2 startauthsession --session session.ctx --policy-session
# tpm2 policysecret --session session.ctx --object-context endorsement
# tpm2_load -C ek.ctx -c key.ctx -u pub.dat -r priv.dat --auth session:session.ctx
# tpm2_evictcontrol -c key.ctx 0x81008001
# tpm2_flushcontext -t && tpm2_flushcontext -s && tpm2_flushcontext -l
### run
gcp-adc-tpm \
--svcAccountEmail="tpm-sa@$PROJECT_ID.iam.gserviceaccount.com" \
--useEKParent=rsa_ek --pcrs=23:F5A5FD42D16A20302798EF6ED309979B43003D2320D9F0E8EA9831A92759FB4B \
--persistentHandle 0x81008001 --tpm-path=$TPMAIf you don't want to use a policy, then set --skipPolicy flag while using tpmcopy
tpmcopy --mode publickey --parentKeyType=rsa_ek -tpmPublicKeyFile=/tmp/public.pem --tpm-path=$TPMA
### duplicate and use --skipPolicy
tpmcopy --mode duplicate --keyType=rsa \
--secret=/tmp/key_rsa.pem --rsaScheme=rsassa \
--hashScheme=sha256 --skipPolicy -tpmPublicKeyFile=/tmp/public.pem -out=/tmp/out.json
tpmcopy --mode import --parentKeyType=rsa_ek --in=/tmp/out.json --out=/tmp/tpmkey.pem --tpm-path=$TPMA
## use the key
gcp-adc-tpm --keyfilepath=/tmp/tpmkey.pem \
--svcAccountEmail="tpm-sa@$PROJECT_ID.iam.gserviceaccount.com" \
--useEKParent=rsa_ek --tpm-path=127.0.0.1:2321For password based non-session tranfers with H2 key:
## get h2
tpmcopy --mode publickey --parentKeyType=h2 -tpmPublicKeyFile=/tmp/public.pem --tpm-path=$TPMA
tpmcopy --mode duplicate --keyType=rsa --password=bar \
--secret=/tmp/key_rsa.pem --rsaScheme=rsassa \
--hashScheme=sha256 --skipPolicy -tpmPublicKeyFile=/tmp/public.pem -out=/tmp/out.json
tpmcopy --mode import --parentKeyType=h2 \
--in=/tmp/out.json --out=/tmp/tpmkey.pem --pubout=/tmp/pub.dat --privout=/tmp/priv.dat \
--tpm-path=$TPMB
## use the key (specify --keyPass but don't use --useEKParent)
gcp-adc-tpm --keyfilepath=/tmp/tpmkey.pem \
--svcAccountEmail="tpm-sa@$PROJECT_ID.iam.gserviceaccount.com" \
--tpm-path=127.0.0.1:2321 --keyPass=barTo use mTLS, you need to have a private key on the TPM and then issue a trusted certificate to use with that key.
You can set this up by following both
you can generate a key on the tpm and issue a CSR following the partial instructions here
So if you have setup workload mtls where your CA issued certificates represent service identities, the following will generate a key on the TPM and then issue a CSR. Your CA will use this CSR to issue a certifiate.
export PROJECT_ID=`gcloud config get-value core/project`
export PROJECT_NUMBER=`gcloud projects describe $PROJECT_ID --format='value(projectNumber)'`
export POOL_ID="cert-pool-1" ## pick a new unique name
export WORKLOAD_CN="workload-adc-1" ## pick a new unique name
export PROVIDER_ID="cert-provider-adc"
export TPM2TOOLS_TCTI="swtpm:port=2321"
export TPM2OPENSSL_TCTI="swtpm:port=2321"
printf '\x00\x00' > unique.dat
tpm2_createprimary -C o -G ecc -g sha256 \
-c primary.ctx -a "fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|restricted|decrypt" -u unique.dat
tpm2_create -G rsa2048:rsapss:null -g sha256 -u key.pub -r key.priv -C primary.ctx
tpm2_flushcontext -t && tpm2_flushcontext -s && tpm2_flushcontext -l
tpm2_load -C primary.ctx -u key.pub -r key.priv -c key.ctx
tpm2_evictcontrol -C o -c key.ctx 0x81010002
### extract the publicKey PEM
tpm2_readpublic -c key.ctx -f PEM -o workload_1_public.pem
tpm2_flushcontext -t && tpm2_flushcontext -s && tpm2_flushcontext -l
## convert the entire TPM public/private key to PEM
## you may need to add a -p if your tpm2 tools is not recent (see https://github.com/tpm2-software/tpm2-tools/issues/3458)
tpm2_encodeobject -C primary.ctx -u key.pub -r key.priv -o workload_1_key.pem
tpm2_flushcontext -t && tpm2_flushcontext -s && tpm2_flushcontext -l
### issue the CSR
openssl req -provider tpm2 -provider default -new -key workload_1_key.pem -out workload_1_csr.pem \
-subj "/C=US/O=Google/OU=Enterprise/CN=$WORKLOAD_CN"
### now get the CSR signed by a CA which you've configured workload identify federation.
### once you have the Certificate,
export TPMA="127.0.0.1:2321"
go run cmd/main.go -useMTLS \
--keyfilepath=workload_1_key.key \
--projectNumber=$PROJECT_NUMBER \
--poolID=$POOL_ID --providerID=$PROVIDER_ID \
--pubCert=workload_1_crt.pem --tpm-path=$TPMAAlternatively, if you already have a raw (non-tpm) PEM key (workload-adc-1-raw.pem) and Certificate, you can import it into the TPM.
printf '\x00\x00' > unique.dat
tpm2_createprimary -C o -G ecc -g sha256 \
-c primary.ctx -a "fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|restricted|decrypt" -u unique.dat
tpm2_create -G rsa2048:rsapss:null -g sha256 -u key.pub -r key.priv -C primary.ctx
tpm2_flushcontext -t && tpm2_flushcontext -s && tpm2_flushcontext -l
# now import the raw PM key
tpm2_import -C primary.ctx -G rsa -i workload-adc-1-raw.pem -u key.pub -r key.prv
tpm2_readpublic -c key.ctx -f PEM -o workload_1_public.pem
tpm2_flushcontext -t && tpm2_flushcontext -s && tpm2_flushcontext -l
tpm2_encodeobject -C primary.ctx -u key.pub -r key.priv -o workload_1_key.keythen to get an access_token:
export TPMA="127.0.0.1:2321"
gcp-adc-tpm -useMTLS \
--keyfilepath=/tmp/workload_1_key.pem \
--projectNumber=$PROJECT_NUMBER \
--poolID=$POOL_ID --providerID=$PROVIDER_ID \
--pubCert=/tmp/workload-adc-1.crt --tpm-path=$TPMAIf you specify --svcAccountEmail= with mTLS, the token returned will be for the target service account and not the workload identity principal://
If you want an id_token, you need to enable impersonation. See Authenticating using Workload Identity Federation to Cloud Run, Cloud Functions. Note that the id_token and default access_token when using workload federation represent different principals.
To get an Identity Token for mTLS, you need to associate the principal:// for the service account to an actual service account
gcloud iam service-accounts \
add-iam-policy-binding "tpm-sa@$PROJECT_ID.iam.gserviceaccount.com" \
--member=serviceAccount:"principal://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_ID/subject/$WORKLOAD_CN" \
--role=roles/iam.serviceAccountTokenCreatorthen
gcp-adc-tpm -useMTLS --audience=foo --identityToken --svcAccountEmail="tpm-sa@$PROJECT_ID.iam.gserviceaccount.com" \
--keyfilepath=/tmp/workload_1_key.pem \
--projectNumber=$PROJECT_NUMBER \
--poolID=$POOL_ID --providerID=$PROVIDER_ID \
--pubCert=/tmp/workload-adc-1.crt --tpm-path=$TPMAIf you want to enable TPM Encrypted sessions, you should provide the "name" of a trusted key on the TPM for each call.
A trusted key can be the EK Key. You can get the name using tpm2_tools:
tpm2_createek -c primary.ctx -G rsa -u ek.pub -Q
tpm2_readpublic -c primary.ctx -o ek.pem -n name.bin -f pem -Q
xxd -p -c 100 name.bin
000bb50d34f6377bb3c2f41a1b4b6094ed6efcd7032d28054566db0766879dad1ee0Then use the hex value returned in the --tpm-session-encrypt-with-name= argument.
For example:
--tpm-session-encrypt-with-name=000bb50d34f6377bb3c2f41a1b4b6094ed6efcd7032d28054566db0766879dad1ee0You can also derive the "name" from a public key of a known template see go-tpm.tpm2_get_name
Unit test just verifies that a token is returned. TODO is to validate the token against a gcp api (the oauth2 tokeninfo endopoint wont work because the access token is a self-signed JWT)
Using swtpm. The following test both oauth2 and mtls tokens. You must setup mtls provider and load a cert prior to use. You can skip the mtls tests with a flag
rm -rf /tmp/myvtpm && mkdir /tmp/myvtpm
swtpm_setup --tpmstate /tmp/myvtpm --tpm2 --create-ek-cert
swtpm socket --tpmstate dir=/tmp/myvtpm --tpm2 --server type=tcp,port=2321 --ctrl type=tcp,port=2322 --flags not-need-init,startup-clear
# then specify "127.0.0.1:2321" as the TPM device path in the examples
# and for tpm2_tools, export the following var
export TPM2TOOLS_TCTI="swtpm:port=2321"
export CICD_SA_EMAIL="tpm-sa@$PROJECT_ID.iam.gserviceaccount.com"
export CICD_SA_PEM=`cat /tmp/key_rsa.pem`
# export CICD_POOL_ID=cert-pool-1
# export CICD_PROJECT_NUMBER=1150810122222
# export CICD_PROVIDER_ID=cert-provider-adc
go test -v -count=1 -run '^(TestPersistentHandleCredentials|TestKeyFileCredentials|TestOauth2Token|TestIdToken)'
# go test -vThe tests cases creates peresistent handles. to clear them
tpm2_getcap handles-persistent
# then purge each one sequentially
tpm2_evictcontrol -c 0x81008001The primary we used happens to be the the specified format described in ASN.1 Specification for TPM 2.0 Key Files where the template h-2 is described in pg 43 TCG EK Credential Profile
This specific format allows us to easily use openssl and export the key as PEM. For reference, see tpm2 primarykey for (eg TCG EK Credential Profile H-2 profile
Finally, you may want to restrict access to the TPM device by applying tpm-udev.rules