aws_recon 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rubocop.yml +12 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +7 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +1000 -0
  9. data/LICENSE.txt +21 -0
  10. data/Rakefile +10 -0
  11. data/aws_recon.gemspec +36 -0
  12. data/bin/aws_recon +5 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/lib/aws_recon.rb +19 -0
  16. data/lib/aws_recon/aws_recon.rb +115 -0
  17. data/lib/aws_recon/collectors/acm.rb +32 -0
  18. data/lib/aws_recon/collectors/apigateway.rb +50 -0
  19. data/lib/aws_recon/collectors/apigatewayv2.rb +37 -0
  20. data/lib/aws_recon/collectors/athena.rb +28 -0
  21. data/lib/aws_recon/collectors/autoscaling.rb +35 -0
  22. data/lib/aws_recon/collectors/cloudformation.rb +29 -0
  23. data/lib/aws_recon/collectors/cloudfront.rb +28 -0
  24. data/lib/aws_recon/collectors/cloudtrail.rb +33 -0
  25. data/lib/aws_recon/collectors/cloudwatch.rb +33 -0
  26. data/lib/aws_recon/collectors/cloudwatchlogs.rb +36 -0
  27. data/lib/aws_recon/collectors/codebuild.rb +29 -0
  28. data/lib/aws_recon/collectors/codepipeline.rb +27 -0
  29. data/lib/aws_recon/collectors/collectors.rb +2 -0
  30. data/lib/aws_recon/collectors/configservice.rb +80 -0
  31. data/lib/aws_recon/collectors/directconnect.rb +25 -0
  32. data/lib/aws_recon/collectors/directyservice.rb +27 -0
  33. data/lib/aws_recon/collectors/dms.rb +25 -0
  34. data/lib/aws_recon/collectors/dynamodb.rb +26 -0
  35. data/lib/aws_recon/collectors/ec2.rb +257 -0
  36. data/lib/aws_recon/collectors/ecr.rb +39 -0
  37. data/lib/aws_recon/collectors/ecs.rb +40 -0
  38. data/lib/aws_recon/collectors/efs.rb +25 -0
  39. data/lib/aws_recon/collectors/eks.rb +36 -0
  40. data/lib/aws_recon/collectors/elasticloadbalancing.rb +41 -0
  41. data/lib/aws_recon/collectors/elasticloadbalancingv2.rb +63 -0
  42. data/lib/aws_recon/collectors/elasticsearch.rb +27 -0
  43. data/lib/aws_recon/collectors/firehose.rb +29 -0
  44. data/lib/aws_recon/collectors/guardduty.rb +33 -0
  45. data/lib/aws_recon/collectors/iam.rb +136 -0
  46. data/lib/aws_recon/collectors/kafka.rb +27 -0
  47. data/lib/aws_recon/collectors/kinesis.rb +26 -0
  48. data/lib/aws_recon/collectors/kms.rb +71 -0
  49. data/lib/aws_recon/collectors/lambda.rb +42 -0
  50. data/lib/aws_recon/collectors/lightsail.rb +38 -0
  51. data/lib/aws_recon/collectors/organizations.rb +36 -0
  52. data/lib/aws_recon/collectors/rds.rb +81 -0
  53. data/lib/aws_recon/collectors/redshift.rb +40 -0
  54. data/lib/aws_recon/collectors/route53.rb +28 -0
  55. data/lib/aws_recon/collectors/route53domains.rb +25 -0
  56. data/lib/aws_recon/collectors/s3.rb +80 -0
  57. data/lib/aws_recon/collectors/sagemaker.rb +25 -0
  58. data/lib/aws_recon/collectors/servicequotas.rb +44 -0
  59. data/lib/aws_recon/collectors/ses.rb +28 -0
  60. data/lib/aws_recon/collectors/shield.rb +67 -0
  61. data/lib/aws_recon/collectors/sns.rb +38 -0
  62. data/lib/aws_recon/collectors/sqs.rb +28 -0
  63. data/lib/aws_recon/collectors/ssm.rb +41 -0
  64. data/lib/aws_recon/collectors/support.rb +43 -0
  65. data/lib/aws_recon/collectors/transfer.rb +24 -0
  66. data/lib/aws_recon/collectors/wafv2.rb +49 -0
  67. data/lib/aws_recon/collectors/workspaces.rb +24 -0
  68. data/lib/aws_recon/collectors/xray.rb +19 -0
  69. data/lib/aws_recon/lib/formatter.rb +32 -0
  70. data/lib/aws_recon/lib/mapper.rb +69 -0
  71. data/lib/aws_recon/options.rb +141 -0
  72. data/lib/aws_recon/services.yaml +134 -0
  73. data/lib/aws_recon/version.rb +3 -0
  74. data/readme.md +226 -0
  75. data/readme_gem.md +39 -0
  76. metadata +245 -0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Darkbit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
