Class: AwsRecon::CLI

Inherits:
Object
  • Object
show all
Defined in:
lib/aws_recon/aws_recon.rb

Instance Method Summary collapse

Constructor Details

#initializeCLI

Returns a new instance of CLI.



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/aws_recon/aws_recon.rb', line 7

def initialize
  # parse options
  @options = Parser.parse ARGV.empty? ? %w[-h] : ARGV

  # timing
  @starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)

  # AWS account id
  @account_id = Aws::STS::Client.new.get_caller_identity.

  # AWS services
  @aws_services = YAML.safe_load(File.read(SERVICES_CONFIG_FILE), symbolize_names: true)

  # User config services
  if @options.config_file
    user_config = YAML.safe_load(File.read(@options.config_file), symbolize_names: true)

    @services = user_config[:services]
    @regions = user_config[:regions]
  else
    @regions = @options.regions
    @services = @options.services
  end

  # collection
  @resources = []

  # formatter
  @formatter = Formatter.new

  return if @options.stream_output

  puts "\nStarting collection with #{@options.threads} threads...\n"
end

Instance Method Details

#collect(service, region) ⇒ Object

collector wrapper



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/aws_recon/aws_recon.rb', line 45

def collect(service, region)
  mapper = Object.const_get(service.name)
  resources = mapper.new(@account_id, service.name, region, @options)

  collection = resources.collect.map do |resource|
    if @options.output_format == 'custom'
      @formatter.custom(@account_id, region, service, resource)
    else
      @formatter.aws(@account_id, region, service, resource)
    end
  end

  # write resources to stdout
  if @options.stream_output
    collection.each do |item|
      puts item.to_json
    end
  end

  # add resources to resources array for output to file
  @resources.concat(collection) if @options.output_file
rescue Aws::Errors::ServiceError => e
  raise if @options.quit_on_exception

  puts "Ignoring exception: '#{e.message}'\n"
end

#formatted_jsonObject

Format @resources as either JSON or JSONL



75
76
77
78
79
80
81
# File 'lib/aws_recon/aws_recon.rb', line 75

def formatted_json
  if @options.jsonl
    @resources.map { |r| JSON.generate(r) }.join("\n")
  else
    @resources.to_json
  end
end

#start(_args) ⇒ Object

main wrapper



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/aws_recon/aws_recon.rb', line 86

def start(_args)
  #
  # global services
  #
  @aws_services.map { |x| OpenStruct.new(x) }.filter(&:global).each do |service|
    # user included this service in the args
    next unless @services.include?(service.name)

    # user did not exclude 'global'
    next unless @regions.include?('global')

    collect(service, 'global')
  end

  #
  # regional services
  #
  @regions.filter { |x| x != 'global' }.each do |region|
    Parallel.map(@aws_services.map { |x| OpenStruct.new(x) }.filter { |s| !s.global }.each, in_threads: @options.threads) do |service|
      # some services aren't available in some regions
      skip_region = service&.excluded_regions&.include?(region)

      # user included this region in the args
      next unless @regions.include?(region) && !skip_region

      # user included this service in the args
      next unless @services.include?(service.name) || @services.include?(service.alias)

      collect(service, region)
    end
  end
rescue Interrupt # ctrl-c
  elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @starting

  puts "\nStopped early after #{elapsed.to_i} seconds.\n"
ensure
  elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @starting

  puts "\nFinished in #{elapsed.to_i} seconds.\n\n" unless @options.stream_output

  # write output file
  if @options.output_file && !@options.s3
    puts "Saving resources to #{@options.output_file}.\n\n"

    File.write(@options.output_file, formatted_json)
  end

  # write output file to S3 bucket
  if @options.s3
    t = Time.now.utc

    s3_full_object_path = "AWSRecon/#{t.year}/#{t.month}/#{t.day}/#{@account_id}_aws_recon_#{t.to_i}.json.gz"

    begin
      # get bucket name (and region if not us-east-1)
      s3_bucket, s3_region = @options.s3.split(':')

      # build IO object and gzip it
      io = StringIO.new
      gzip_data = Zlib::GzipWriter.new(io)
      gzip_data.write(formatted_json)
      gzip_data.close

      # send it to S3
      s3_client = Aws::S3::Client.new(region: s3_region || 'us-east-1')
      s3_resource = Aws::S3::Resource.new(client: s3_client)
      obj = s3_resource.bucket(s3_bucket).object(s3_full_object_path)
      obj.put(body: io.string)

      puts "Saving resources to S3 s3://#{s3_bucket}/#{s3_full_object_path}\n\n"
    rescue Aws::S3::Errors::ServiceError => e
      puts "Error! - could not save output S3 bucket\n\n"
      puts "#{e.message} - #{e.code}\n"
    end
  end
end