Last active
June 12, 2024 16:02
-
-
Save jay0lee/620b2ba6d37722ccaa0f8c91d6025bf4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cryptography | |
requests |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment