blob: 4f59d9cfe0008ade3ac2c18e86416b67a35dd1c7 [file] [log] [blame]
Anthony Sottile8f615292022-01-15 19:24:05 -05001from __future__ import annotations
2
Ara Hayrabedian95bf20d2015-05-31 23:50:49 +04003import argparse
Anthony Sottilef5c42a02020-02-05 11:10:42 -08004import configparser
Ara Hayrabedian95bf20d2015-05-31 23:50:49 +04005import os
Anthony Sottilef5c42a02020-02-05 11:10:42 -08006from typing import NamedTuple
Anthony Sottile030bfac2019-01-31 19:19:10 -08007from typing import Sequence
Ara Hayrabedian02e8bdc2015-06-12 19:20:56 +04008
Anthony Sottilef5c42a02020-02-05 11:10:42 -08009
10class BadFile(NamedTuple):
11 filename: str
12 key: str
Ara Hayrabedian95bf20d2015-05-31 23:50:49 +040013
14
Anthony Sottile8f615292022-01-15 19:24:05 -050015def get_aws_cred_files_from_env() -> set[str]:
Daniel Roschkab0d4cdb2016-12-30 08:41:24 +010016 """Extract credential file paths from environment variables."""
Anthony Sottile45756522019-02-11 19:56:15 -080017 return {
18 os.environ[env_var]
19 for env_var in (
20 'AWS_CONFIG_FILE', 'AWS_CREDENTIAL_FILE',
21 'AWS_SHARED_CREDENTIALS_FILE', 'BOTO_CONFIG',
22 )
23 if env_var in os.environ
24 }
Daniel Roschkab0d4cdb2016-12-30 08:41:24 +010025
26
Anthony Sottile8f615292022-01-15 19:24:05 -050027def get_aws_secrets_from_env() -> set[str]:
Daniel Roschkab0d4cdb2016-12-30 08:41:24 +010028 """Extract AWS secrets from environment variables."""
29 keys = set()
Daniel Roschka3939aee2017-01-03 19:05:49 +010030 for env_var in (
Anthony Sottile2a902e02017-07-12 18:35:24 -070031 'AWS_SECRET_ACCESS_KEY', 'AWS_SECURITY_TOKEN', 'AWS_SESSION_TOKEN',
Daniel Roschka3939aee2017-01-03 19:05:49 +010032 ):
Alexander Deminb3a28de2020-02-13 17:12:45 +000033 if os.environ.get(env_var):
Daniel Roschkab0d4cdb2016-12-30 08:41:24 +010034 keys.add(os.environ[env_var])
Daniel Roschkab0d4cdb2016-12-30 08:41:24 +010035 return keys
36
37
Anthony Sottile8f615292022-01-15 19:24:05 -050038def get_aws_secrets_from_file(credentials_file: str) -> set[str]:
Daniel Roschkab0d4cdb2016-12-30 08:41:24 +010039 """Extract AWS secrets from configuration files.
40
41 Read an ini-style configuration file and return a set with all found AWS
42 secret access keys.
Ara Hayrabedian95bf20d2015-05-31 23:50:49 +040043 """
44 aws_credentials_file_path = os.path.expanduser(credentials_file)
45 if not os.path.exists(aws_credentials_file_path):
Daniel Roschkab0d4cdb2016-12-30 08:41:24 +010046 return set()
Ara Hayrabedian95bf20d2015-05-31 23:50:49 +040047
Ara Hayrabedian3078aec2015-06-12 16:24:01 +040048 parser = configparser.ConfigParser()
Daniel Roschkab0d4cdb2016-12-30 08:41:24 +010049 try:
50 parser.read(aws_credentials_file_path)
51 except configparser.MissingSectionHeaderError:
52 return set()
Ara Hayrabedian95bf20d2015-05-31 23:50:49 +040053
54 keys = set()
55 for section in parser.sections():
Daniel Roschka3939aee2017-01-03 19:05:49 +010056 for var in (
57 'aws_secret_access_key', 'aws_security_token',
Anthony Sottile2a902e02017-07-12 18:35:24 -070058 'aws_session_token',
Daniel Roschka3939aee2017-01-03 19:05:49 +010059 ):
Daniel Roschkab0d4cdb2016-12-30 08:41:24 +010060 try:
Pablo Vega83fca4c2018-01-26 00:28:39 -080061 key = parser.get(section, var).strip()
62 if key:
63 keys.add(key)
Daniel Roschkab0d4cdb2016-12-30 08:41:24 +010064 except configparser.NoOptionError:
65 pass
Ara Hayrabedian95bf20d2015-05-31 23:50:49 +040066 return keys
67
68
Anthony Sottilef5c42a02020-02-05 11:10:42 -080069def check_file_for_aws_keys(
70 filenames: Sequence[str],
Anthony Sottile8f615292022-01-15 19:24:05 -050071 keys: set[bytes],
72) -> list[BadFile]:
Daniel Roschkab0d4cdb2016-12-30 08:41:24 +010073 """Check if files contain AWS secrets.
74
75 Return a list of all files containing AWS secrets and keys found, with all
76 but the first four characters obfuscated to ease debugging.
77 """
Dean Wilsona6665272015-10-28 05:13:37 +000078 bad_files = []
79
80 for filename in filenames:
Anthony Sottile21553c22020-02-18 10:24:17 -080081 with open(filename, 'rb') as content:
Dean Wilsona6665272015-10-28 05:13:37 +000082 text_body = content.read()
Daniel Roschkab0d4cdb2016-12-30 08:41:24 +010083 for key in keys:
84 # naively match the entire file, low chance of incorrect
85 # collision
Alexander Demin75d48322020-02-13 12:01:38 +000086 if key in text_body:
Anthony Sottile21553c22020-02-18 10:24:17 -080087 key_hidden = key.decode()[:4].ljust(28, '*')
88 bad_files.append(BadFile(filename, key_hidden))
Dean Wilsona6665272015-10-28 05:13:37 +000089 return bad_files
Ara Hayrabedian95bf20d2015-05-31 23:50:49 +040090
91
Anthony Sottile8f615292022-01-15 19:24:05 -050092def main(argv: Sequence[str] | None = None) -> int:
Ara Hayrabedian95bf20d2015-05-31 23:50:49 +040093 parser = argparse.ArgumentParser()
Daniel Roschkab0d4cdb2016-12-30 08:41:24 +010094 parser.add_argument('filenames', nargs='+', help='Filenames to run')
Ara Hayrabedian95bf20d2015-05-31 23:50:49 +040095 parser.add_argument(
Anthony Sottiled444ab82016-02-08 17:05:39 -080096 '--credentials-file',
Ryan Delaney2d4833c2018-10-28 15:58:14 -070097 dest='credentials_file',
Daniel Roschkab0d4cdb2016-12-30 08:41:24 +010098 action='append',
Anthony Sottile5da199b2017-01-03 13:13:44 -080099 default=[
100 '~/.aws/config', '~/.aws/credentials', '/etc/boto.cfg', '~/.boto',
101 ],
Anthony Sottiled444ab82016-02-08 17:05:39 -0800102 help=(
Ryan Delaney0d83fed2018-10-28 15:59:39 -0700103 'Location of additional AWS credential file from which to get '
104 'secret keys. Can be passed multiple times.'
Anthony Sottile2a902e02017-07-12 18:35:24 -0700105 ),
Ara Hayrabedian95bf20d2015-05-31 23:50:49 +0400106 )
Mike Fiedler312e7212017-02-10 08:26:26 -0500107 parser.add_argument(
108 '--allow-missing-credentials',
109 dest='allow_missing_credentials',
110 action='store_true',
Anthony Sottile2a902e02017-07-12 18:35:24 -0700111 help='Allow hook to pass when no credentials are detected.',
Mike Fiedler312e7212017-02-10 08:26:26 -0500112 )
Ara Hayrabedian95bf20d2015-05-31 23:50:49 +0400113 args = parser.parse_args(argv)
Daniel Roschkab0d4cdb2016-12-30 08:41:24 +0100114
Ryan Delaney2d4833c2018-10-28 15:58:14 -0700115 credential_files = set(args.credentials_file)
Daniel Roschkab0d4cdb2016-12-30 08:41:24 +0100116
117 # Add the credentials files configured via environment variables to the set
118 # of files to to gather AWS secrets from.
Anthony Sottile45756522019-02-11 19:56:15 -0800119 credential_files |= get_aws_cred_files_from_env()
Daniel Roschkab0d4cdb2016-12-30 08:41:24 +0100120
Anthony Sottile8f615292022-01-15 19:24:05 -0500121 keys: set[str] = set()
Daniel Roschkab0d4cdb2016-12-30 08:41:24 +0100122 for credential_file in credential_files:
123 keys |= get_aws_secrets_from_file(credential_file)
124
125 # Secrets might be part of environment variables, so add such secrets to
126 # the set of keys.
127 keys |= get_aws_secrets_from_env()
128
Mike Fiedler312e7212017-02-10 08:26:26 -0500129 if not keys and args.allow_missing_credentials:
130 return 0
131
Ara Hayrabedian02e8bdc2015-06-12 19:20:56 +0400132 if not keys:
Daniel Roschka3939aee2017-01-03 19:05:49 +0100133 print(
134 'No AWS keys were found in the configured credential files and '
135 'environment variables.\nPlease ensure you have the correct '
Anthony Sottile2a902e02017-07-12 18:35:24 -0700136 'setting for --credentials-file',
Daniel Roschka3939aee2017-01-03 19:05:49 +0100137 )
Ara Hayrabedian02e8bdc2015-06-12 19:20:56 +0400138 return 2
Ara Hayrabedian95bf20d2015-05-31 23:50:49 +0400139
Anthony Sottile21553c22020-02-18 10:24:17 -0800140 keys_b = {key.encode() for key in keys}
141 bad_filenames = check_file_for_aws_keys(args.filenames, keys_b)
Dean Wilsona6665272015-10-28 05:13:37 +0000142 if bad_filenames:
143 for bad_file in bad_filenames:
Anthony Sottilef5c42a02020-02-05 11:10:42 -0800144 print(f'AWS secret found in {bad_file.filename}: {bad_file.key}')
Dean Wilsona6665272015-10-28 05:13:37 +0000145 return 1
146 else:
147 return 0
Ara Hayrabedian95bf20d2015-05-31 23:50:49 +0400148
Anthony Sottile70e405e2016-11-30 09:56:42 -0800149
Ara Hayrabedian95bf20d2015-05-31 23:50:49 +0400150if __name__ == '__main__':
Anthony Sottile39ab2ed2021-10-23 13:23:50 -0400151 raise SystemExit(main())