Class: Setup

Inherits:
Object
  • Object
show all
Defined in:
lib/cht/aws.rb,
lib/cht/output.rb,
lib/cht/policy.rb,
lib/cht/mechanize.rb,
lib/cloudhealth-setup.rb,
lib/cht/error_handling.rb

Constant Summary collapse

AWS_LOGIN_URL =
'https://www.amazon.com/ap/signin?openid.assoc_handle=aws&openid.return_to=https%3A%2F%2Fportal.aws.amazon.com%2Fgp%2Faws%2Fdeveloper%2Faccount%2Findex.html%3Fie%3DUTF8%26action%3Dactivity-summary&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&action=&disableCorpSignUp=&clientContext=&marketPlaceId=&poolName=&authCookies=&pageId=aws.ssop&siteState=&accountStatusPolicy=&sso=&openid.pape.preferred_auth_policies=MultifactorPhysical&openid.pape.max_auth_age=3600&openid.ns.pape=http%3A%2F%2Fspecs.openid.net%2Fextensions%2Fpape%2F1.0&server=%2Fap%2Fsignin%3Fie%3DUTF8&accountPoolAlias='

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ Setup

Returns a new instance of Setup.



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/cloudhealth-setup.rb', line 128

def initialize(options)
  manual_input = ensure_options(options)
  options.merge!(manual_input)
  @aws_key = options[:aws_key]
  @aws_secret = options[:aws_secret]
  @output_file = options[:output_file]
  @input_file = options[:input_file]
  @aws_user = options[:aws_user]
  @aws_pass = options[:aws_pass]
  @setup_bucket = options[:setup_bucket]
  @aws_ro_name = options[:aws_ro_name]
  @aws_acct_alias = options[:aws_acct_alias]
  @aws_account_id = nil
  @verbose = options[:verbose]
  @overwrite_file = options[:overwrite_file]
  @ro_user_exists = options[:ro_user_exists]
  @mfa = options[:multi_factor_code] || nil
  @created_account = {}
  @iam = iam
  @s3 = s3
  @browser = mech_browser
  @mode = ARGV[0]
end

Instance Attribute Details

#account_nameObject

Returns the value of attribute account_name.



126
127
128
# File 'lib/cloudhealth-setup.rb', line 126

def 
  @account_name
end

#aws_account_idObject

Returns the value of attribute aws_account_id.



126
127
128
# File 'lib/cloudhealth-setup.rb', line 126

def 
  @aws_account_id
end

#aws_acct_aliasObject

Returns the value of attribute aws_acct_alias.



126
127
128
# File 'lib/cloudhealth-setup.rb', line 126

def aws_acct_alias
  @aws_acct_alias
end

#aws_keyObject

Returns the value of attribute aws_key.



126
127
128
# File 'lib/cloudhealth-setup.rb', line 126

def aws_key
  @aws_key
end

#aws_passObject

Returns the value of attribute aws_pass.



126
127
128
# File 'lib/cloudhealth-setup.rb', line 126

def aws_pass
  @aws_pass
end

#aws_ro_nameObject

Returns the value of attribute aws_ro_name.



126
127
128
# File 'lib/cloudhealth-setup.rb', line 126

def aws_ro_name
  @aws_ro_name
end

#aws_secretObject

Returns the value of attribute aws_secret.



126
127
128
# File 'lib/cloudhealth-setup.rb', line 126

def aws_secret
  @aws_secret
end

#aws_urlObject

Returns the value of attribute aws_url.



126
127
128
# File 'lib/cloudhealth-setup.rb', line 126

def aws_url
  @aws_url
end

#aws_userObject

Returns the value of attribute aws_user.



126
127
128
# File 'lib/cloudhealth-setup.rb', line 126

def aws_user
  @aws_user
end

#input_fileObject

Returns the value of attribute input_file.



126
127
128
# File 'lib/cloudhealth-setup.rb', line 126

def input_file
  @input_file
end

#output_fileObject

Returns the value of attribute output_file.



126
127
128
# File 'lib/cloudhealth-setup.rb', line 126

def output_file
  @output_file
end

#setup_bucketObject

Returns the value of attribute setup_bucket.



126
127
128
# File 'lib/cloudhealth-setup.rb', line 126

def setup_bucket
  @setup_bucket
end

Class Method Details

.runObject



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/cloudhealth-setup.rb', line 196

