From d4583dcad7d68d3c1503b04ec0d3364809304807 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 30 Jan 2022 12:02:58 +0100 Subject: Adding upstream version 4.5.0+dfsg. Signed-off-by: Daniel Baumann --- pre_commit_hooks/detect_aws_credentials.py | 151 +++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 pre_commit_hooks/detect_aws_credentials.py (limited to 'pre_commit_hooks/detect_aws_credentials.py') diff --git a/pre_commit_hooks/detect_aws_credentials.py b/pre_commit_hooks/detect_aws_credentials.py new file mode 100644 index 0000000..4f59d9c --- /dev/null +++ b/pre_commit_hooks/detect_aws_credentials.py @@ -0,0 +1,151 @@ +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 { + os.environ[env_var] + for env_var in ( + 'AWS_CONFIG_FILE', 'AWS_CREDENTIAL_FILE', + 'AWS_SHARED_CREDENTIALS_FILE', 'BOTO_CONFIG', + ) + 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 ( + 'AWS_SECRET_ACCESS_KEY', 'AWS_SECURITY_TOKEN', 'AWS_SESSION_TOKEN', + ): + if os.environ.get(env_var): + keys.add(os.environ[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() + try: + parser.read(aws_credentials_file_path) + except configparser.MissingSectionHeaderError: + return set() + + keys = set() + for section in parser.sections(): + for var in ( + 'aws_secret_access_key', 'aws_security_token', + 'aws_session_token', + ): + try: + key = parser.get(section, var).strip() + if key: + keys.add(key) + except configparser.NoOptionError: + pass + 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 = content.read() + 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') + parser.add_argument( + '--credentials-file', + dest='credentials_file', + action='append', + default=[ + '~/.aws/config', '~/.aws/credentials', '/etc/boto.cfg', '~/.boto', + ], + help=( + 'Location of additional AWS credential file from which to get ' + 'secret keys. Can be passed multiple times.' + ), + ) + parser.add_argument( + '--allow-missing-credentials', + dest='allow_missing_credentials', + action='store_true', + 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: + print( + '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 + else: + return 0 + + +if __name__ == '__main__': + raise SystemExit(main()) -- cgit v1.2.3