├── .gitignore ├── LICENSE ├── README.md ├── renovate.json └── s3scan.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.cfg 2 | 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Packages 9 | *.egg 10 | *.egg-info 11 | dist 12 | build 13 | eggs 14 | parts 15 | bin 16 | var 17 | sdist 18 | develop-eggs 19 | .installed.cfg 20 | lib 21 | lib64 22 | 23 | # Installer logs 24 | pip-log.txt 25 | 26 | # Unit test / coverage reports 27 | .coverage 28 | .tox 29 | nosetests.xml 30 | 31 | # Translations 32 | *.mo 33 | 34 | # Mr Developer 35 | .mr.developer.cfg 36 | .project 37 | .pydevproject 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Mike Taylor 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 24 | THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | s3scan 2 | ====== 3 | 4 | Generate a report of all s3 buckets and their permissions 5 | 6 | Usage 7 | ===== 8 | 9 | python s3scan.py [-f ] [-p ] 10 | 11 | Options: 12 | 13 | -f | --format Output format 14 | Default is 'text' 15 | [Optional] 16 | -p | --profile AWS Credentials Profile 17 | Default is None 18 | [Optional] 19 | 20 | Where the output format can be either 'text' or 'csv' 21 | 22 | Requirements 23 | ============ 24 | 25 | Python 2.6+ 26 | http://python.org 27 | 28 | Boto3 Library 29 | https://github.com/boto/boto3 30 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /s3scan.py: -------------------------------------------------------------------------------- 1 | """ 2 | :copyright: (c) 2012-2015 by Mike Taylor 3 | :license: BSD, see LICENSE for more details. 4 | 5 | Usage: 6 | 7 | python s3scan.py [-f ] 8 | 9 | Options: 10 | 11 | -f | --format Output format 12 | [Optional] 13 | 14 | Where the output format can be either 'text' or 'csv' 15 | """ 16 | 17 | VERSION = (0, 2, 0, '') 18 | 19 | __author__ = 'bear (Mike Taylor)' 20 | __contact__ = 'bear@bear.im' 21 | __copyright__ = 'Copyright 2012-2016, Mike Taylor' 22 | __license__ = 'BSD 2-Clause' 23 | __site__ = 'https://github.com/bear/s3scan' 24 | __version__ = u'.'.join(map(str, VERSION[0:3])) + u''.join(VERSION[3:]) 25 | 26 | 27 | from optparse import OptionParser 28 | import boto3 29 | 30 | def getConfig(): 31 | parser = OptionParser() 32 | # Read API Key & Secret from Environment... 33 | parser.add_option('-f', '--format', dest='format', default='text', help='Output format: text, csv') 34 | parser.add_option('-p', '--profile', dest='profile', default=None, help='AWS Profile') 35 | options, args = parser.parse_args() 36 | 37 | return options 38 | 39 | def discoverBuckets(profile=None): 40 | bs = boto3.session.Session(profile_name=profile) 41 | s3 = bs.client('s3', config=boto3.session.Config(signature_version='s3v4')) 42 | 43 | buckets = {} 44 | maxName = 0 45 | bucketList = s3.list_buckets() 46 | 47 | for b in bucketList['Buckets']: 48 | bucketName = b['Name'] 49 | buckets[bucketName] = {} 50 | 51 | #keep track of longest bucketName for textFormat 52 | if len(bucketName) > maxName: 53 | maxName = len(bucketName) 54 | 55 | grants = s3.get_bucket_acl(Bucket=bucketName) 56 | for grant in grants['Grants']: 57 | grantee_name = 'None' 58 | grantee_id = 'None' 59 | 60 | grantee = grant['Grantee'] 61 | if 'DisplayName' in grantee: 62 | grantee_name = grantee['DisplayName'] 63 | grantee_id = grantee['ID'] 64 | elif 'URI' in grantee: 65 | grantee_name = grantee['URI'].split('/')[-1] 66 | grantee_id = grantee['URI'] 67 | 68 | if grantee_name not in buckets[bucketName]: 69 | buckets[bucketName][grantee_name] = [] 70 | 71 | buckets[bucketName][grantee_name].append((grantee_id,grant['Permission'])) 72 | 73 | return buckets, maxName 74 | 75 | def csvFormat(bucket): 76 | reads = [] 77 | writes = [] 78 | reads_acp = [] 79 | writes_acp = [] 80 | 81 | for grantee in bucket: 82 | for grantee_id, permission in bucket[grantee]: 83 | if 'READ' == permission: 84 | reads.append(grantee) 85 | if 'WRITE' == permission: 86 | writes.append(grantee) 87 | if 'READ_ACP' == permission: 88 | reads_acp.append(grantee) 89 | if 'WRITE_ACP' == permission: 90 | writes_acp.append(grantee) 91 | if 'FULL_CONTROL' == permission: 92 | reads.append(grantee) 93 | writes.append(grantee) 94 | writes_acp.append(grantee) 95 | reads_acp.append(grantee) 96 | 97 | l = [key, 98 | ';'.join(writes), 99 | ';'.join(reads), 100 | ';'.join(writes_acp), 101 | ';'.join(reads_acp), 102 | ] 103 | 104 | return ','.join(l) 105 | 106 | def textFormat(bucket, maxName): 107 | reads = [] 108 | writes = [] 109 | reads_acp = [] 110 | writes_acp = [] 111 | 112 | # Adding full_control 113 | # see: http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#permissions 114 | for grantee in bucket: 115 | for grantee_id, permission in bucket[grantee]: 116 | if 'READ' == permission: 117 | reads.append(grantee) 118 | if 'WRITE' == permission: 119 | writes.append(grantee) 120 | if 'READ_ACP' == permission: 121 | reads_acp.append(grantee) 122 | if 'WRITE_ACP' == permission: 123 | writes_acp.append(grantee) 124 | if 'FULL_CONTROL' == permission: 125 | reads.append(grantee) 126 | writes.append(grantee) 127 | reads_acp.append(grantee) 128 | writes_acp.append(grantee) 129 | 130 | s = '{0:>{1}} --'.format(key, maxName) 131 | t = '\n' + ' '*(maxName + 4) 132 | if len(writes) > 0: 133 | s += ' Write: %s;' % ','.join(writes) 134 | if len(reads) > 0: 135 | s += ' Read: %s;' % ','.join(reads) 136 | if len(writes_acp) > 0: 137 | s += t + 'ACP Write: %s' % ','.join(writes_acp) 138 | if len(reads_acp) > 0: 139 | s += t + 'ACP Read: %s' % ','.join(reads_acp) 140 | 141 | return s 142 | 143 | if __name__ == '__main__': 144 | options = getConfig() 145 | 146 | buckets, maxName = discoverBuckets(options.profile) 147 | 148 | for key in buckets: 149 | bucket = buckets[key] 150 | 151 | if options.format == 'csv': 152 | print csvFormat(bucket) 153 | else: 154 | print textFormat(bucket, maxName) 155 | --------------------------------------------------------------------------------