def self.run
  cli = MyCLI.new
  cli.banner = "Usage: cloudhealth-setup test|install|uninstall (options)"
  cli.parse_options
  accounts_to_setup = []
  accounts_processed = []
  if ARGV[0].nil?
    puts cli.opt_parser
    exit
  end

  if cli.config[:input_file]
    puts "Starting CloudHealth setup in multi-account setup mode, using input file #{cli.config[:input_file]}"
    accounts_to_setup << CsvImport.import_file(cli.config[:input_file])
  else
    puts "Starting CloudHealth setup in single account mode."
    accounts_to_setup << cli.config
  end

  accounts_to_setup.each do ||
     = Setup.new()

    case ARGV[0]
    when "install"
      .check_iam_credentials
      .check_web_credentials
      .setup_monthly_report
      .setup_s3_bucket
      .setup_prog_access
      .setup_detailed_billing
      .setup_cost_alloc
      .setup_checkboxes
      .setup_ro_user
      .
      .
      accounts_processed << .response
    when "test"
      .test_monthly_report
      .test_s3_bucket
      .test_prog_access
      .test_detailed_billing
      .test_cost_alloc
      .test_checkboxes
      .test_ro_user
      .
      .test_consolidated
    when "uninstall"
      .uninstall_ro_user
    else
      puts cli.opt_parser
    end
  end
  Setup.write_csv(accounts_processed, cli.config[:output_file]) if ARGV[0] == "install"
end

.write_csv(accounts, filename) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/cht/output.rb', line 2

def self.write_csv(accounts, filename)
  file_exists = File.exists?(filename)
  mode = if file_exists
           if @overwrite_file
             puts "CSV Output file #{filename} exists! Overwriting the file per --overwrite."
             "wb"
           else
             puts "CSV Output file #{filename} exists! Appending to existing file."
             "ab"
           end
         else
           "wb"
         end

  count = 0
  CSV.open(filename, mode) do |csv|
    unless mode == "ab"
      csv << ["Account ID", "Console URL", "IAM Username", "IAM Password", "AWS Access Key", "AWS Access Secret", "S3 Bucket", "Is Consolidated?"]
    end
    accounts.each do ||
      count += 1
      csv << [[:account_id], [:account_url], [:user], [:user_pass], [:access_key], [:secret_key], [:s3_bucket], [:consolidated]]
    end
  end
  puts "Finished setting up #{count} account(s). CSV File path: #{filename}"
end

Instance Method Details

#account_consolidatedObject



107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/cht/mechanize.rb', line 107

def 
  page = @browser.get("https://portal.aws.amazon.com/gp/aws/developer/account?ie=UTF8&action=consolidated-billing")
  consolidated_link = page.link_with(:href => "https://portal.aws.amazon.com/gp/aws/developer/subscription/index.html?ie=UTF8&productCode=AWSCBill")
  if consolidated_link.nil?
    @created_account.merge!(:consolidated => true)
    puts "Account is on consolidated billing"
  else
    #We must be on consolidated billing
    @created_account.merge!(:consolidated => false)
    puts "Account is not on consolidated billing"
  end
end

#aws_ro_policyObject



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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/cht/policy.rb', line 45

def aws_ro_policy
  {
    "Statement" => [
      {
        "Effect" => "Allow",
        "Action" => [
          "aws-portal:ViewBilling",
          "aws-portal:ViewUsage",
          "autoscaling:Describe*",
          "cloudformation:ListStacks",
          "cloudformation:ListStackResources",
          "cloudformation:DescribeStacks",
          "cloudformation:DescribeStackEvents",
          "cloudformation:DescribeStackResources",
          "cloudformation:GetTemplate",
          "cloudfront:Get*",
          "cloudfront:List*",
          "cloudwatch:Describe*",
          "cloudwatch:Get*",
          "cloudwatch:List*",
          "dynamodb:DescribeTable",
          "dynamodb:ListTables",
          "ec2:Describe*",
          "elasticache:Describe*",
          "elasticbeanstalk:Check*",
          "elasticbeanstalk:Describe*",
          "elasticbeanstalk:List*",
          "elasticbeanstalk:RequestEnvironmentInfo",
          "elasticbeanstalk:RetrieveEnvironmentInfo",
          "elasticloadbalancing:Describe*",
          "elasticmapreduce:Describe*",
          "iam:List*",
          "iam:Get*",
          "redshift:Describe*",
          "route53:Get*",
          "route53:List*",
          "rds:Describe*",
          "rds:ListTagsForResource",
          "s3:List*",
          "sdb:GetAttributes",
          "sdb:List*",
          "sdb:Select*",
          "ses:Get*",
          "ses:List*",
          "sns:Get*",
          "sns:List*",
          "sqs:GetQueueAttributes",
          "sqs:ListQueues",
          "sqs:ReceiveMessage",
          "storagegateway:List*",
          "storagegateway:Describe*"
        ],
        "Resource" => "*"
      },
      {
        "Effect" => "Allow",
        "Action" => [ "s3:Get*","s3:List*" ],
        "Resource" => [
          "arn:aws:s3:::#{@setup_bucket}",
          "arn:aws:s3:::#{@setup_bucket}/*"
        ]
      }
    ]
  }
end

#bucket_has_policyObject



