Skip to content

Instantly share code, notes, and snippets.

@jay0lee
Last active June 12, 2024 16:02
Show Gist options
  • Save jay0lee/620b2ba6d37722ccaa0f8c91d6025bf4 to your computer and use it in GitHub Desktop.
Save jay0lee/620b2ba6d37722ccaa0f8c91d6025bf4 to your computer and use it in GitHub Desktop.
'''
Simple script to determine current service account private keys and
try to guess if they are user-managed (downloaded or created outside Google)
or google-managed (maintained by Google, private to Google).
Usage:
python3 detect_sa_key_type.py <service account email or Client ID>
Sample output:
'''
import datetime
import re
import sys
from cryptography import x509
from cryptography.hazmat.backends import default_backend
import requests
def guess_mgmt(cert):
# Google-managed keys should be 2048 length
print(f'key is good {cert.not_valid_before} to {cert.not_valid_after}')
um_reasons = []
key_size = cert.public_key().key_size
if key_size != 2048:
um_reasons.append(f'length is {key_size} and Google-managed should be 2048')
# Google-managed keys should be valid for a bit more than 2 years
max_validity = datetime.timedelta(days=(365*2)+31)
validity_range = cert.not_valid_after - cert.not_valid_before
if validity_range > max_validity:
um_reasons.append(f'validity is {str(validity_range)} but google-managed keys are valid for about two years')
# Google-managed keys shouldn't start validity more than a month ago
today = datetime.datetime.now()
last_month = today - datetime.timedelta(days=31)
if last_month > cert.not_valid_before:
um_reasons.append(f'it hasn\'t been rotated since {cert.not_valid_before} while google-managed keys are usually rotated monthly')
# Google-managed keys should have SA email address as issuer and subject
sa_cn_rx = r'CN=.*\.gserviceaccount\.com$'
subject = cert.issuer.rfc4514_string()
if not re.match(sa_cn_rx, subject):
um_reasons.append(f'subject is {subject} but google-managed keys would be CN=*.gserviceaccount.com')
issuer = cert.issuer.rfc4514_string()
if not re.match(sa_cn_rx, issuer):
um_reasons.append(f'issuer is {issuer} but google-managed keys would be CN=*.gserviceaccount.com')
if um_reasons:
return('user-managed', ', '.join(um_reasons))
return ('google-managed', 'it matches google cert characteristics')
sa_id = sys.argv[1]
certs_url = f'https://www.googleapis.com/service_accounts/v1/metadata/x509/{sa_id}'
print(f'Getting {certs_url}')
r = requests.get(certs_url)
if r.status_code == 404:
print(f'ERROR: {r.status_code} - {r.reason}')
print('This service account may be disabled, deleted or never have existed.')
sys.exit(1)
if r.status_code != 200:
print(f'ERROR: {r.status_code} - {r.reason}')
sys.exit(1)
keys = r.json().items()
print(f'service account has {len(keys)} public certificates.')
for kid, raw_cert in keys:
print(f'Key ID: {kid}')
cert = x509.load_pem_x509_certificate(raw_cert.encode(), default_backend())
who_manages, reason = guess_mgmt(cert)
print(f'I believe this is a {who_manages.upper()} key because {reason}.')
print()
cryptography
requests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment