Skip to content

mercari/gcp-sa-key-checker

Repository files navigation

Third Party GCP Service Account Key Checker

This program implements a simple security checker for GCP Service Account Keys for any GCP Service Account using the public x509 certificate endpoint.

It is useful for auditing if GCP Service Accounts used by third party SaaS services are following best pratices before you grant them access to your environment.

Background

All Google Cloud Service Accounts have service account keys associated with them which they use for signing JWTs which can be used as idtokens or exchanged for access tokens. These are almost always 2048-bit RSA keys and are a foundational component of the GCP security model.

These keys have attributes keyOrigin and keyType, which can be:

  • keyOrigin
    • GOOGLE_PROVIDED - key material was generated by Google
    • USER_PROVIDED - generated by the user
  • keyType
    • SYSTEM_MANAGED - key material is managed by GCP
    • USER_MANAGED - key material is managed by the user

These can be in the following combinations:

Note that USER_PROVIDED/SYSTEM_MANAGED doesn't exist because there's no way to import private key material into the cloud.

According to Best practices for managing service account keys it is prefered to not have any USER_MANAGED keys.

GCP does not directly make the information about what types of keys are attached to a service account public, however, it does provide several endpoints (documented here and here) to see the public portions of the keys, to be used for verifying signatures.

This tool takes the certificates from the public x509 endpoint, and uses heuristics to determine the key origin and type for each key.

Usage

Clone this repository and ensure you have golang installed.

If you plan to use features requiring GCP authentication, ensure you run gcloud auth login --update-adc.

You can run the tool with go run ./... [args] (or go build and then ./gcp-sa-key-checker [args]).

The list of Service Account emails to process can be provided in four different ways:

  • On the command line as individual positional arguments
  • with the --in FILE flag, pointing to a text file with one service account email on each line
  • with the --project PROJECTID flag, which will list all Service Accounts in the project using the projects.serviceAccounts.list API
  • with the --scope SCOPE flag, which will list all active service accounts using the searchAllResources API. Supported scopes are:
    • projects/{PROJECT_ID} or projects/{PROJECT_NUMBER} (redundant with --project flag, but requires different permissions)
    • folders/{FOLDER_NUMBER}
    • organizations/{ORGANIZATION_NUMBER}

The tool can be run in two different modes:

  • Normal: Default mode, only list keys that are likely not GOOGLE_PROVIDED/SYSTEM_MANAGED
  • Verbose: enabled with --verbose, it will output all the information about all keys seen. This could be useful for diffing and monitoring but is mostly for debugging.
  • Ground Truth: enabled with --ground-truth, it will use ADCs to pull the real status of the keys from the IAM API, and then compare that with the predicted keyOrigin/keyType from the public information, then it will report any descrepencies. This is useful for verifying the correctness of the heuristics.

Additional flags:

  • --out-dir DIR - will write the PEM encoded x509 certificates for all scanned SAs to the output directory
  • --quota-project PROJECT_ID - will use the specified project for quota/billing purposes. Only really relevant for the --ground-truth which issues many IAM read calls.

How it Works

The certificate for each SA key is downloaded using the https://www.googleapis.com/service_accounts/v1/metadata/x509/ACCOUNT_EMAIL endpoint. Checks are run to gather "Signals" which are a guess towards a specific keyOrigin+keyType combination, and an explanation. The following checks are run, each of which were determined experimentally:

  • Validity period (NotBefore, NotAfter)
    • 16d12h15m -> GOOGLE_PROVIDED/SYSTEM_MANAGED
    • 3650d (~10 years) -> GOOGLE_PROVIDED/USER_MANAGED
    • Valid between 730d (~2 years) and 761d (~2 years + 1 month) -> GOOGLE_PROVIDED/SYSTEM_MANAGED
      • This does not seem to be documented but was confirmed experimentally. Seems to have started rollout around February 2025
    • NotAfter date of 9999-12-31 23:59:59 +0000 UTC -> GOOGLE_PROVIDED/SYSTEM_MANAGED
      • SA keys generated after around May 2021 seem to not expire at all
    • Any period in the iam.serviceAccountKeyExpiryHours org constraint -> GOOGLE_PROVIDED/USER_MANAGED
    • Any other period can not be generated by Google Cloud, so must be USER_PROVIDED/USER_MANAGED
  • Names (Subject and Issuer)
    • GAIA IDs -> GOOGLE_PROVIDED/USER_MANAGED
      • Experimentally determined user-generated keys always have GAIA IDs in these fields.
    • service account email or email truncated to 64 bytes -> GOOGLE_PROVIDED/GOOGLE_MANAGED
      • It is unclear when this truncation occurs, and seems to not be documented.
    • Anything else cannot be generated by GCP -> USER_PROVIDED/USER_MANAGED
  • Crypto settings:
    • 1024 bit SHA1WithRSA -> GOOGLE_PROVIDED/USER_MANAGED
    • anything else other than 2024 bit SHA1WithRSA -> USER_PROVIDED/USER_MANAGED
  • The extensions (key usage, etc) are also checked because these are very consistent from GCP, so if they differ they key must have been USER_PROVIDED.

Finally, the signals are compiled, and ordered by precedence. The highest precedence finding wins. The prececdence order is USER_PROVIDED/USER_MANAGED, GOOGLE_PROVIDED/USER_MANAGED and finally GOOGLE_PROVIDED/GOOGLE_MANAGED.

Findings

This was run with --ground-truth across the main Mercari GCP organization which has existed for over 10 years and contains >20k service accounts, including some that have user-generated or user-managed keys. There were no disparities between the heuristic detection code in this script and the ground truth from the API.

Additionally, we pulled data from Wiz for external service accounts that are connected to our environment using the following advanced query:

{
  "select": true,
  "type": [
    "SERVICE_ACCOUNT"
  ],
  "where": {
    "_partial": {
      "EQUALS": true
    },
    "externalId": {
      "ENDS_WITH": [
        ".gserviceaccount.com"
      ]
    }
  }
}

This discovered that several of our SaaS services are potentially not following GCP best practices for managing service account keys and we plan to privately follow up with them.

The --out-dir parameter is useful for running keys through badkeys, however we found no examples of such keys in practice. A survey of SA keys looking for issues like duplicate moduli, shared primes or other oddities could be interesting future work, particularly if combined with recon to gather a large number of SAs to scan.

Contribution

If you want to submit a PR for bug fixes or documentation, please read the CONTRIBUTING.md and follow the instruction beforehand.

License

The gcp-sa-key-checker is released under the MIT License.

About

A recon tool for GCP Service Account Keys that requires no permissions

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Languages