2
3
4
5
6
7
8
9
# File 'lib/cht/policy.rb', line 2

def bucket_has_policy
  begin
    @s3.get_bucket_policy(@setup_bucket)
    true
  rescue
    false
  end
end

#bucket_policyObject



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/cht/policy.rb', line 20

def bucket_policy
  { "Version" => "2008-10-17",
    "Id" => "Policy1335892530063",
    "Statement" => [
      {
        "Sid" => "Stmt1335892150622",
        "Effect" => "Allow",
        "Principal" => {
          "AWS" => "arn:aws:iam::386209384616:root"
        },
        "Action" => ["s3:GetBucketAcl", "s3:GetBucketPolicy"],
        "Resource" => "arn:aws:s3:::#{@setup_bucket}"
      },
      {
        "Sid" => "Stmt1335892526596",
        "Effect" => "Allow",
        "Principal" => {
          "AWS" => "arn:aws:iam::386209384616:root"
        },
        "Action" => ["s3:PutObject"],
        "Resource" => "arn:aws:s3:::#{@setup_bucket}/*"
      }
    ]
  }
end

#check_iam_credentialsObject



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/cht/aws.rb', line 21

def check_iam_credentials
  puts "Please wait, ensuring the provided credentials work."
  begin
    @iam.list_users
  rescue => e
    if @input_file.nil? || @input_file.empty?
      # Single account mode
      puts "Could not login to your account with the provided Amazon Web Services credentials. Please check your AWS Access Key and Secret Access Key and try again. If issue persists contact [email protected]."
      exit
    else
      # Multi account mode
      raise SetupFailed, "Could not login to your account with the provided Amazon Web Services credentials. Please check your AWS Access Key and Secret Access Key and try again. If issue persists contact [email protected]."
    end
  end
end

#check_web_credentialsObject



81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/cht/mechanize.rb', line 81

def check_web_credentials
  get_page
  get_page # We run this twice, only on the second login do we find our account id on the initial page. This primes the pump so to speak anyway for subsequent usage. TODO: Fix me
  if @aws_account_id.nil? || @aws_account_id.empty?
    if @input_file.nil? || @input_file.empty?
      # Single account mode
      puts "Could not login to your account with the provided Amazon Web Services credentials. Please check your provided email address and password and try again. If issue persists contact [email protected]."
      exit
    else
      # Multi account mode
      raise SetupFailed, "Could not login to your account with the provided Amazon Web Services credentials. Please check your provided email address and password and try again. If issue persists contact [email protected]."
    end
  end
end

#create_user_passwordObject



259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/cht/aws.rb', line 259

def create_user_password
  puts "    Setting user password"
  pw = SecureRandom.hex
  begin
    @iam.(@aws_ro_name, pw)
  rescue => e
    if e.response.status == 409
      puts "    User already had a password set..."
    else
      raise e
    end
  end
  pw
end

#critical_failure(message) ⇒ Object



5
6
7
8
# File 'lib/cht/error_handling.rb', line 5

def critical_failure(message)
  puts message
  #raise SetupFailed, message
end

#dump_page(page) ⇒ Object



120
121
122
123
124
125
126
# File 'lib/cht/mechanize.rb', line 120

def dump_page(page)
    no = rand(500)
    puts "Writing out page as #{no}.html"
    File.open("#{no}.html", 'w') do |file|
        file << page.body
    end
end

#ensure_options(input) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/cloudhealth-setup.rb', line 152

def ensure_options(input)
  # If things dont exist in the options that are required, or required in a combination.
  output_opts = {}

  if input[:aws_key].nil?
    output_opts[:aws_key] = ask("Input AWS Key: ") do |q|
      q.responses[:not_valid] = "You must enter a 20 char AWS Access Key ID"
      q.responses[:invalid_type] = "You must enter a 20 char AWS Access Key ID"
      q.validate = lambda {|p| p.length == 20 }
    end
  end

  if input[:aws_secret].nil?
    output_opts[:aws_secret] = ask("Input AWS Secret: ") do |q|
      q.responses[:not_valid] = "You must enter a 40 char AWS Access Secret Key"
      q.responses[:invalid_type] = "You must enter a 40 char AWS Access Secret Key"
      q.validate = lambda {|p| p.length == 40 }
    end
  end

  if input[:aws_user].nil?
    output_opts[:aws_user] = ask("Input AWS Email/Username: ") do |q|
      q.responses[:not_valid] = "You must enter a valid Email/Username"
      q.responses[:invalid_type] = "You must enter a valid Email/Username"
      q.validate = lambda {|p| p.length > 5 }
    end
  end

  if input[:aws_pass].nil?
    output_opts[:aws_pass] = ask("Input AWS Password: ") do |q|
      q.responses[:not_valid] = "You must enter a valid password"
      q.responses[:invalid_type] = "You must enter a valid password"
      q.validate = lambda {|p| p.length > 4 }
    end
  end

  if input[:setup_bucket].nil?
    output_opts[:setup_bucket] = ask("Input S3 Bucket name for billing: ")
  end

  output_opts

