When you create a Google service account key file for an external system, the private key has to
be transported. In addition, the generated key is valid until January 1st in
the year 10000. In this blog I will show you, how an external system can identity itself as the
service account without exposing the private key.
why are Google service account key files a problem?
For most applications running on Google Cloud Platform there is no need for downloading a key
file: The run-time environment provides temporary credentials to your application. Unfortunately,
external systems have no such luxury. So why are service account keys an issue? Look into one:
{
"type": "service_account",
"project_id": "demo-project",
"private_key_id": "f871b60d0617be19393bb66ea142887fc9621360",
"private_key": "-----BEGIN RSA PRIVATE KEY-----.....",
"client_email": "look-no-keys@demo-project.iam.gserviceaccount.com",
"client_id": "102234449335144000000",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/look-no-keys%40demo-project.iam.gserviceaccount.com"
}
As you can see, there is a plain text RSA private key in there! In addition, if the key is
compromised, it will be valid until the 1st of January in the year 10000.
Create your own Google service account private key file
When you look at a google service account key file, you will find the following variable parts:
field | description |
---|---|
project_id | of the service account |
private_key_id | the id of the private key |
private_key | the pem encoded RSA private key |
client_email | the email address of the service account |
client_id | the id of the service account in Google IAM |
client_x509_cert_url | url pointing to the certificates of the service account |
From this it is clear, that if you have an RSA private key, you can create a key file
and associate the public key with any service account with
the key upload command. To do this, you have to:
- Create a service account
- Bind a role to it
- Generate a private key
- Create a self-signed certificate
- Upload the public key
- Generate the service account key file
After that, you can use the key file to identify as the service account!
Create a service account
To create our demo service account, type:
gcloud iam service-accounts create look-no-keys
PROJECT_ID=$(gcloud config get-value project)
CLIENT_EMAIL=look-no-keys@${PROJECT_ID}.iam.gserviceaccount.com
CLIENT_ID=$(gcloud iam service-accounts \
describe $CLIENT_EMAIL \
--format 'value(uniqueId)')
Bind a role
To bind a role to the service account, type:
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member "serviceAccount:$CLIENT_EMAIL" \
--role roles/viewer
We chose the role of project viewer for demonstration purpose.
Generate a private key
To generate your own private key, type:
PRIVATE_KEY=$(openssl genrsa 4096)
Of course, you can also use an existing private key on your system.
Create a self-signed certificate
To create a self-signed X509 certificate using your own private key, type:
openssl req -x509 -new \
-key - \
-subj /CN=markvanholsteijn@binx.io \
-out csr.pem <<< $PRIVATE_KEY
openssl x509 \
-in csr.pem \
-signkey - \
-days 365 \
-out certificate.pem <<< $PRIVATE_KEY
The certificate both contains information about the subject and the public key.
Upload the public key
To upload this public key to the service account, type:
gcloud iam service-accounts keys \
upload certificate.pem \
--iam-account $CLIENT_EMAIL \
--format json > uploaded.json
PRIVATE_KEY_ID=$(jq -r .name uploaded.json | \
awk -F/ '{print $NF}')
I should be able to calculate the key id from the certificate.pem, but I have not found which
algorithm Google uses.
generate the google service account key file
To generate the Google service account key file, type:
touch look-no-keys.json
chmod 0600 look-no-keys.json
jq -n \
--arg PRIVATE_KEY "$PRIVATE_KEY" \
--arg PROJECT_ID $PROJECT_ID \
--arg CLIENT_EMAIL $CLIENT_EMAIL \
--arg CLIENT_ID $CLIENT_ID \
--arg PRIVATE_KEY_ID $PRIVATE_KEY_ID \
'{
"type": "service_account",
"project_id": $PROJECT_ID,
"private_key_id": $PRIVATE_KEY_ID,
"private_key": $PRIVATE_KEY,
"client_email": $CLIENT_EMAIL,
"client_id": $CLIENT_ID,
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": @uri "https://www.googleapis.com/robot/v1/metadata/x509/\($CLIENT_EMAIL)"
}' > look-no-keys.json
In this demonstration, we are copying the private key into the
Authenticate using your private key
To authenticate yourself using your own private key, type:
gcloud auth activate-service-account \
--key-file look-no-keys.json
Now you can view the project resources, but you cannot change anything:
$ gcloud compute instances list
Listed 0 items.
$ gcloud compute instances create demo1
ERROR (gcloud.compute.instances.create) Could not fetch resource:
- Required 'compute.instances.create' permission
Conclusion
In the blog, I demonstrated that an external system can authenticate itself using its own private RSA key.
The key does not have to be transported, and the lifetime of the public key can be limited to a period
of your choice. This knowledge can be used to keep private key data out of the Terraform state file or
to construct your credentials programmatically using an existing private key. Image by Steve Buissinne from Pixabay