aws_recon 0.2.6 → 0.2.11

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2707134a4d4be23f0f1cd3320fda7e75463aae49113b2f4b802257aa02b0de52
4
- data.tar.gz: 85452e37a3657d315fae2b67852bc521786a95fc386db53f4a3212801e140ee4
3
+ metadata.gz: 342238f4c9197721172d05451ed5c3346475b52ae807f27607b83036d15280dd
4
+ data.tar.gz: 3f26ec0ad41491836331076bac849ab967db5640e9295413db30b21f1567a324
5
5
  SHA512:
6
- metadata.gz: 5e1a919375c34b51a72a3bb470ce178b39c178e8e3a569fb47f618c8ac122512e14c2564e42bd72aab3e5a550041f8e9490c3f3d6245ac2d3c933e77a9053d18
7
- data.tar.gz: e93eaad39853ffdd5c934572b2d9fb2add88efc0150bc48cbc3cf7cef542c25a98a2ebd1c92e6b64404dde122d86d8d2d38638f6bd5fde0e11974c2c52e5cdc1
6
+ metadata.gz: b1591c03b3d1608d9a4f38987ff2f7ffce879d685966214184eaa7e8bdc6bb5f39cc2e7e0a99235e48ee6f2b25d927189bc8f8873b693b212ff2671819bd641b
7
+ data.tar.gz: 407bb475473ba08694681a77a7c8212b4406b8587ba668dfdff0947f514c48fba065cfd5f94e08d8a5cc0567886af944a31c3d0518d1a15537bb8682049078b2
@@ -0,0 +1,34 @@
1
+ ARG RUBY_VERSION=2.6.6
2
+ FROM ruby:${RUBY_VERSION}-alpine
3
+
4
+ LABEL maintainer="Darkbit <info@darkbit.io>"
5
+
6
+ ARG USER=recon
7
+ ARG GEM=aws_recon
8
+ ARG VERSION=0.2.10
9
+ ARG BUNDLER_VERSION=2.1.4
10
+
11
+ # Install new Bundler version
12
+ RUN rm /usr/local/lib/ruby/gems/*/specifications/default/bundler-*.gemspec && \
13
+ gem uninstall bundler && \
14
+ gem install bundler -v ${BUNDLER_VERSION}
15
+
16
+ # Install gem
17
+ RUN gem install ${GEM} -v ${VERSION}
18
+
19
+ # Create non-root user
20
+ RUN addgroup -S ${USER} && \
21
+ adduser -S ${USER} \
22
+ -G ${USER} \
23
+ -s /bin/ash \
24
+ -h /${USER}
25
+
26
+ # Copy binstub
27
+ COPY binstub/${GEM} /usr/local/bundle/bin/
28
+ RUN chmod +x /usr/local/bundle/bin/${GEM}
29
+
30
+ # Switch user
31
+ USER ${USER}
32
+ WORKDIR /${USER}
33
+
34
+ CMD ["ash"]
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2020 Darkbit
3
+ Copyright (c) 2020 Darkbit, LLC
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -28,9 +28,10 @@ Gem::Specification.new do |spec|
28
28
 
29
29
  spec.add_development_dependency 'bundler', '~> 1.17'
30
30
  spec.add_development_dependency 'gem-release', '~> 2.1'
31
- spec.add_development_dependency 'rake', '>= 12.3.3'
31
+ spec.add_development_dependency 'rake', '~> 12.3'
32
32
  spec.add_development_dependency 'minitest', '~> 5.0'
33
33
  spec.add_development_dependency 'solargraph', '~> 0.39.11'
34
34
  spec.add_development_dependency 'rubocop', '~> 0.87.1'
35
35
  spec.add_development_dependency 'pry', '~> 0.13.1'
36
+ spec.add_development_dependency 'byebug', '~> 11.1'
36
37
  end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #
4
+ # Manually generated binstub
5
+ #
6
+
7
+ require "rubygems"
8
+ require "bundler/setup"
9
+
10
+ load Gem.bin_path("aws_recon", "aws_recon")
@@ -44,7 +44,7 @@ module AwsRecon
44
44
  #
45
45
  def collect(service, region)
46
46
  mapper = Object.const_get(service.name)
47
- resources = mapper.new(service.name, region, @options)
47
+ resources = mapper.new(@account_id, service.name, region, @options)
48
48
 
49
49
  collection = resources.collect.map do |resource|
50
50
  if @options.output_format == 'custom'
@@ -10,7 +10,10 @@ class IAM < Mapper
10
10
  # list_mfa_devices
11
11
  # list_ssh_public_keys
12
12
  #
13
- @client.get_account_authorization_details.each_with_index do |response, page|
13
+ opts = {
14
+ filter: %w[User Role Group LocalManagedPolicy AWSManagedPolicy]
15
+ }
16
+ @client.get_account_authorization_details(opts).each_with_index do |response, page|
14
17
  log(response.context.operation_name, page)
15
18
 
16
19
  # users
@@ -19,6 +22,14 @@ class IAM < Mapper
19
22
  struct.type = 'user'
20
23
  struct.mfa_devices = @client.list_mfa_devices({ user_name: user.user_name }).mfa_devices.map(&:to_h)
21
24
  struct.ssh_keys = @client.list_ssh_public_keys({ user_name: user.user_name }).ssh_public_keys.map(&:to_h)
25
+ struct.user_policy_list = if user.user_policy_list
26
+ user.user_policy_list.map do |p|
27
+ {
28
+ policy_name: p.policy_name,
29
+ policy_document: JSON.parse(CGI.unescape(p.policy_document))
30
+ }
31
+ end
32
+ end
22
33
 
23
34
  resources.push(struct.to_h)
24
35
  end
@@ -27,6 +38,14 @@ class IAM < Mapper
27
38
  response.group_detail_list.each do |group|
28
39
  struct = OpenStruct.new(group.to_h)
29
40
  struct.type = 'group'
41
+ struct.group_policy_list = if group.group_policy_list
42
+ group.group_policy_list.map do |p|
43
+ {
44
+ policy_name: p.policy_name,
45
+ policy_document: JSON.parse(CGI.unescape(p.policy_document))
46
+ }
47
+ end
48
+ end
30
49
 
31
50
  resources.push(struct.to_h)
32
51
  end
@@ -35,6 +54,15 @@ class IAM < Mapper
35
54
  response.role_detail_list.each do |role|
36
55
  struct = OpenStruct.new(role.to_h)
37
56
  struct.type = 'role'
57
+ struct.assume_role_policy_document = JSON.parse(CGI.unescape(role.assume_role_policy_document))
58
+ struct.role_policy_list = if role.role_policy_list
59
+ role.role_policy_list.map do |p|
60
+ {
61
+ policy_name: p.policy_name,
62
+ policy_document: JSON.parse(CGI.unescape(p.policy_document))
63
+ }
64
+ end
65
+ end
38
66
 
39
67
  resources.push(struct.to_h)
40
68
  end
@@ -43,6 +71,16 @@ class IAM < Mapper
43
71
  response.policies.each do |policy|
44
72
  struct = OpenStruct.new(policy.to_h)
45
73
  struct.type = 'policy'
74
+ struct.policy_version_list = if policy.policy_version_list
75
+ policy.policy_version_list.map do |p|
76
+ {
77
+ version_id: p.version_id,
78
+ document: JSON.parse(CGI.unescape(p.document)),
79
+ is_default_version: p.is_default_version,
80
+ create_date: p.create_date
81
+ }
82
+ end
83
+ end
46
84
 
47
85
  resources.push(struct.to_h)
48
86
  end
@@ -56,6 +94,7 @@ class IAM < Mapper
56
94
 
57
95
  struct = OpenStruct.new(response.password_policy.to_h)
58
96
  struct.type = 'password_policy'
97
+ struct.arn = "arn:aws:iam::#{@account}:account_password_policy/global"
59
98
 
60
99
  resources.push(struct.to_h)
61
100
  end
@@ -68,6 +107,7 @@ class IAM < Mapper
68
107
 
69
108
  struct = OpenStruct.new(response.summary_map)
70
109
  struct.type = 'account_summary'
110
+ struct.arn = "arn:aws:iam::#{@account}:account_summary/global"
71
111
 
72
112
  resources.push(struct.to_h)
73
113
  end
@@ -102,6 +142,24 @@ class IAM < Mapper
102
142
  end
103
143
  end
104
144
 
145
+ #
146
+ # generate_credential_report
147
+ #
148
+ unless @options.skip_credential_report
149
+ status = 'STARTED'
150
+ interval = 5
151
+
152
+ # wait for report to generate
153
+ while status != 'COMPLETE'
154
+ @client.generate_credential_report.each do |response|
155
+ log(response.context.operation_name)
156
+ status = response.state
157
+ end
158
+
159
+ sleep interval unless status == 'COMPLETE'
160
+ end
161
+ end
162
+
105
163
  #
106
164
  # get_credential_report
107
165
  #
@@ -111,6 +169,7 @@ class IAM < Mapper
111
169
 
112
170
  struct = OpenStruct.new
113
171
  struct.type = 'credential_report'
172
+ struct.arn = "arn:aws:iam::#{@account}:credential_report/global"
114
173
  struct.content = CSV.parse(response.content, headers: :first_row).map(&:to_h)
115
174
  struct.report_format = response.report_format
116
175
  struct.generated_time = response.generated_time
@@ -31,6 +31,21 @@ class Organizations < Mapper
31
31
  end
32
32
  end
33
33
 
34
+ #
35
+ # list_policies
36
+ #
37
+ @client.list_policies({ filter: 'SERVICE_CONTROL_POLICY' }).each_with_index do |response, page|
38
+ log(response.context.operation_name, page)
39
+
40
+ response.policies.each do |policy|
41
+ struct = OpenStruct.new(policy.to_h)
42
+ struct.type = 'service_control_policy'
43
+ struct.content = JSON.parse(CGI.unescape(@client.describe_policy({ policy_id: policy.id }).policy.content))
44
+
45
+ resources.push(struct.to_h)
46
+ end
47
+ end
48
+
34
49
  resources
35
50
  end
36
51
  end
@@ -13,7 +13,7 @@ class Shield < Mapper
13
13
 
14
14
  struct = OpenStruct.new(response.subscription.to_h)
15
15
  struct.type = 'subscription'
16
- struct.arn = "arn:aws:shield:#{@region}:#{account}:subscription"
16
+ struct.arn = "arn:aws:shield:#{@region}:#{@account}:subscription"
17
17
 
18
18
  resources.push(struct.to_h)
19
19
  end
@@ -26,7 +26,7 @@ class Shield < Mapper
26
26
 
27
27
  struct = OpenStruct.new
28
28
  struct.type = 'contact_list'
29
- struct.arn = "arn:aws:shield:#{@region}:#{account}:contact_list"
29
+ struct.arn = "arn:aws:shield:#{@region}:#{@account}:contact_list"
30
30
  struct.contacts = response.emergency_contact_list.map(&:to_h)
31
31
 
32
32
  resources.push(struct.to_h)
@@ -18,6 +18,7 @@ class SQS < Mapper
18
18
  struct = OpenStruct.new(@client.get_queue_attributes({ queue_url: queue, attribute_names: ['All'] }).attributes.to_h)
19
19
  struct.type = 'queue'
20
20
  struct.arn = struct.QueueArn
21
+ struct.Policy = JSON.parse(CGI.unescape(struct.Policy))
21
22
 
22
23
  resources.push(struct.to_h)
23
24
  end
@@ -8,7 +8,7 @@ class Formatter
8
8
  def custom(account_id, region, service, resource)
9
9
  {
10
10
  account: account_id,
11
- name: resource[:arn] || "#{account_id}_#{region}_#{service.name}_#{resource[:type]}",
11
+ name: resource[:arn],
12
12
  service: service.name,
13
13
  region: region,
14
14
  asset_type: resource[:type],
@@ -22,7 +22,8 @@ class Mapper
22
22
  # S3 (unless the bucket was created in another region)
23
23
  SINGLE_REGION_SERVICES = %w[route53domains s3 shield support organizations].freeze
24
24
 
25
- def initialize(service, region, options)
25
+ def initialize(account, service, region, options)
26
+ @account = account
26
27
  @service = service
27
28
  @region = region
28
29
  @options = options
@@ -17,6 +17,7 @@ class Parser
17
17
  :threads,
18
18
  :collect_user_data,
19
19
  :skip_slow,
20
+ :skip_credential_report,
20
21
  :stream_output,
21
22
  :verbose,
22
23
  :debug
@@ -45,6 +46,7 @@ class Parser
45
46
  false,
46
47
  false,
47
48
  false,
49
+ false,
48
50
  false
49
51
  )
50
52
 
@@ -115,6 +117,11 @@ class Parser
115
117
  args.skip_slow = true
116
118
  end
117
119
 
120
+ # skip generating IAM credential report
121
+ opts.on('-g', '--skip-credential-report', 'Skip generating IAM credential report (default: false)') do
122
+ args.skip_credential_report = true
123
+ end
124
+
118
125
  # stream output (forces JSON lines, doesn't output handled warnings or errors )
119
126
  opts.on('-j', '--stream-output', 'Stream JSON lines to stdout (default: false)') do
120
127
  args.output_file = nil
@@ -21,8 +21,6 @@
21
21
  alias: ec2
22
22
  - name: EKS
23
23
  alias: eks
24
- excluded_regions:
25
- - us-west-1
26
24
  - name: ECS
27
25
  alias: ecs
28
26
  - name: ElasticLoadBalancing
@@ -1,3 +1,3 @@
1
1
  module AwsRecon
2
- VERSION = "0.2.6"
2
+ VERSION = "0.2.11"
3
3
  end
data/readme.md CHANGED
@@ -26,18 +26,20 @@ Ruby 2.5.x or 2.6.x (developed and tested with 2.6.5)
26
26
 
27
27
  ### Installation
28
28
 
29
- Install the gem:
29
+ AWS Recon can be run locally by installing the Ruby gem, or via a Docker container.
30
+
31
+ To run locally, first install the gem:
30
32
 
31
33
  ```
32
34
  $ gem install aws_recon
33
- Fetching aws_recon-0.2.6.gem
35
+ Fetching aws_recon-0.2.8.gem
34
36
  Fetching aws-sdk-resources-3.76.0.gem
35
37
  Fetching aws-sdk-3.0.1.gem
36
38
  Fetching parallel-1.19.2.gem
37
39
  ...
38
40
  Successfully installed aws-sdk-3.0.1
39
41
  Successfully installed parallel-1.19.2
40
- Successfully installed aws_recon-0.2.6
42
+ Successfully installed aws_recon-0.2.8
41
43
  ```
42
44
 
43
45
  Or add it to your Gemfile using `bundle`:
@@ -49,9 +51,23 @@ Resolving dependencies...
49
51
  ...
50
52
  Using aws-sdk 3.0.1
51
53
  Using parallel 1.19.2
52
- Using aws_recon 0.2.2
54
+ Using aws_recon 0.2.8
55
+ ```
56
+
57
+ To run via a Docker a container, pass the necessary AWS credentials into the Docker `run` command. For example:
58
+
59
+ ```
60
+ $ docker run -t --rm \
61
+ -e AWS_REGION \
62
+ -e AWS_ACCESS_KEY_ID \
63
+ -e AWS_SECRET_ACCESS_KEY \
64
+ -e AWS_SESSION_TOKEN \
65
+ -v $(pwd)/output.json:/recon/output.json \
66
+ darkbitio/aws_recon:latest \
67
+ aws_recon -v -s EC2 -r global,us-east-1,us-east-2
53
68
  ```
54
69
 
70
+
55
71
  ## Usage
56
72
 
57
73
  AWS Recon will leverage any AWS credentials currently available to the environment it runs in. If you are collecting from multiple accounts, you may want to leverage something like [aws-vault](https://github.com/99designs/aws-vault) to manage different credentials.
@@ -66,6 +82,39 @@ Plain environment variables will work fine too.
66
82
  $ AWS_PROFILE=<profile> aws_recon
67
83
  ```
68
84
 
85
+ To run from a Docker container using `aws-vault` managed credentials (output to stdout):
86
+
87
+ ```
88
+ $ aws-vault exec <vault_profile> -- docker run -t --rm \
89
+ -e AWS_REGION \
90
+ -e AWS_ACCESS_KEY_ID \
91
+ -e AWS_SECRET_ACCESS_KEY \
92
+ -e AWS_SESSION_TOKEN \
93
+ darkbitio/aws_recon:latest \
94
+ aws_recon -j -s EC2 -r global,us-east-1,us-east-2
95
+ ```
96
+
97
+ To run from a Docker container using `aws-vault` managed credentials and output to a file, you will need to satisfy a couple of requirements. First, Docker needs access to bind mount the path you specify (or a parent path above). Second, you need to create an empty file to save the output into (e.g. `output.json`). This is because we are only mounting that one file into the Docker container at run time. For example:
98
+
99
+ Create an empty file.
100
+
101
+ ```
102
+ $ touch output.json
103
+ ```
104
+
105
+ Run the `aws_recon` container, specifying the output file.
106
+
107
+ ```
108
+ $ aws-vault exec <vault_profile> -- docker run -t --rm \
109
+ -e AWS_REGION \
110
+ -e AWS_ACCESS_KEY_ID \
111
+ -e AWS_SECRET_ACCESS_KEY \
112
+ -e AWS_SESSION_TOKEN \
113
+ -v $(pwd)/output.json:/recon/output.json \
114
+ darkbitio/aws_recon:latest \
115
+ aws_recon -s EC2 -v -r global,us-east-1,us-east-2
116
+ ```
117
+
69
118
  You may want to use the `-v` or `--verbose` flag initially to see status and activity while collection is running.
70
119
 
71
120
  In verbose mode, the console output will show:
@@ -109,6 +158,12 @@ $ AWS_PROFILE=<profile> aws_recon -s S3,EC2 -r global,us-east-1,us-east-2
109
158
  $ AWS_PROFILE=<profile> aws_recon --services S3,EC2 --regions global,us-east-1,us-east-2
110
159
  ```
111
160
 
161
+ Example [OpenCSPM](https://github.com/OpenCSPM/opencspm) formatted output.
162
+
163
+ ```
164
+ $ AWS_PROFILE=<profile> aws_recon -s S3,EC2 -r global,us-east-1,us-east-2 -f custom > output.json
165
+ ```
166
+
112
167
  #### Errors
113
168
 
114
169
  An exception will be raised on `AccessDeniedException` errors. This typically means your user/role doesn't have the necessary permissions to get/list/describe for that service. These exceptions are raised so troubleshooting access issues is easier.
@@ -135,7 +190,7 @@ Most users will want to limit collection to relevant services and regions. Runni
135
190
  ```
136
191
  $ aws_recon -h
137
192
 
138
- AWS Recon - AWS Inventory Collector (0.2.6)
193
+ AWS Recon - AWS Inventory Collector (0.2.8)
139
194
 
140
195
  Usage: aws_recon [options]
141
196
  -r, --regions [REGIONS] Regions to scan, separated by comma (default: all)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aws_recon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.6
4
+ version: 0.2.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Larsen
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-08-19 00:00:00.000000000 Z
12
+ date: 2020-11-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: aws-sdk
@@ -71,16 +71,16 @@ dependencies:
71
71
  name: rake
72
72
  requirement: !ruby/object:Gem::Requirement
73
73
  requirements:
74
- - - ">="
74
+ - - "~>"
75
75
  - !ruby/object:Gem::Version
76
- version: 12.3.3
76
+ version: '12.3'
77
77
  type: :development
78
78
  prerelease: false
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
- - - ">="
81
+ - - "~>"
82
82
  - !ruby/object:Gem::Version
83
- version: 12.3.3
83
+ version: '12.3'
84
84
  - !ruby/object:Gem::Dependency
85
85
  name: minitest
86
86
  requirement: !ruby/object:Gem::Requirement
@@ -137,6 +137,20 @@ dependencies:
137
137
  - - "~>"
138
138
  - !ruby/object:Gem::Version
139
139
  version: 0.13.1
140
+ - !ruby/object:Gem::Dependency
141
+ name: byebug
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - "~>"
145
+ - !ruby/object:Gem::Version
146
+ version: '11.1'
147
+ type: :development
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - "~>"
152
+ - !ruby/object:Gem::Version
153
+ version: '11.1'
140
154
  description: AWS Recon is a command line tool to collect resources from an Amazon
141
155
  Web Services (AWS) account. The tool outputs JSON suitable for processing with other
142
156
  tools.
@@ -152,6 +166,7 @@ files:
152
166
  - ".gitignore"
153
167
  - ".rubocop.yml"
154
168
  - ".travis.yml"
169
+ - Dockerfile
155
170
  - Gemfile
156
171
  - LICENSE.txt
157
172
  - Rakefile
@@ -159,6 +174,7 @@ files:
159
174
  - bin/aws_recon
160
175
  - bin/console
161
176
  - bin/setup
177
+ - binstub/aws_recon
162
178
  - lib/aws_recon.rb
163
179
  - lib/aws_recon/aws_recon.rb
164
180
  - lib/aws_recon/collectors.rb