end

#get_pageObject



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
41
42
43
44
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
71
72
73
74
75
76
77
78
79
# File 'lib/cht/mechanize.rb', line 16

def get_page
  begin
    url = AWS_LOGIN_URL
    page = @browser.get(url)
    signed_in = page.link_with(:text => "Sign Out")

    if signed_in.nil? # Since @browser is shared, sometimes we are already logged in.
        form = page.form_with(:name => 'signIn')
        form['email'] = @aws_user
        form['password'] = @aws_pass
         = form.submit

        if .code.to_i != 200
          puts "The login page returned error code #{.code}"
          raise SetupFailed, "   Could not login to AWS Web Console, Amazon may be experiencing issues or the credentials you provided are incorrect. HTTP Code: #{.code}"
        end

      # A Captcha can be present if they failed to login too many times, Lets detect it here.
      captcha_form = .form_with(:id => 'ap_signin_form')
      unless captcha_form.nil?
        captcha_on = captcha_form.field_with(:id => 'ap_captcha_guess')
        unless captcha_on.nil?
          raise SetupFailed, "  Your account currently has a captcha on the login screen, This is most likely due to failed logins. Please login to your account at http://aws.amazon.com/ to stop the Captcha and then retry cloudhealth-setup."
        end
      end

      mfa_form = .form_with(:id => 'ap_signin_form')
      unless mfa_form.nil?
        if @mfa.nil?
          @mfa = ask("Multi-Factor Authentication detected, please enter 6 digit pin: ") do |q|
            q.responses[:not_valid] = "You must enter a 6 digit MFA pin."
            q.responses[:invalid_type] = "You must enter a 6 digit MFA pin."
            q.validate = lambda {|p| p.length == 6 }
          end
        end
        mfa_form['tokenCode'] = @mfa
        mfa_form.submit
      end
    end


     = page.search('//span[@class="txtxxsm"]/text()')
    unless .nil? || .size == 0 || @created_account[:account_id]
      firstw, secondw,  = .first.content.strip.split(" ")
       = .gsub("-","")
      puts "    Account ID for this account is: #{.to_s}" if @verbose
      @aws_account_id = 
      @created_account.merge!(:account_id => )
    end

    page = @browser.get("https://portal.aws.amazon.com/gp/aws/developer/account?ie=UTF8&action=billing-preferences")

    if page.code.to_i != 200
      raise SetupFailed, "  Could not login to AWS Web Console, Amazon may be experiencing issues or the credentials you provided are incorrect. HTTP Code: #{page.code}"
    end

    page
  rescue => e
    #TODO: Fix when multi input account works
    #raise SetupFailed, e
    puts e
    exit
  end
end

#iamObject



5
6
7
8
9
10
11
# File 'lib/cht/aws.rb', line 5

def iam
  begin
    Fog::AWS::IAM.new({:aws_access_key_id => @aws_key, :aws_secret_access_key => @aws_secret})
  rescue => e
    critical_failure("Failed to connect to Amazon AWS IAM: Please ensure your provided credentials are correct and you have internet access. #{e if @verbose}")
  end
end

#mech_browserObject



4
5
6
7
8
9
10
11
12
13
14
# File 'lib/cht/mechanize.rb', line 4

