blob: 4f59d9cfe0008ade3ac2c18e86416b67a35dd1c7 [file] [log] [blame]
from __future__ import annotations
import argparse
import configparser
import os
from typing import NamedTuple
from typing import Sequence
class BadFile(NamedTuple):
filename: str
key: str
def get_aws_cred_files_from_env() -> set[str]:
"""Extract credential file paths from environment variables."""
return {
for env_var in (
if env_var in os.environ
def get_aws_secrets_from_env() -> set[str]:
"""Extract AWS secrets from environment variables."""
keys = set()
for env_var in (
if os.environ.get(env_var):
return keys
def get_aws_secrets_from_file(credentials_file: str) -> set[str]:
"""Extract AWS secrets from configuration files.
Read an ini-style configuration file and return a set with all found AWS
secret access keys.
aws_credentials_file_path = os.path.expanduser(credentials_file)
if not os.path.exists(aws_credentials_file_path):
return set()
parser = configparser.ConfigParser()
except configparser.MissingSectionHeaderError:
return set()
keys = set()
for section in parser.sections():
for var in (
'aws_secret_access_key', 'aws_security_token',
key = parser.get(section, var).strip()
if key:
except configparser.NoOptionError:
return keys
def check_file_for_aws_keys(
filenames: Sequence[str],
keys: set[bytes],
) -> list[BadFile]:
"""Check if files contain AWS secrets.
Return a list of all files containing AWS secrets and keys found, with all
but the first four characters obfuscated to ease debugging.
bad_files = []
for filename in filenames:
with open(filename, 'rb') as content:
text_body =
for key in keys:
# naively match the entire file, low chance of incorrect
# collision
if key in text_body:
key_hidden = key.decode()[:4].ljust(28, '*')
bad_files.append(BadFile(filename, key_hidden))
return bad_files
def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='+', help='Filenames to run')
'~/.aws/config', '~/.aws/credentials', '/etc/boto.cfg', '~/.boto',
'Location of additional AWS credential file from which to get '
'secret keys. Can be passed multiple times.'
help='Allow hook to pass when no credentials are detected.',
args = parser.parse_args(argv)
credential_files = set(args.credentials_file)
# Add the credentials files configured via environment variables to the set
# of files to to gather AWS secrets from.
credential_files |= get_aws_cred_files_from_env()
keys: set[str] = set()
for credential_file in credential_files:
keys |= get_aws_secrets_from_file(credential_file)
# Secrets might be part of environment variables, so add such secrets to
# the set of keys.
keys |= get_aws_secrets_from_env()
if not keys and args.allow_missing_credentials:
return 0
if not keys:
'No AWS keys were found in the configured credential files and '
'environment variables.\nPlease ensure you have the correct '
'setting for --credentials-file',
return 2
keys_b = {key.encode() for key in keys}
bad_filenames = check_file_for_aws_keys(args.filenames, keys_b)
if bad_filenames:
for bad_file in bad_filenames:
print(f'AWS secret found in {bad_file.filename}: {bad_file.key}')
return 1
return 0
if __name__ == '__main__':
raise SystemExit(main())