From 95bf20d52d5daea2ee493622498cb2299db0781e Mon Sep 17 00:00:00 2001 From: Ara Hayrabedian Date: Sun, 31 May 2015 23:50:49 +0400 Subject: [PATCH 1/7] add aws credential checking ONLY FOR YOUR OWN credentials if they're set in a configurable credentials file (AWS CLI tools' native format) --- README.md | 1 + hooks.yaml | 6 ++ pre_commit_hooks/detect_aws_credentials.py | 65 ++++++++++++++++++++++ setup.py | 1 + 4 files changed, 73 insertions(+) create mode 100644 pre_commit_hooks/detect_aws_credentials.py diff --git a/README.md b/README.md index 7a919e6..6795320 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Add this to your `.pre-commit-config.yaml` - `check-xml` - Attempts to load all xml files to verify syntax. - `check-yaml` - Attempts to load all yaml files to verify syntax. - `debug-statements` - Check for pdb / ipdb / pudb statements in code. +- `detect-aws-credentials` - Checks for the existence of aws access keys and secrets that you have set up with the AWS cli. - `detect-private-key` - Checks for the existence of private keys. - `double-quote-string-fixer` - This hook replaces double quoted strings with single quoted strings. diff --git a/hooks.yaml b/hooks.yaml index 13fef85..3bac5ae 100644 --- a/hooks.yaml +++ b/hooks.yaml @@ -56,6 +56,12 @@ entry: debug-statement-hook language: python files: \.py$ +- id: detect-aws-credentials + name: Detect AWS Credentials + description: Detects *your* aws credentials from the aws cli credentials file + entry: detect-aws-credentials + language: python + files: '' - id: detect-private-key name: Detect Private Key description: Detects the presence of private keys diff --git a/pre_commit_hooks/detect_aws_credentials.py b/pre_commit_hooks/detect_aws_credentials.py new file mode 100644 index 0000000..77c1991 --- /dev/null +++ b/pre_commit_hooks/detect_aws_credentials.py @@ -0,0 +1,65 @@ +from __future__ import print_function +from __future__ import unicode_literals + +import argparse +import ConfigParser +import os + + +def get_your_keys(credentials_file, ignore_access_key=False): + """ reads the keys in your credentials file in order to be able to look + for them in the submitted code. + """ + aws_credentials_file_path = os.path.expanduser(credentials_file) + if not os.path.exists(aws_credentials_file_path): + exit(2) + + parser = ConfigParser.ConfigParser() + parser.read(aws_credentials_file_path) + + keys = set() + for section in parser.sections(): + if not ignore_access_key: + keys.add(parser.get(section, 'aws_access_key_id')) + keys.add(parser.get(section, 'aws_secret_access_key')) + return keys + + +def check_file_for_aws_keys(filename, keys): + with open(filename, 'r') as content: + # naively match the entire file, chances be so slim + # of random characters matching your flipping key. + for line in content: + if any(key in line for key in keys): + return 1 + return 0 + + +def main(argv=None): + parser = argparse.ArgumentParser() + parser.add_argument('filenames', nargs='*', help='Filenames to run') + parser.add_argument( + "--credentials-file", + default='~/.aws/credentials', + help="location of aws credentials file from which to get the keys " + "we're looking for", + ) + parser.add_argument( + "--ignore-access-key", + action='store_true', + help="if you would like to ignore access keys, as there is " + "occasionally legitimate use for these.", + ) + args = parser.parse_args(argv) + ignore_access_key = args.ignore_access_key + keys = get_your_keys(args.credentials_file, + ignore_access_key=ignore_access_key) + + retv = 0 + for filename in args.filenames: + retv |= check_file_for_aws_keys(filename, keys) + return retv + + +if __name__ == '__main__': + exit(main()) diff --git a/setup.py b/setup.py index b24c05e..3050118 100644 --- a/setup.py +++ b/setup.py @@ -44,6 +44,7 @@ setup( 'check-xml = pre_commit_hooks.check_xml:check_xml', 'check-yaml = pre_commit_hooks.check_yaml:check_yaml', 'debug-statement-hook = pre_commit_hooks.debug_statement_hook:debug_statement_hook', + 'detect-aws-credentials = pre_commit_hooks.detect_aws_credentials:main', 'detect-private-key = pre_commit_hooks.detect_private_key:detect_private_key', 'end-of-file-fixer = pre_commit_hooks.end_of_file_fixer:end_of_file_fixer', 'name-tests-test = pre_commit_hooks.tests_should_end_in_test:validate_files', From 3078aec57b6ea803178a093eedaea0bac4d21864 Mon Sep 17 00:00:00 2001 From: Ara Hayrabedian Date: Fri, 12 Jun 2015 16:24:01 +0400 Subject: [PATCH 2/7] use six for config parser, add to reqs ditch checkign access_key (don't consider these a secret) don't check line by line, check the whole file in bulk instead --- pre_commit_hooks/detect_aws_credentials.py | 31 ++++++++-------------- requirements-dev.txt | 1 + 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/pre_commit_hooks/detect_aws_credentials.py b/pre_commit_hooks/detect_aws_credentials.py index 77c1991..19b2316 100644 --- a/pre_commit_hooks/detect_aws_credentials.py +++ b/pre_commit_hooks/detect_aws_credentials.py @@ -2,26 +2,25 @@ from __future__ import print_function from __future__ import unicode_literals import argparse -import ConfigParser import os +from six.moves import configparser -def get_your_keys(credentials_file, ignore_access_key=False): - """ reads the keys in your credentials file in order to be able to look +def get_your_keys(credentials_file): + """ reads the secret keys in your credentials file in order to be able to look for them in the submitted code. """ aws_credentials_file_path = os.path.expanduser(credentials_file) if not os.path.exists(aws_credentials_file_path): exit(2) - parser = ConfigParser.ConfigParser() + parser = configparser.ConfigParser() parser.read(aws_credentials_file_path) keys = set() for section in parser.sections(): - if not ignore_access_key: - keys.add(parser.get(section, 'aws_access_key_id')) keys.add(parser.get(section, 'aws_secret_access_key')) + print(str(keys)) return keys @@ -29,9 +28,9 @@ def check_file_for_aws_keys(filename, keys): with open(filename, 'r') as content: # naively match the entire file, chances be so slim # of random characters matching your flipping key. - for line in content: - if any(key in line for key in keys): - return 1 + text_body = content.read() + if any(key in text_body for key in keys): + return 1 return 0 @@ -41,19 +40,11 @@ def main(argv=None): parser.add_argument( "--credentials-file", default='~/.aws/credentials', - help="location of aws credentials file from which to get the keys " - "we're looking for", - ) - parser.add_argument( - "--ignore-access-key", - action='store_true', - help="if you would like to ignore access keys, as there is " - "occasionally legitimate use for these.", + help="location of aws credentials file from which to get the secret " + "keys we're looking for", ) args = parser.parse_args(argv) - ignore_access_key = args.ignore_access_key - keys = get_your_keys(args.credentials_file, - ignore_access_key=ignore_access_key) + keys = get_your_keys(args.credentials_file) retv = 0 for filename in args.filenames: diff --git a/requirements-dev.txt b/requirements-dev.txt index 97343d5..a7dac81 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,3 +7,4 @@ mock pre-commit pylint<1.4 pytest +six==1.9.0 From 88725503c4adfd6876a6b275f7f157fcb78effe9 Mon Sep 17 00:00:00 2001 From: Ara Hayrabedian Date: Fri, 12 Jun 2015 17:02:06 +0400 Subject: [PATCH 3/7] remove print statement --- pre_commit_hooks/detect_aws_credentials.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pre_commit_hooks/detect_aws_credentials.py b/pre_commit_hooks/detect_aws_credentials.py index 19b2316..10642af 100644 --- a/pre_commit_hooks/detect_aws_credentials.py +++ b/pre_commit_hooks/detect_aws_credentials.py @@ -20,7 +20,6 @@ def get_your_keys(credentials_file): keys = set() for section in parser.sections(): keys.add(parser.get(section, 'aws_secret_access_key')) - print(str(keys)) return keys From 02e8bdc9d83d76d43c1d50fcaafac86f03f2ff36 Mon Sep 17 00:00:00 2001 From: Ara Hayrabedian Date: Fri, 12 Jun 2015 19:20:56 +0400 Subject: [PATCH 4/7] add tests, test sample files and minor refactor of exit codes in actual hook in order to facilitate testing --- pre_commit_hooks/detect_aws_credentials.py | 7 +++-- testing/resources/nonsense.txt | 6 ++++ testing/resources/sample_aws_credentials | 10 +++++++ testing/resources/with_no_secrets.txt | 5 ++++ testing/resources/with_secrets.txt | 5 ++++ tests/detect_aws_credentials_test.py | 35 ++++++++++++++++++++++ 6 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 testing/resources/nonsense.txt create mode 100644 testing/resources/sample_aws_credentials create mode 100644 testing/resources/with_no_secrets.txt create mode 100644 testing/resources/with_secrets.txt create mode 100644 tests/detect_aws_credentials_test.py diff --git a/pre_commit_hooks/detect_aws_credentials.py b/pre_commit_hooks/detect_aws_credentials.py index 10642af..e28724a 100644 --- a/pre_commit_hooks/detect_aws_credentials.py +++ b/pre_commit_hooks/detect_aws_credentials.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import argparse import os + from six.moves import configparser @@ -12,7 +13,7 @@ def get_your_keys(credentials_file): """ aws_credentials_file_path = os.path.expanduser(credentials_file) if not os.path.exists(aws_credentials_file_path): - exit(2) + return None parser = configparser.ConfigParser() parser.read(aws_credentials_file_path) @@ -37,13 +38,15 @@ def main(argv=None): parser = argparse.ArgumentParser() parser.add_argument('filenames', nargs='*', help='Filenames to run') parser.add_argument( - "--credentials-file", + "--credentials-file", default='~/.aws/credentials', help="location of aws credentials file from which to get the secret " "keys we're looking for", ) args = parser.parse_args(argv) keys = get_your_keys(args.credentials_file) + if not keys: + return 2 retv = 0 for filename in args.filenames: diff --git a/testing/resources/nonsense.txt b/testing/resources/nonsense.txt new file mode 100644 index 0000000..6c7fe83 --- /dev/null +++ b/testing/resources/nonsense.txt @@ -0,0 +1,6 @@ +some nonsense text generated at https://baconipsum.com/ +Bacon ipsum dolor amet ipsum fugiat pastrami pork belly, non ball tip flank est short loin. Fatback landjaeger meatloaf flank. Sunt boudin duis occaecat mollit velit. Capicola lorem frankfurter doner strip steak jerky rump elit laborum mollit. Venison cupidatat laboris duis ut chuck proident mollit. Minim do rump, eu jerky ham turkey chuck in tempor venison pariatur voluptate landjaeger beef. + +Duis aliqua esse, exercitation in ball tip ut capicola sausage dolore frankfurter occaecat. Duis in nulla consequat salami. Est shoulder tempor commodo shankle short ribs. In meatball aliqua boudin tenderloin, meatloaf leberkas hamburger quis pig dolore ea eu. Ham hock ex laboris, filet mignon sunt doner cillum short loin prosciutto voluptate. + +Occaecat pork doner meatloaf nulla biltong ullamco tenderloin culpa brisket. Culpa jowl ea shank t-bone shankle voluptate nostrud incididunt leberkas pork loin. Bacon kevin jerky pork belly t-bone labore duis. Boudin corned beef adipisicing aute, fatback ribeye nulla pancetta anim venison. Short ribs kevin pastrami cow drumstick velit. Turkey exercitation jowl, fatback labore swine do voluptate. diff --git a/testing/resources/sample_aws_credentials b/testing/resources/sample_aws_credentials new file mode 100644 index 0000000..a79b021 --- /dev/null +++ b/testing/resources/sample_aws_credentials @@ -0,0 +1,10 @@ +# this is an aws credentials configuration file. obviously not real credentials :P +[default] +aws_access_key_id = AKIASLARTIBARTFAST11 +aws_secret_access_key = 7xebzorgm5143ouge9gvepxb2z70bsb2rtrh099e +[production] +aws_access_key_id = AKIAVOGONSVOGONS0042 +aws_secret_access_key = z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb +[staging] +aws_access_key_id = AKIAJIMMINYCRICKET0A +aws_secret_access_key = ixswosj8gz3wuik405jl9k3vdajsnxfhnpui38ez diff --git a/testing/resources/with_no_secrets.txt b/testing/resources/with_no_secrets.txt new file mode 100644 index 0000000..d9ab505 --- /dev/null +++ b/testing/resources/with_no_secrets.txt @@ -0,0 +1,5 @@ +# file with an access key but no secrets +# you'll notice it is a redacted section of sample_aws_credentials + +[production] +aws_access_key_id = AKIASLARTIBARTFAST11 diff --git a/testing/resources/with_secrets.txt b/testing/resources/with_secrets.txt new file mode 100644 index 0000000..0018225 --- /dev/null +++ b/testing/resources/with_secrets.txt @@ -0,0 +1,5 @@ +#file with a secret key, you'll notice it is a section of sample_aws_credentials + +[production] +aws_access_key_id = AKIAVOGONSVOGONS0042 +aws_secret_access_key = z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb diff --git a/tests/detect_aws_credentials_test.py b/tests/detect_aws_credentials_test.py new file mode 100644 index 0000000..495c574 --- /dev/null +++ b/tests/detect_aws_credentials_test.py @@ -0,0 +1,35 @@ +import pytest + +from pre_commit_hooks.detect_aws_credentials import main +from testing.util import get_resource_path + + +# Input filename, expected return value +TESTS = ( + ('with_no_secrets.txt', 0), + ('with_secrets.txt', 1), + ('nonsense.txt', 0), + ('ok_json.json', 0), +) + +NO_CREDENTIALS_TEST = ( + ('with_secrets.txt', 2), +) + + +@pytest.mark.parametrize(('filename', 'expected_retval'), TESTS) +def test_detect_aws_credentials(filename, expected_retval): + # with a valid credentials file + ret = main( + [get_resource_path(filename), "--credentials-file=testing/resources/sample_aws_credentials"] + ) + assert ret == expected_retval + + +@pytest.mark.parametrize(('filename', 'expected_retval'), NO_CREDENTIALS_TEST) +def test_non_existent_credentials(filename, expected_retval): + # with a non-existent credentials file + ret = main( + [get_resource_path(filename), "--credentials-file=testing/resources/credentailsfilethatdoesntexist"] + ) + assert ret == expected_retval From 255af75d1f5a2ee30a0abff79d2557e436e92cbd Mon Sep 17 00:00:00 2001 From: Ara Hayrabedian Date: Fri, 12 Jun 2015 19:21:14 +0400 Subject: [PATCH 5/7] add six to installation requirements --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 3050118..852d8e1 100644 --- a/setup.py +++ b/setup.py @@ -32,6 +32,7 @@ setup( 'autopep8>=1.1', 'pyyaml', 'simplejson', + 'six==1.9.0', ], entry_points={ 'console_scripts': [ From 974ef4e93cbc1f1e20cb2048ea2645f13faad34d Mon Sep 17 00:00:00 2001 From: Ara Hayrabedian Date: Sat, 13 Jun 2015 14:18:08 +0400 Subject: [PATCH 6/7] disable import checking for six.moves --- pre_commit_hooks/detect_aws_credentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pre_commit_hooks/detect_aws_credentials.py b/pre_commit_hooks/detect_aws_credentials.py index e28724a..55e83a1 100644 --- a/pre_commit_hooks/detect_aws_credentials.py +++ b/pre_commit_hooks/detect_aws_credentials.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import argparse import os -from six.moves import configparser +from six.moves import configparser # pylint: disable=import-error def get_your_keys(credentials_file): From 993c05be6558bb5457a9c40986e5935cb761b751 Mon Sep 17 00:00:00 2001 From: Ara Hayrabedian Date: Sat, 13 Jun 2015 16:32:30 +0400 Subject: [PATCH 7/7] update readme to reflect that we no longer check for access keys, only secrets --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6795320..a04454e 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Add this to your `.pre-commit-config.yaml` - `check-xml` - Attempts to load all xml files to verify syntax. - `check-yaml` - Attempts to load all yaml files to verify syntax. - `debug-statements` - Check for pdb / ipdb / pudb statements in code. -- `detect-aws-credentials` - Checks for the existence of aws access keys and secrets that you have set up with the AWS cli. +- `detect-aws-credentials` - Checks for the existence of AWS secrets that you have set up with the AWS CLI. - `detect-private-key` - Checks for the existence of private keys. - `double-quote-string-fixer` - This hook replaces double quoted strings with single quoted strings.