Did you know if you’re only given the IAM iam.serviceAccounts.signBlob permission you indirectly effectively also get iam.serviceAccounts.signJwt
?
.AND..
if you have iam.serviceAccounts.signJwt you effectively also get iam.serviceAccounts.getAccessToken
AND iam.serviceAccounts.getOpenIdToken
?
…in other words, signBlob
means you have signJwt
and having signJwt
means you can getAccessToken
and getOpenIdToken
:
maybe you already know this but its important to realize that these are concentric and what that means when you to assign an over-provisioned GCP IAM Role like roles/iam.serviceAccountTokenCreator
when you may not need all its capabilities.
so…how is signBlob
the one ring to rule them all?
Well, a lot of it has to do with the fact that the ability to digitally sign using a service Account’s key is the basis for creating access_tokens
and id_tokens
using the oauth2 2LO flow
so, lets start off at the first level of concentric permissions:
This is easy:
signedJWT
is just a JWT that is signed by a service account…all you have to do is create a JWT and use the signBlob
API and get a signedJWT…its the same JWT you’d get if you used the IAM API for a given key.
Done. Miller time
This is a bit more complicated and requires looking at the protocol used by a Service account to issue access_tokens
and id_tokens
:
if you look at the protocol described there, it all hinges on having service account sign a JWT with its private key and then exchange it with Google to get one of those tokens.
For access_token
, the JWT payload will look like
{
"iss": "svcaccount1@$PROJECT_ID.iam.gserviceaccount.com",
"scope": "https://www.googleapis.com/auth/devstorage.read_only",
"aud": "https://oauth2.googleapis.com/token",
"exp": 1328554385,
"iat": 1328550785
}
For id_tokens
, the JWT payload will look like the following where target_audience
is the final audience, issued value
{
"iss": "svcaccount1@$PROJECT_ID.iam.gserviceaccount.com",
"aud": "https://oauth2.googleapis.com/token",
"exp": 1328554385,
"iat": 1328550785,
"target_audience": "https://foo.bar"
}
Once you have the raw JWT, you will sign it using the signBlob
API….which in the end gives you a signedJWT
.
Once you have the signedJWT
, you can exchange that with the google oauth2 token endpoint for either an access_token
or an id_token
In other words, the only real permission you need is the ability to signBlob
…you can just use that ability to do the other capabilities.
If you want to look at this in another way from an API perspective since these methods map to permissions pretty much 1:1, its like this:
So, if these permissions are concentric, what does that mean?
if all you need to use the iamCredentials API to impersonate service Account to just generate aid_tokens
, do you need to assign it the tokenCreator role?
no. Just assign a custom role with iam.serviceAccounts.getOpenIdToken
if all you need to use the iamCredentials API to impersonate service Account to just generate an access_token
, do you need to assign it the tokenCreator role?
no. Just assign a custom role with iam.serviceAccounts.getAccessToken
The other thing you’d want to understand is what other roles are using signBlob
?
For that, you can use the a public dataset i wrote a bit ago to enumerate the roles that include that: Google Cloud IAM Roles-Permissions Public Dataset
For signBlob
:
$ bq query --nouse_legacy_sql '
SELECT
r1
FROM
iam-log.iam.permissions AS d1, UNNEST(roles) r1
WHERE
d1._PARTITIONTIME = TIMESTAMP("2022-04-01")
AND d1.name = "iam.serviceAccounts.signBlob"
AND d1.region = "us-central1"
'
+-----------------------------------------+
| r1 |
+-----------------------------------------+
| roles/aiplatform.customCodeServiceAgent |
| roles/cloudfunctions.serviceAgent |
| roles/appengineflex.serviceAgent |
| roles/dataflow.serviceAgent |
| roles/iam.serviceAccountTokenCreator |
| roles/pubsub.serviceAgent |
| roles/ml.serviceAgent |
| roles/run.serviceAgent |
| roles/serverless.serviceAgent |
+-----------------------------------------+
Now lets see what other permissions are included because knowing that signBlob
will allow other permissions
bq query --nouse_legacy_sql '
SELECT
p1, d1.name
FROM
iam-log.iam.roles AS d1,
UNNEST(included_permissions) p1
WHERE
d1._PARTITIONTIME = TIMESTAMP("2022-04-01")
AND d1.name in UNNEST(["roles/aiplatform.customCodeServiceAgent", "roles/cloudfunctions.serviceAgent","roles/appengineflex.serviceAgent","roles/dataflow.serviceAgent","roles/iam.serviceAccountTokenCreator","roles/pubsub.serviceAgent","roles/ml.serviceAgent","roles/run.serviceAgent","roles/serverless.serviceAgent"])
AND d1.region = "us-central1"
AND p1 in UNNEST(["iam.serviceAccounts.signBlob", "iam.serviceAccounts.signJwt","iam.serviceAccounts.getAccessToken","iam.serviceAccounts.getOpenIdToken"])
'
+------------------------------------+-----------------------------------------+
| p1 | name |
+------------------------------------+-----------------------------------------+
| iam.serviceAccounts.getAccessToken | roles/ml.serviceAgent |
| iam.serviceAccounts.getOpenIdToken | roles/ml.serviceAgent |
| iam.serviceAccounts.signBlob | roles/ml.serviceAgent |
| iam.serviceAccounts.signJwt | roles/ml.serviceAgent |
| iam.serviceAccounts.getAccessToken | roles/run.serviceAgent |
| iam.serviceAccounts.getOpenIdToken | roles/run.serviceAgent |
| iam.serviceAccounts.signBlob | roles/run.serviceAgent |
| iam.serviceAccounts.getAccessToken | roles/pubsub.serviceAgent |
| iam.serviceAccounts.getOpenIdToken | roles/pubsub.serviceAgent |
| iam.serviceAccounts.signBlob | roles/pubsub.serviceAgent |
| iam.serviceAccounts.signJwt | roles/pubsub.serviceAgent |
| iam.serviceAccounts.getAccessToken | roles/dataflow.serviceAgent |
| iam.serviceAccounts.signBlob | roles/dataflow.serviceAgent |
| iam.serviceAccounts.signJwt | roles/dataflow.serviceAgent |
| iam.serviceAccounts.getAccessToken | roles/serverless.serviceAgent |
| iam.serviceAccounts.getOpenIdToken | roles/serverless.serviceAgent |
| iam.serviceAccounts.signBlob | roles/serverless.serviceAgent |
| iam.serviceAccounts.getAccessToken | roles/appengineflex.serviceAgent |
| iam.serviceAccounts.signBlob | roles/appengineflex.serviceAgent |
| iam.serviceAccounts.signJwt | roles/appengineflex.serviceAgent |
| iam.serviceAccounts.getAccessToken | roles/cloudfunctions.serviceAgent |
| iam.serviceAccounts.getOpenIdToken | roles/cloudfunctions.serviceAgent |
| iam.serviceAccounts.signBlob | roles/cloudfunctions.serviceAgent |
| iam.serviceAccounts.getAccessToken | roles/iam.serviceAccountTokenCreator |
| iam.serviceAccounts.getOpenIdToken | roles/iam.serviceAccountTokenCreator |
| iam.serviceAccounts.signBlob | roles/iam.serviceAccountTokenCreator |
| iam.serviceAccounts.signJwt | roles/iam.serviceAccountTokenCreator |
| iam.serviceAccounts.getAccessToken | roles/aiplatform.customCodeServiceAgent |
| iam.serviceAccounts.getOpenIdToken | roles/aiplatform.customCodeServiceAgent |
| iam.serviceAccounts.signBlob | roles/aiplatform.customCodeServiceAgent |
| iam.serviceAccounts.signJwt | roles/aiplatform.customCodeServiceAgent |
+------------------------------------+-----------------------------------------+
From the above, note that
roles/run.serviceAgent
does not include iam.serviceAccounts.signJwt
but it can already do that anywayroles/dataflow.serviceAgent
does not include iam.serviceAccounts.getOpenIdToken
but it can already do that anywayroles/serverless.serviceAgent
does not include iam.serviceAccounts.signJwt
but it can already do that anywayroles/appengineflex.serviceAgent
does not include iam.serviceAccounts.getOpenIdToken
but it can already do that anywayroles/cloudfunctions.serviceAgent
does not include iam.serviceAccounts.signJwt
but it can already do that anywaySo…don’t take my word for all this, see for yourself with this demo:
The following code will crate a serviceAccount, allow you to impersonate that account and then…using just signBlob
iamcredentials api, we will:
export PROJECT_ID=`gcloud config get-value core/project`
export PROJECT_NUMBER=`gcloud projects describe $PROJECT_ID --format='value(projectNumber)'`
export GCLOUD_USER=`gcloud config get-value core/account`
gcloud iam service-accounts create svcaccount1
gcloud iam service-accounts add-iam-policy-binding \
svcaccount1@$PROJECT_ID.iam.gserviceaccount.com \
--member=user:$GCLOUD_USER \
--role=roles/iam.serviceAccountTokenCreator
Now run the app:
go run main.go --svcAccountEmail=svcaccount1@$PROJECT_ID.iam.gserviceaccount.com
One potential issue with self-impersonation is that if a service account is allowed to impersonate itself, an access_token
it has can be used within an hour to issue a new access_token
. Then that one can be used to get a new one and so on within an hour (i.,e before the previous token expires).
However GCP prevents cycling impersonated tokens by detecting the source and preventing the chained use:
For example, see in the following how the chained use of the token is prohibited (with a useless error but thats ok)
export PROJECT_ID=`gcloud config get-value core/project`
export GCLOUD_USER=`gcloud config get-value core/account`
gcloud config set core/log_http_redact_token false
gcloud iam service-accounts create svcacctcycle
gcloud iam service-accounts add-iam-policy-binding \
--role roles/iam.serviceAccountTokenCreator \
--member "user:$GCLOUD_USER" \
svcacctcycle@$PROJECT_ID.iam.gserviceaccount.com
gcloud iam service-accounts add-iam-policy-binding \
--role roles/iam.serviceAccountTokenCreator \
--member "serviceAccount:svcacctcycle@$PROJECT_ID.iam.gserviceaccount.com" \
svcacctcycle@$PROJECT_ID.iam.gserviceaccount.com
gcloud auth print-access-token \
--impersonate-service-account svcacctcycle@$PROJECT_ID.iam.gserviceaccount.com > /tmp/sa_1.txt
gcloud auth revoke
gcloud auth print-access-token \
--impersonate-service-account svcacctcycle@$PROJECT_ID.iam.gserviceaccount.com \
--access-token-file=/tmp/sa_1.txt > /tmp/sa_2.txt
gcloud auth print-access-token \
--impersonate-service-account svcacctcycle@$PROJECT_ID.iam.gserviceaccount.com \
--access-token-file=/tmp/sa_2.txt
{
"error": {
"code": 403,
"message": "The caller does not have permission",
"status": "PERMISSION_DENIED"
}
}
Also see,
This site supports webmentions. Send me a mention via this form.