Ara Hayrabedian | 95bf20d | 2015-05-31 23:50:49 +0400 | [diff] [blame] | 1 | from __future__ import print_function |
| 2 | from __future__ import unicode_literals |
| 3 | |
| 4 | import argparse |
Ara Hayrabedian | 95bf20d | 2015-05-31 23:50:49 +0400 | [diff] [blame] | 5 | import os |
Ara Hayrabedian | 02e8bdc | 2015-06-12 19:20:56 +0400 | [diff] [blame] | 6 | |
Anthony Sottile | 8be6f4c | 2016-08-22 14:38:49 -0700 | [diff] [blame] | 7 | from six.moves import configparser |
Ara Hayrabedian | 95bf20d | 2015-05-31 23:50:49 +0400 | [diff] [blame] | 8 | |
| 9 | |
Daniel Roschka | b0d4cdb | 2016-12-30 08:41:24 +0100 | [diff] [blame] | 10 | def get_aws_credential_files_from_env(): |
| 11 | """Extract credential file paths from environment variables.""" |
| 12 | files = set() |
Daniel Roschka | 3939aee | 2017-01-03 19:05:49 +0100 | [diff] [blame] | 13 | for env_var in ( |
| 14 | 'AWS_CONFIG_FILE', 'AWS_CREDENTIAL_FILE', 'AWS_SHARED_CREDENTIALS_FILE', |
Anthony Sottile | 2a902e0 | 2017-07-12 18:35:24 -0700 | [diff] [blame^] | 15 | 'BOTO_CONFIG', |
Daniel Roschka | 3939aee | 2017-01-03 19:05:49 +0100 | [diff] [blame] | 16 | ): |
| 17 | if env_var in os.environ: |
Daniel Roschka | b0d4cdb | 2016-12-30 08:41:24 +0100 | [diff] [blame] | 18 | files.add(os.environ[env_var]) |
Daniel Roschka | b0d4cdb | 2016-12-30 08:41:24 +0100 | [diff] [blame] | 19 | return files |
| 20 | |
| 21 | |
| 22 | def get_aws_secrets_from_env(): |
| 23 | """Extract AWS secrets from environment variables.""" |
| 24 | keys = set() |
Daniel Roschka | 3939aee | 2017-01-03 19:05:49 +0100 | [diff] [blame] | 25 | for env_var in ( |
Anthony Sottile | 2a902e0 | 2017-07-12 18:35:24 -0700 | [diff] [blame^] | 26 | 'AWS_SECRET_ACCESS_KEY', 'AWS_SECURITY_TOKEN', 'AWS_SESSION_TOKEN', |
Daniel Roschka | 3939aee | 2017-01-03 19:05:49 +0100 | [diff] [blame] | 27 | ): |
| 28 | if env_var in os.environ: |
Daniel Roschka | b0d4cdb | 2016-12-30 08:41:24 +0100 | [diff] [blame] | 29 | keys.add(os.environ[env_var]) |
Daniel Roschka | b0d4cdb | 2016-12-30 08:41:24 +0100 | [diff] [blame] | 30 | return keys |
| 31 | |
| 32 | |
| 33 | def get_aws_secrets_from_file(credentials_file): |
| 34 | """Extract AWS secrets from configuration files. |
| 35 | |
| 36 | Read an ini-style configuration file and return a set with all found AWS |
| 37 | secret access keys. |
Ara Hayrabedian | 95bf20d | 2015-05-31 23:50:49 +0400 | [diff] [blame] | 38 | """ |
| 39 | aws_credentials_file_path = os.path.expanduser(credentials_file) |
| 40 | if not os.path.exists(aws_credentials_file_path): |
Daniel Roschka | b0d4cdb | 2016-12-30 08:41:24 +0100 | [diff] [blame] | 41 | return set() |
Ara Hayrabedian | 95bf20d | 2015-05-31 23:50:49 +0400 | [diff] [blame] | 42 | |
Ara Hayrabedian | 3078aec | 2015-06-12 16:24:01 +0400 | [diff] [blame] | 43 | parser = configparser.ConfigParser() |
Daniel Roschka | b0d4cdb | 2016-12-30 08:41:24 +0100 | [diff] [blame] | 44 | try: |
| 45 | parser.read(aws_credentials_file_path) |
| 46 | except configparser.MissingSectionHeaderError: |
| 47 | return set() |
Ara Hayrabedian | 95bf20d | 2015-05-31 23:50:49 +0400 | [diff] [blame] | 48 | |
| 49 | keys = set() |
| 50 | for section in parser.sections(): |
Daniel Roschka | 3939aee | 2017-01-03 19:05:49 +0100 | [diff] [blame] | 51 | for var in ( |
| 52 | 'aws_secret_access_key', 'aws_security_token', |
Anthony Sottile | 2a902e0 | 2017-07-12 18:35:24 -0700 | [diff] [blame^] | 53 | 'aws_session_token', |
Daniel Roschka | 3939aee | 2017-01-03 19:05:49 +0100 | [diff] [blame] | 54 | ): |
Daniel Roschka | b0d4cdb | 2016-12-30 08:41:24 +0100 | [diff] [blame] | 55 | try: |
| 56 | keys.add(parser.get(section, var)) |
| 57 | except configparser.NoOptionError: |
| 58 | pass |
Ara Hayrabedian | 95bf20d | 2015-05-31 23:50:49 +0400 | [diff] [blame] | 59 | return keys |
| 60 | |
| 61 | |
Dean Wilson | a666527 | 2015-10-28 05:13:37 +0000 | [diff] [blame] | 62 | def check_file_for_aws_keys(filenames, keys): |
Daniel Roschka | b0d4cdb | 2016-12-30 08:41:24 +0100 | [diff] [blame] | 63 | """Check if files contain AWS secrets. |
| 64 | |
| 65 | Return a list of all files containing AWS secrets and keys found, with all |
| 66 | but the first four characters obfuscated to ease debugging. |
| 67 | """ |
Dean Wilson | a666527 | 2015-10-28 05:13:37 +0000 | [diff] [blame] | 68 | bad_files = [] |
| 69 | |
| 70 | for filename in filenames: |
| 71 | with open(filename, 'r') as content: |
| 72 | text_body = content.read() |
Daniel Roschka | b0d4cdb | 2016-12-30 08:41:24 +0100 | [diff] [blame] | 73 | for key in keys: |
| 74 | # naively match the entire file, low chance of incorrect |
| 75 | # collision |
| 76 | if key in text_body: |
Anthony Sottile | 5da199b | 2017-01-03 13:13:44 -0800 | [diff] [blame] | 77 | bad_files.append({ |
| 78 | 'filename': filename, 'key': key[:4] + '*' * 28, |
| 79 | }) |
Dean Wilson | a666527 | 2015-10-28 05:13:37 +0000 | [diff] [blame] | 80 | return bad_files |
Ara Hayrabedian | 95bf20d | 2015-05-31 23:50:49 +0400 | [diff] [blame] | 81 | |
| 82 | |
| 83 | def main(argv=None): |
| 84 | parser = argparse.ArgumentParser() |
Daniel Roschka | b0d4cdb | 2016-12-30 08:41:24 +0100 | [diff] [blame] | 85 | parser.add_argument('filenames', nargs='+', help='Filenames to run') |
Ara Hayrabedian | 95bf20d | 2015-05-31 23:50:49 +0400 | [diff] [blame] | 86 | parser.add_argument( |
Anthony Sottile | d444ab8 | 2016-02-08 17:05:39 -0800 | [diff] [blame] | 87 | '--credentials-file', |
Daniel Roschka | b0d4cdb | 2016-12-30 08:41:24 +0100 | [diff] [blame] | 88 | dest='credential_files', |
| 89 | action='append', |
Anthony Sottile | 5da199b | 2017-01-03 13:13:44 -0800 | [diff] [blame] | 90 | default=[ |
| 91 | '~/.aws/config', '~/.aws/credentials', '/etc/boto.cfg', '~/.boto', |
| 92 | ], |
Anthony Sottile | d444ab8 | 2016-02-08 17:05:39 -0800 | [diff] [blame] | 93 | help=( |
Daniel Roschka | b0d4cdb | 2016-12-30 08:41:24 +0100 | [diff] [blame] | 94 | 'Location of additional AWS credential files from which to get ' |
| 95 | 'secret keys from' |
Anthony Sottile | 2a902e0 | 2017-07-12 18:35:24 -0700 | [diff] [blame^] | 96 | ), |
Ara Hayrabedian | 95bf20d | 2015-05-31 23:50:49 +0400 | [diff] [blame] | 97 | ) |
Mike Fiedler | 312e721 | 2017-02-10 08:26:26 -0500 | [diff] [blame] | 98 | parser.add_argument( |
| 99 | '--allow-missing-credentials', |
| 100 | dest='allow_missing_credentials', |
| 101 | action='store_true', |
Anthony Sottile | 2a902e0 | 2017-07-12 18:35:24 -0700 | [diff] [blame^] | 102 | help='Allow hook to pass when no credentials are detected.', |
Mike Fiedler | 312e721 | 2017-02-10 08:26:26 -0500 | [diff] [blame] | 103 | ) |
Ara Hayrabedian | 95bf20d | 2015-05-31 23:50:49 +0400 | [diff] [blame] | 104 | args = parser.parse_args(argv) |
Daniel Roschka | b0d4cdb | 2016-12-30 08:41:24 +0100 | [diff] [blame] | 105 | |
| 106 | credential_files = set(args.credential_files) |
| 107 | |
| 108 | # Add the credentials files configured via environment variables to the set |
| 109 | # of files to to gather AWS secrets from. |
| 110 | credential_files |= get_aws_credential_files_from_env() |
| 111 | |
| 112 | keys = set() |
| 113 | for credential_file in credential_files: |
| 114 | keys |= get_aws_secrets_from_file(credential_file) |
| 115 | |
| 116 | # Secrets might be part of environment variables, so add such secrets to |
| 117 | # the set of keys. |
| 118 | keys |= get_aws_secrets_from_env() |
| 119 | |
Mike Fiedler | 312e721 | 2017-02-10 08:26:26 -0500 | [diff] [blame] | 120 | if not keys and args.allow_missing_credentials: |
| 121 | return 0 |
| 122 | |
Ara Hayrabedian | 02e8bdc | 2015-06-12 19:20:56 +0400 | [diff] [blame] | 123 | if not keys: |
Daniel Roschka | 3939aee | 2017-01-03 19:05:49 +0100 | [diff] [blame] | 124 | print( |
| 125 | 'No AWS keys were found in the configured credential files and ' |
| 126 | 'environment variables.\nPlease ensure you have the correct ' |
Anthony Sottile | 2a902e0 | 2017-07-12 18:35:24 -0700 | [diff] [blame^] | 127 | 'setting for --credentials-file', |
Daniel Roschka | 3939aee | 2017-01-03 19:05:49 +0100 | [diff] [blame] | 128 | ) |
Ara Hayrabedian | 02e8bdc | 2015-06-12 19:20:56 +0400 | [diff] [blame] | 129 | return 2 |
Ara Hayrabedian | 95bf20d | 2015-05-31 23:50:49 +0400 | [diff] [blame] | 130 | |
Dean Wilson | a666527 | 2015-10-28 05:13:37 +0000 | [diff] [blame] | 131 | bad_filenames = check_file_for_aws_keys(args.filenames, keys) |
| 132 | if bad_filenames: |
| 133 | for bad_file in bad_filenames: |
Daniel Roschka | 3939aee | 2017-01-03 19:05:49 +0100 | [diff] [blame] | 134 | print('AWS secret found in {filename}: {key}'.format(**bad_file)) |
Dean Wilson | a666527 | 2015-10-28 05:13:37 +0000 | [diff] [blame] | 135 | return 1 |
| 136 | else: |
| 137 | return 0 |
Ara Hayrabedian | 95bf20d | 2015-05-31 23:50:49 +0400 | [diff] [blame] | 138 | |
Anthony Sottile | 70e405e | 2016-11-30 09:56:42 -0800 | [diff] [blame] | 139 | |
Ara Hayrabedian | 95bf20d | 2015-05-31 23:50:49 +0400 | [diff] [blame] | 140 | if __name__ == '__main__': |
| 141 | exit(main()) |