data/aws_recon.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'aws_recon/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'aws_recon'
9
+ spec.version = AwsRecon::VERSION
10
+ spec.authors = ['Josh Larsen']
11
+ spec.required_ruby_version = '>= 2.5.0'
12
+ spec.summary = 'A multi-threaded AWS inventory collection tool.'
13
+ spec.description = spec.summary
14
+ spec.homepage = 'https://github.com/darkbitio/aws-recon'
15
+ spec.license = 'MIT'
16
+
17
+ # Specify which files should be added to the gem when it is released.
18
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ spec.bindir = 'bin'
23
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
24
+ spec.require_paths = ['lib']
25
+
26
+ spec.add_dependency 'aws-sdk', '~> 3.0'
27
+ spec.add_dependency 'parallel', '~> 1.19'
28
+
29
+ spec.add_development_dependency 'bundler', '~> 1.17'
30
+ spec.add_development_dependency 'gem-release', '~> 2.1'
31
+ spec.add_development_dependency 'rake', '~> 10.0'
32
+ spec.add_development_dependency 'minitest', '~> 5.0'
33
+ spec.add_development_dependency 'solargraph', '~> 0.39.11'
34
+ spec.add_development_dependency 'rubocop', '~> 0.87.1'
35
+ spec.add_development_dependency 'pry', '~> 0.13.1'
36
+ end
data/bin/aws_recon ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'aws_recon'
4
+
5
+ AwsRecon::CLI.new.start(ARGV)
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "aws_recon"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/lib/aws_recon.rb ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AwsRecon
4
+ end
5
+
6
+ require 'parallel'
7
+ require 'ostruct'
8
+ require 'optparse'
9
+ require 'yaml'
10
+ require 'csv'
11
+ require 'pry'
12
+ require 'aws-sdk'
13
+ require 'aws_recon/options.rb'
14
+ require 'aws_recon/lib/mapper.rb'
15
+ require 'aws_recon/lib/formatter.rb'
16
+ require 'aws_recon/collectors/collectors.rb'
17
+
18
+ require 'aws_recon/version'
19
+ require 'aws_recon/aws_recon'
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ SERVICES_CONFIG_FILE = File.join(File.dirname(__FILE__), 'services.yaml').freeze
4
+
5
+ module AwsRecon
6
+ class CLI
7
+ # parse options
8
+ @options = Parser.parse ARGV.length < 1 ? %w[-h] : ARGV
9
+
10
+ # timing
11
+ starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
12
+
13
+ # AWS account id
14
+ @account_id = Aws::STS::Client.new.get_caller_identity.account
15
+
16
+ # AWS services
17
+ aws_services = YAML.load(File.read(SERVICES_CONFIG_FILE), symbolize_names: true)
18
+
19
+ # User config services
20
+ if @options.config_file
21
+ user_config = YAML.load(File.read(@options.config_file), symbolize_names: true)
22
+
23
+ services = user_config[:services]
24
+ regions = user_config[:regions]
25
+ else
26
+ regions = @options.regions
27
+ services = @options.services
28
+ end
29
+
30
+ # collection
31
+ @resources = []
32
+
33
+ # formatter
34
+ @formatter = Formatter.new
35
+
36
+ unless @options.stream_output
37
+ puts "\nStarting collection with #{@options.threads} threads...\n"
38
+ end
39
+
40
+ #
41
+ # collector wrapper
42
+ #
43
+ def collect(service, region)
44
+ mapper = Object.const_get(service.name)
45
+ resources = mapper.new(service.name, region, @options)
46
+
47
+ collection = resources.collect.map do |resource|
48
+ if @options.output_format == 'custom'
49
+ @formatter.custom(@account_id, region, service, resource)
50
+ else
51
+ @formatter.aws(@account_id, region, service, resource)
52
+ end
53
+ end
54
+
55
+ # write resources to stdout
56
+ if @options.stream_output
57
+ collection.each do |item|
58
+ puts item.to_json
59
+ end
60
+ end
61
+
62
+ # add resources to resources array for output to file
63
+ @resources.concat(collection) if @options.output_file
64
+ end
65
+
66
+ #
67
+ # main wrapper
68
+ #
69
+ def start
70
+ #
71
+ # global services
72
+ #
73
+ aws_services.map { |x| OpenStruct.new(x) }.filter { |s| s.global }.each do |service|
74
+ # user included this service in the args
75
+ next unless services.include?(service.name)
76
+
77
+ # user did not exclude 'global'
78
+ next unless regions.include?('global')
79
+
80
+ collect(service, 'global')
81
+ end
82
+
83
+ #
84
+ # regional services
85
+ #
86
+ regions.filter { |x| x != 'global' }.each do |region|
87
+ Parallel.map(aws_services.map { |x| OpenStruct.new(x) }.filter { |s| !s.global }.each, in_threads: @options.threads) do |service|
88
+ # some services aren't available in some regions
89
+ skip_region = service&.excluded_regions&.include?(region)
90
+
91
+ # user included this region in the args
92
+ next unless regions.include?(region) && !skip_region
93
+
94
+ # user included this service in the args
95
+ next unless services.include?(service.name) || services.include?(service.alias) # rubocop:disable Layout/LineLength
96
+
97
+ collect(service, region)
98
+ end
99
+ end
100
+ rescue Interrupt # ctrl-c
101
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - starting
102
+
103
+ puts "\nStopped early after \x1b[32m#{elapsed.to_i}\x1b[0m seconds.\n"
104
+ ensure
105
+ # write output file
106
+ if @options.output_file
107
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - starting
108
+
109
+ puts "\nFinished in \x1b[32m#{elapsed.to_i}\x1b[0m seconds. Saving resources to \x1b[32m#{@options.output_file}\x1b[0m.\n\n"
110
+
111
+ File.write(@options.output_file, @resources.to_json)
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,32 @@
1
+ class ACM < Mapper
2
+ #
3
+ # Returns an array of resources.
4
+ #
5
+ def collect
6
+ resources = []
7
+
8
+ #
9
+ # list_certificates
10
+ #
11
+ @client.list_certificates.each_with_index do |response, page|
12
+ log(response.context.operation_name, page)
13
+
14
+ response.certificate_summary_list.each do |cert|
15
+ log(response.context.operation_name, cert.domain_name, page)
16
+
17
+ struct = OpenStruct.new(cert.to_h)
18
+ struct.type = 'certificate'
19
+ struct.arn = cert.certificate_arn
20
+
21
+ # describe_certificate
22
+ struct.details = @client
23
+ .describe_certificate({ certificate_arn: cert.certificate_arn })
24
+ .certificate.to_h
25
+
26
+ resources.push(struct.to_h)
27
+ end
28
+ end
29
+
30
+ resources
31
+ end
32
+ end
@@ -0,0 +1,50 @@
1
+ class APIGateway < Mapper
2
+ #
3
+ # Returns an array of resources.
4
+ #
5
+ def collect
6
+ resources = []
7
+
8
+ #
9
+ # get_rest_apis
10
+ #
11
+ @client.get_rest_apis.each_with_index do |response, page|
12
+ log(response.context.operation_name, page)
13
+
14
+ response.items.each do |api|
15
+ struct = OpenStruct.new(api.to_h)
16
+ struct.type = 'api'
17
+ struct.arn = api.id
18
+
19
+ # get_authorizers
20
+ struct.authorizers = @client.get_authorizers({ rest_api_id: api.id }).items.map(&:to_h)
21
+
22
+ # get_stages
23
+ struct.stages = @client.get_stages({ rest_api_id: api.id }).item.map(&:to_h)
24
+
25
+ # get_models
26
+ struct.models = @client.get_models({ rest_api_id: api.id }).items.map(&:to_h)
27
+
28
+ # get_resources
29
+ struct.resources = @client.get_resources({ rest_api_id: api.id }).items.map(&:to_h)
30
+
31
+ resources.push(struct.to_h)
32
+ end
33
+ end
34
+
35
+ # get_domain_names
36
+ @client.get_domain_names.each_with_index do |response, page|
37
+ log(response.context.operation_name, page)
38
+
39
+ response.items.each do |domain|
40
+ struct = OpenStruct.new(domain.to_h)
41
+ struct.type = 'domain'
42
+ struct.arn = domain.domain_name
43
+
44
+ resources.push(struct.to_h)
45
+ end
46
+ end
47
+
48
+ resources
49
+ end
50
+ end
@@ -0,0 +1,37 @@
1
+ class ApiGatewayV2 < Mapper
2
+ #
3
+ # Returns an array of resources.
4
+ #
5
+ def collect
6
+ resources = []
7
+
8
+ #
9
+ # get_apis
10
+ #
11
+ @client.get_apis.each_with_index do |response, page|
12
+ log(response.context.operation_name, page)
13
+
14
+ response.items.each do |api|
15
+ struct = OpenStruct.new(api.to_h)
16
+ struct.type = 'api'
17
+ struct.arn = api.api_id
18
+
19
+ # get_authorizers
20
+ struct.authorizers = @client.get_authorizers({ api_id: api.api_id }).items.map(&:to_h)
21
+
22
+ # get_stages
23
+ struct.stages = @client.get_stages({ api_id: api.api_id }).items.map(&:to_h)
24
+
25
+ # get_models
26
+ struct.models = @client.get_models({ api_id: api.api_id }).items.map(&:to_h)
27
+
28
+ # get_deployments
29
+ struct.deployments = @client.get_deployments({ api_id: api.api_id }).items.map(&:to_h)
30
+
31
+ resources.push(struct.to_h)
32
+ end
33
+ end
34
+
35
+ resources
36
+ end
37
+ end
@@ -0,0 +1,28 @@
1
+ class Athena < Mapper
2
+ #
3
+ # Returns an array of resources.
4
+ #
5
+ def collect
6
+ resources = []
7
+
8
+ #
9
+ # list_work_groups
10
+ #
11
+ @client.list_work_groups.each_with_index do |response, page|
12
+ log(response.context.operation_name, page)
13
+
14
+ response.work_groups.each do |workgroup|
15
+ struct = OpenStruct.new(workgroup.to_h)
16
+ struct.type = 'workgroup'
17
+ struct.arn = "arn:aws:athena:#{@region}::workgroup/#{workgroup.name}"
18
+
19
+ # get_work_group
20
+ struct.details = @client.get_work_group({ work_group: workgroup.name }).to_h
21
+
22
+ resources.push(struct.to_h)
23
+ end
24
+ end
25
+
26
+ resources
27
+ end
28
+ end
@@ -0,0 +1,35 @@
1
+ class AutoScaling < Mapper
2
+ #
3
+ # Returns an array of resources.
4
+ #
5
+ def collect
6
+ resources = []
7
+
8
+ #
9
+ # describe_auto_scaling_groups
10
+ #
11
+ @client.describe_auto_scaling_groups.each_with_index do |response, page|
12
+ log(response.context.operation_name, page)
13
+
14
+ response.auto_scaling_groups.each do |asg|
15
+ struct = OpenStruct.new(asg.to_h)
16
+ struct.type = 'auto_scaling_group'
17
+ struct.arn = asg.auto_scaling_group_arn
18
+ struct.policies = []
19
+
20
+ # describe_policies
21
+ @client.describe_policies({ auto_scaling_group_name: asg.auto_scaling_group_name }).each_with_index do |response, page|
22
+ log(response.context.operation_name, page)
23
+
24
+ response.scaling_policies.each do |policy|
25
+ struct.policies.push(policy.to_h)
26
+ end
27
+ end
28
+
29
+ resources.push(struct.to_h)
30
+ end
31
+ end
32
+
33
+ resources
34
+ end
35
+ end
@@ -0,0 +1,29 @@
1
+ class CloudFormation < Mapper
2
+ #
3
+ # Returns an array of resources.
4
+ #
5
+ def collect
6
+ resources = []
7
+
8
+ #
9
+ # describe_stacks
10
+ #
11
+ @client.describe_stacks.each_with_index do |response, page|
12
+ log(response.context.operation_name, page)
13
+
14
+ response.stacks.each do |stack|
15
+ struct = OpenStruct.new(stack.to_h)
16
+ struct.type = 'stack'
17
+ struct.arn = stack.stack_id
18
+
19
+ # get_template
20
+ struct.tempate = @client.get_template({ stack_name: stack.stack_name }).to_h
21
+ log(response.context.operation_name, 'get_template', stack.stack_name)
22
+
23
+ resources.push(struct.to_h)
24
+ end
25
+ end
26
+
27
+ resources
28
+ end
29
+ end