def mech_browser
  # Creating Mechanize object
  browser = Mechanize.new
  browser.verify_mode = OpenSSL::SSL::VERIFY_NONE
  browser.redirect_ok = true
  # Let's give some header here
  browser.request_headers['User-Agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4'
  browser.request_headers['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
  browser.request_headers['application/xml'] = '*/*'
  browser
end

#responseObject



29
30
31
# File 'lib/cht/output.rb', line 29

def response
  @created_account
end

#s3Object



13
14
15
16
17
18
19
# File 'lib/cht/aws.rb', line 13

def s3
  begin
    Fog::Storage.new({:provider => 'AWS', :aws_access_key_id => @aws_key, :aws_secret_access_key => @aws_secret})
  rescue => e
    critical_failure("Failed to connect to Amazon AWS S3: Please ensure your provided credentials are correct and you have internet access. #{e if @verbose}")
  end
end

#setup_account_aliasObject



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/cht/aws.rb', line 229

def 
  begin
    if @aws_acct_alias.nil?
      puts "AWS account alias not given, will use account id: #{@created_account[:account_id]} -- Skipping alias setup."
      @created_account.merge!(:account_alias => @created_account[:account_id],
                              :account_url => "https://#{@created_account[:account_id]}.signin.aws.amazon.com/")
    else
      puts "Setting up account alias... "
      begin
        @iam.(@aws_acct_alias)
        puts "    alias set to #{@aws_acct_alias}..."
      rescue => e
        if e.class == Fog::AWS::IAM::EntityAlreadyExists
          puts "    Account alias was already set."
        elsif e.response.status == 409
          puts "    Account alias was already set."
        else
          raise e
        end
      end
      @created_account.merge!(:account_alias => @aws_acct_alias,
                              :account_url => "https://#{@aws_acct_alias}.signin.aws.amazon.com/")
    end
  rescue => e
    puts "    We were unable to create an account alias."
    puts "    Please contact CloudHealth support at [email protected]"
    warning(e)
  end
end

#setup_checkboxesObject



369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/cht/mechanize.rb', line 369

def setup_checkboxes
  checkbox_setup_error = "    We were unable to enable account activity & usage reports.\n    This setting can be enabled manually under the Manage Your Account from the AWS account page."
  begin
    puts "Enabling account activity & usage reports..."
    page = @browser.get("https://portal.aws.amazon.com/gp/aws/manageYourAccount?action=updateIAMUserAccess&activateaa=1&activateur=1")
    json = JSON.parse(page.body)
    if json["error"] != "0"
      puts checkbox_setup_error
    else
      puts "    Activity & Usage reports enabled."
    end
  rescue => e
    puts checkbox_setup_error
    warning(e)
  end
end

#setup_cost_allocObject



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/cht/mechanize.rb', line 287

def setup_cost_alloc
  begin
    puts "Setting up cost allocation report... "
    page = get_page
    cost_alloc_form = page.form_with(:name=>'carOptInForm')
    car_enabled = cost_alloc_form.field_with(:name => "buttonOptionCAR")

    if car_enabled.value == "EnableCAR"
      puts "    Enabling cost allocation report... "
      cost_alloc_form.submit
    elsif car_enabled.value == "DisableCAR"
      puts "    Report already enabled... "
    end
    puts "    Report setup finished"
  rescue => e
    puts "    We were unable to enable cost allocation reports."
    puts "    This setting can be enabled manually under Billing Preferences from the AWS account page."
    warning(e)
  end
end

#setup_detailed_billingObject



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/cht/mechanize.rb', line 234

def setup_detailed_billing
  begin
    puts "Setting up detailed billing report..."
    page = get_page

    # This is the regular detailed billing report
    detailed_billing_form = page.form_with(:name=>'hourlyOptInForm')
    bill_enabled = detailed_billing_form.field_with(:name => "buttonOptionHourly")

    # This is the detailed billing report with tags and resources
    detailed_billing_tag_form = page.form_with(:name => 'hourlyWithResourcesAndTagsOptInForm')
    tag_bill_enabled = detailed_billing_tag_form.field_with(:name => "buttonOptionHourlyWithResourcesAndTags")

    if bill_enabled.value == "EnableHourly"
      puts "    Enabling detailed billing report... "
      detailed_billing_form.submit
    elsif bill_enabled.value == "DisableHourly"
      puts "    Detailed report already enabled... "
    end

    if tag_bill_enabled.value == "EnableHourlyWithResourcesAndTags"
      puts "    Enabling detailed billing report w/ tags & resources... "
      detailed_billing_tag_form.submit
    elsif tag_bill_enabled.value == "DisableHourlyWithResourcesAndTags"
      puts "    Detailed report w/ resources & tags already enabled... "
    end
    puts "    Report setup finished"

  rescue => e
    puts "    we were unable to enable detailed billing reports."
    puts "    This setting can be enabled manually under Billing Preferences from the AWS account page."
    warning(e)
  end
end

#setup_monthly_reportObject



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/cht/mechanize.rb', line 146

def setup_monthly_report
  begin
    puts "Setting up monthly report access... "
    page = get_page
    monthly_report_form = page.form_with(:name => "csvReportOptInForm")
    mrf_enabled = monthly_report_form.field_with(:name => "buttonOption")

    if mrf_enabled.value == "EnableCSVReport"
      puts "    Report not enabled, enabling... "
      monthly_report_form.submit
    elsif mrf_enabled.value == "CancelCSVReport"
      puts "    Report already enabled... "
    end
    puts "    Monthly report access setup complete."
  rescue => e
    puts "    We were unable to setup monthly report access."
    puts "    This setting can be enabled manually under Billing Preferences from the AWS Account page."
    warning(e)
  end
end

#setup_prog_accessObject



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/cht/mechanize.rb', line 185

def setup_prog_access
  begin
    puts "Setting up programmatic access to billing... "
    page = get_page
    prog_access_form = page.form_with(:name => "paOptInForm")
    paf_enabled = prog_access_form.field_with(:name => "existingS3BucketName")

    if paf_enabled.nil?
      puts "    Enabling access in bucket #{@setup_bucket}... "
      prog_access_form['s3BucketName'] = @setup_bucket
      prog_access_form.submit
    else
      puts "    Access already enabled..."
    end
    puts "    Setup of programmatic access to billing finished"
  rescue => e
    puts "    We were unable to setup programmatic access to your billing information."
    puts "    You can manually enable this by following these instructions: http://docs.aws.amazon.com/awsaccountbilling/latest/about/programaccess.html"
    warning(e)
  end
end

#setup_ro_userObject



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
# File 'lib/cht/aws.rb', line 107

def setup_ro_user
  user_created = nil
  begin
    puts "Setting up AWS Read only user... "
    if @aws_ro_name.nil?
      puts "    Name not specified -- Skipping."
    else
      if user_exists
        if @ro_user_exists
          puts "    User #{@aws_ro_name} exists... continuing due to --user-exists"
          puts "    Note: CSV Output file will NOT be complete. Since we did not create the aws read only user"
          puts "          You must fill in the blanks of the CSV if you plan on importing the CSV on the website."
        else
          #TODO This should not exit() in multi-account import mode, it should be raised up and caught. e.g. SetupFailed
          puts "User #{@aws_ro_name} exists already, If this was your intention please re-run with --user-exists and ensure you update the CSV manually or choose another username via -r <name>"
          exit
        end
      else
        puts "    Creating user... "
        user_create = @iam.create_user(@aws_ro_name)
        key_create = @iam.create_access_key('UserName' => @aws_ro_name)
        access_key = key_create.body['AccessKey']['AccessKeyId']
        secret_key = key_create.body['AccessKey']['SecretAccessKey']
        arn = user_create.body['User']['Arn']
        user_pass = create_user_password
        @created_account.merge!(:access_key => access_key, :secret_key => secret_key, :user_pass => user_pass, :user => @aws_ro_name)
        user_created = "    The user #{@aws_ro_name} has been created with password #{user_pass}, access key #{access_key}, and secret key #{secret_key}"
      end
      if user_has_policy
        puts "    User policy already exists..."
      else
        puts "    Creating user policy... "
        @iam.put_user_policy(@aws_ro_name, "CHTRoPolicy", aws_ro_policy)
      end
      puts user_created unless user_created.nil?
      puts "    Setup of AWS Read only user completed"

    end
  rescue => e
    puts "    We were unable to create a read only user."
    puts "    Please contact CloudHealth support at [email protected]."
    warning(e)
  end
end

#setup_s3_bucketObject



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/cht/aws.rb', line 172

def setup_s3_bucket
  begin
    puts "Setting up S3 billing bucket... "
    if @s3.directories.collect{|d| d.key}.include?(@setup_bucket)
      puts "    Bucket exists..."
    else
      puts "    Creating bucket... "
      begin
        @s3.directories.create(:key => @setup_bucket, :public => false)
      rescue => e
        if e.response.status == 409
          puts "    The bucket you are trying to use is already created, but owned by another account. Please use another bucket name."
        else
          raise e
        end
      end
    end

    if bucket_has_policy
      puts "    Bucket already has policy... "
    else
      puts "    Creating bucket policy..."
      @s3.put_bucket_policy(@setup_bucket, bucket_policy)
    end
    @created_account.merge!(:s3_bucket => @setup_bucket)
    puts "    Bucket setup finished"
  rescue => e
    puts "    We were unable to setup an S3 billing bucket. You can manually enable this,"
    puts "    by following these instructions: http://docs.aws.amazon.com/awsaccountbilling/latest/about/programaccess.html"
    warning(e)
  end
end

#test_account_aliasObject



214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/cht/aws.rb', line 214

def 
  begin
    acct_aliases = @iam..body['AccountAliases']
    if acct_aliases.empty?
      puts "[ ] AWS Account alias is not setup, Account ID: #{@aws_account_id} used instead -- Not setup"
    else
      puts "[X] AWS Account alias(es) are setup (#{acct_aliases.map{|aa| aa.strip}.join(', ')}), ID: #{@aws_account_id} -- Setup"
    end
  rescue => e
    puts "    We were unable to check for an account alias."
    puts "    Please contact CloudHealth support at [email protected]"
    warning(e)
  end
end

#test_checkboxesObject



308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
# File 'lib/cht/mechanize.rb', line 308

def test_checkboxes
  begin
    get_page
    page = @browser.get("https://portal.aws.amazon.com/gp/aws/manageYourAccount")
     = page.search('[@id="account_activity_checkbox"]')
    usage_report_search = page.search('[@id="usage_reports_checkbox"]')
    activate_button_search = page.search('[@id="activateIAMUserAccess"]')
    deactivate_button_search = page.search('[@id="deactivateIAMUserAccess"]')

    activate_hidden = begin
                        activate_button_search.first.attributes['style'].value.include?("display:none")
                      rescue
                        false
                      end

    deactivate_hidden = begin
                          deactivate_button_search.first.attributes['style'].value.include?("display:none")
                        rescue
                          false
                        end

    if activate_hidden
      #Activate button is hidden, Deactivate is shown
      if .first.attributes['checked'].nil?
        # Not checked
        puts "[ ] IAM access to Account Activity not setup -- Disabled"
      else
        # Checked
        puts "[X] IAM access to Account Activity is setup -- Enabled"
      end
      if usage_report_search.first.attributes['checked'].nil?
        # Not checked
        puts "[ ] IAM access to Usage Reports not setup -- Disabled"
      else
        # Checked
        puts "[X] IAM access to Usage Reports is setup -- Enabled"
      end
    elsif deactivate_hidden
      #Deactivate button is hidden, Activate shown
      if .first.attributes['checked'].nil?
        # Not checked
        puts "[X] IAM access to Account Activity is setup -- Enabled"
      else
        # Checked
        puts "[ ] IAM access to Account Activity not setup -- Disabled"
      end
      if usage_report_search.first.attributes['checked'].nil?
        # Not checked
        puts "[X] IAM access to Usage Reports is setup -- Enabled"
      else
        # Checked
        puts "[ ] IAM access to Usage Reports not setup -- Disabled"
      end
    else
      puts "[ ] Could not get status of IAM Usage report & Account activity checkboxes - Unknown"
    end
  rescue => e
    warning(e)
  end
end

#test_consolidatedObject



96
97
98
99
100
101
102
103
104
105
# File 'lib/cht/mechanize.rb', line 96

def test_consolidated
  page = @browser.get("https://portal.aws.amazon.com/gp/aws/developer/account?ie=UTF8&action=consolidated-billing")
  consolidated_link = page.link_with(:href => "https://portal.aws.amazon.com/gp/aws/developer/subscription/index.html?ie=UTF8&productCode=AWSCBill")
  if consolidated_link.nil?
    puts "[O] Account is setup on consolidated billing - Optional"
  else
    #We must be on consolidated billing
    puts "[O] Account is not on consolidated billing - Optional"
  end
end

#test_cost_allocObject



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/cht/mechanize.rb', line 269

def test_cost_alloc
  begin
    page = get_page
    cost_alloc_form = page.form_with(:name=>'carOptInForm')
    car_enabled = cost_alloc_form.field_with(:name => "buttonOptionCAR")

    if car_enabled.value == "EnableCAR"
      puts "[ ] Cost allocation report not setup -- Disabled"
    elsif car_enabled.value == "DisableCAR"
      puts "[X] Cost allocation report setup -- Enabled"
    end
  rescue => e
    puts "    We were unable to test cost allocation reports."
    puts "    This setting can be tested manually under Billing Preferences from the AWS account page."
    warning(e)
  end
end

#test_detailed_billingObject



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/cht/mechanize.rb', line 207

def test_detailed_billing
  begin
    page = get_page
    detailed_billing_form = page.form_with(:name=>'hourlyOptInForm')
    bill_enabled = detailed_billing_form.field_with(:name => "buttonOptionHourly")

    detailed_billing_tag_form = page.form_with(:name => 'hourlyWithResourcesAndTagsOptInForm')
    tag_bill_enabled = detailed_billing_tag_form = detailed_billing_tag_form.field_with(:name => "buttonOptionHourlyWithResourcesAndTags")

    if bill_enabled.value == "EnableHourly"
      puts "[ ] Detailed billing report not setup -- Disabled"
    elsif bill_enabled.value == "DisableHourly"
      puts "[X] Detailed billing report setup -- Enabled"
    end

    if tag_bill_enabled.value == "EnableHourlyWithResourcesAndTags"
      puts "[ ] Detailed billing report w/ tags & resources not setup -- Disabled"
    elsif tag_bill_enabled.value == "DisableHourlyWithResourcesAndTags"
      puts "[X] Detailed billing report w/ tags & resources setup -- Enabled"
    end
  rescue => e
    puts "    We were unable to test detailed billing reports."
    puts "    This setting can be enabled/tested manually under Billing Preferences from the AWS account page."
    warning(e)
  end
end

#test_monthly_reportObject



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/cht/mechanize.rb', line 128

def test_monthly_report
  begin
    page = get_page
    monthly_report_form = page.form_with(:name => "csvReportOptInForm")
    mrf_enabled = monthly_report_form.field_with(:name => "buttonOption")

    if mrf_enabled.value == "EnableCSVReport"
      puts "[ ] Monthly report -- Disabled"
    elsif mrf_enabled.value == "CancelCSVReport"
      puts "[X] Monthly report -- Enabled"
    end
  rescue => e
    puts "    We were unable to test if monthly report access is enabled."
    puts "    This setting can be checked manually under Billing Preferences from the AWS Account page."
    warning(e)
  end
end

#test_prog_accessObject



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/cht/mechanize.rb', line 167

def test_prog_access
  begin
    page = get_page
    prog_access_form = page.form_with(:name => "paOptInForm")
    paf_enabled = prog_access_form.field_with(:name => "existingS3BucketName")

    if paf_enabled.nil?
      puts "[ ] Programmatic access not setup -- Disabled"
    else
      puts "[X] Programmatic access is setup -- Enabled"
    end
  rescue => e
    puts "    We were unable to test programmatic access to your billing information."
    puts "    You can manually enable/test this by following these instructions: http://docs.aws.amazon.com/awsaccountbilling/latest/about/programaccess.html"
    warning(e)
  end
end

#test_ro_userObject



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/cht/aws.rb', line 37

def test_ro_user
  begin
    if user_exists
      puts "[X] AWS Read-Only user (#{@aws_ro_name}) is setup -- Exists"
    else
      puts "[ ] AWS Read-Only user (#{@aws_ro_name}) not setup -- No user"
    end
    if user_has_policy
      puts "[X] AWS Read-Only user has a policy -- Exists"
    else
      puts "[ ] AWS Read-Only user has no policy -- No policy"
    end
  rescue => e
    puts "    We were unable to test your read only user."
    puts "    Please contact CloudHealth support at [email protected]."
    warning(e)
  end
end

#test_s3_bucketObject



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/cht/aws.rb', line 152

def test_s3_bucket
  begin
    if @s3.directories.collect{|d| d.key}.include?(@setup_bucket)
      puts "[X] S3 Billing bucket (#{@setup_bucket}) -- Enabled"
    else
      puts "[ ] S3 Billing bucket (#{@setup_bucket}) -- Does not exist"
    end

    if bucket_has_policy
      puts "[X] S3 Billing bucket policy -- Exists"
    else
      puts "[ ] S3 Billing bucket policy -- No Policy"
    end
  rescue => e
    puts "    We were unable to test your S3 billing bucket. You can manually enable this,"
    puts "    by following these instructions: http://docs.aws.amazon.com/awsaccountbilling/latest/about/programaccess.html"
    warning(e)
  end
end

#uninstall_ro_userObject



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/cht/aws.rb', line 56

def uninstall_ro_user
  begin
    print "Delete Cloudhealth Read-only user #{@aws_ro_name},attached policies, profiles (y/n)? "
    k = get_character
    if k.chr == "y"
      puts "Deleting user #{@aws_ro_name} and all associated policies and login profiles..."
      user_policies = @iam.list_user_policies(@aws_ro_name)

      user_policies.body['PolicyNames'].each do |policy|
        puts "    Deleting user policy #{policy} attached to this user."
        @iam.delete_user_policy(@aws_ro_name, policy)
      end

      puts "    Deleting users access keys..."
      access_keys = @iam.list_access_keys('UserName' => @aws_ro_name).body

      access_keys['AccessKeys'].each do |access_key|
        puts "    Deleting access key #{access_key['AccessKeyId']}..."
        @iam.delete_access_key(access_key['AccessKeyId'], 'UserName' => @aws_ro_name)
      end

      begin
        puts "    Deleting login profile..."
        @iam.(@aws_ro_name)
      rescue => e
        puts "    User does not have a login profile, skipping."
        warning(e)
      end

      puts "    Deleting user...."
      @iam.delete_user(@aws_ro_name)
      puts "    IAM User #{@aws_ro_name} deleted."
    else
      puts "    You did not agree to delete the AWS Read only user #{aws_ro_name}."
    end
  rescue => e
    if defined? e.response
      if e.response.status == 409
        puts "    Could not delete user and/or login profile/policy, subordinate entities exist."
      elsif e.response.status == 404
        puts "    Could not delete user and/or login profile/policy, user/profile/policy does not exist."
      else
        puts "    Could not delete User or Policy/profile, unknown error: #{e.response.inspect}"
      end
    else
      puts "    Could not delete: #{e.message}"
      warning(e) if @verbose
    end
  end
end

#user_existsObject



205
206
207
208
209
210
211
212
# File 'lib/cht/aws.rb', line 205

def user_exists
  begin
    @iam.get_user(@aws_ro_name)
    true
  rescue
    false
  end
end

#user_has_policyObject



11
12
13
14
15
16
17
18
# File 'lib/cht/policy.rb', line 11

def user_has_policy
  begin
    @iam.get_user_policy("CHTRoPolicy", @aws_ro_name)
    true
  rescue
    false
  end
end

#warning(e) ⇒ Object



10
11
12
13
14
15
# File 'lib/cht/error_handling.rb', line 10

def warning(e)
  if @verbose
    puts e
    puts e.backtrace
  end
end