How to create your own Google service account key file

Hiring

We are Binx. We make every organization cloud-native.

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

Mark van Holsteijn is a senior software systems architect, and CTO of binx.io. He is passionate about removing waste in the software delivery process and keeping things clear and simple.
Share this article: Tweet this post / Post on LinkedIn