|
1 | 1 | require 'yaml' |
| 2 | +require_relative 'restacker_config' |
2 | 3 |
|
3 | 4 | CREDS_FILE="#{CONFIG_DIR}/auth" |
4 | 5 |
|
5 | 6 | class Auth |
6 | 7 |
|
| 8 | + # TODO use keychain to save creds |
| 9 | + def self.login(options, config, location) |
| 10 | + auth_file = "#{CREDS_FILE}.#{location}" |
| 11 | + region = config.fetch(:region) |
| 12 | + profile_name = RestackerConfig.find_profile(options) |
| 13 | + username = RestackerConfig.find_user(options) |
| 14 | + |
| 15 | + # if no ctrl plane specified, authenticate directly |
| 16 | + return target_plane_auth(region, profile_name) if config[:ctrl].nil? |
| 17 | + |
| 18 | + if File.exists?(auth_file) |
| 19 | + session = YAML.load_file(auth_file) |
| 20 | + if session && valid_session?(region, session) |
| 21 | + create_auth_file(auth_file, session) |
| 22 | + return cloudformation_client(region, session) |
| 23 | + else # if session expired |
| 24 | + session = get_auth_session(profile_name, username, config) |
| 25 | + create_auth_file(auth_file, session) |
| 26 | + return cloudformation_client(region, session) |
| 27 | + end |
| 28 | + else # if file does not exist |
| 29 | + session = get_auth_session(profile_name, username, config) |
| 30 | + create_auth_file(auth_file, session) |
| 31 | + return cloudformation_client(region, session) |
| 32 | + end |
| 33 | + |
| 34 | + end |
| 35 | + |
| 36 | + private |
| 37 | + |
7 | 38 | def self.get_mfa_code |
8 | 39 | print Rainbow("Enter MFA: ").yellow |
9 | 40 | STDOUT.flush |
10 | 41 | STDIN.gets(7).chomp |
11 | 42 | end |
12 | 43 |
|
13 | | - def self.get_creds(username, defaults) |
14 | | - region = defaults.fetch(:region) |
15 | | - ctrl = defaults.fetch(:ctrl) |
16 | | - ctrl_account_number = ctrl.fetch(:account_number) |
17 | | - ctrl_role_prefix = ctrl.fetch(:role_prefix) |
18 | | - ctrl_role_name = ctrl.fetch(:role_name) |
19 | | - |
20 | | - target = defaults.fetch(:target) |
21 | | - target_account_number = target.fetch(:account_number) |
22 | | - target_role_prefix = target.fetch(:role_prefix) |
23 | | - target_role_name = target.fetch(:role_name) |
24 | | - target_label = target.fetch(:label) |
25 | | - serial_number = "arn:aws:iam::#{ctrl_account_number}:mfa/#{username}" |
26 | | - puts "Logging into #{Rainbow(target_label.upcase).yellow} using MFA: #{serial_number} (#{region})" |
27 | | - role_arn = "arn:aws:iam::#{ctrl_account_number}:role#{ctrl_role_prefix}#{ctrl_role_name}" |
| 44 | + def self.get_creds(username, config) |
| 45 | + region = config.fetch(:region) |
| 46 | + target = RestackerConfig.target_config(config) # target account will always exist in restacker.yml |
| 47 | + |
| 48 | + if config[:ctrl].nil? |
| 49 | + target_plane_auth(target) |
| 50 | + else |
| 51 | + ctrl = RestackerConfig.ctrl_config(config) |
| 52 | + control_plane_auth(ctrl, target, username, region) |
| 53 | + end |
| 54 | + end |
| 55 | + |
| 56 | + def self.control_plane_auth(ctrl, target, username, region) |
| 57 | + serial_number = "arn:aws:iam::#{ctrl[:account_number]}:mfa/#{username}" |
| 58 | + puts "Logging into #{Rainbow(target[:label].upcase).yellow} using MFA: #{serial_number} (#{region})" |
| 59 | + role_arn = "arn:aws:iam::#{ctrl[:account_number]}:role#{ctrl[:role_prefix]}#{ctrl[:role_name]}" |
28 | 60 | session_name = username[0..31] |
29 | 61 |
|
30 | 62 | sts_client = Aws::STS::Client.new(region: region) |
31 | | - sts_role = sts_client.assume_role(role_arn: role_arn, role_session_name: session_name, serial_number: serial_number, token_code: get_mfa_code) |
| 63 | + sts_role = sts_client.assume_role(role_arn: role_arn, |
| 64 | + role_session_name: session_name, |
| 65 | + serial_number: serial_number, |
| 66 | + token_code: get_mfa_code) |
32 | 67 | creds = sts_role[:credentials] |
33 | | - creds_obj = Aws::Credentials.new(creds.access_key_id, creds.secret_access_key, creds.session_token) |
| 68 | + creds_obj = Aws::Credentials.new( creds.access_key_id, |
| 69 | + creds.secret_access_key, |
| 70 | + creds.session_token ) |
34 | 71 |
|
35 | | - role_arn = "arn:aws:iam::#{target_account_number}:role#{target_role_prefix}#{target_role_name}" |
| 72 | + role_arn = "arn:aws:iam::#{target[:account_number]}:role#{target[:role_prefix]}#{target[:role_name]}" |
36 | 73 | session_name = username[0..31] |
37 | 74 | sts_client = Aws::STS::Client.new(region: region, credentials: creds_obj) |
38 | | - sts_role = sts_client.assume_role(role_arn: role_arn, role_session_name: session_name) |
| 75 | + sts_role = sts_client.assume_role(role_arn: role_arn, |
| 76 | + role_session_name: session_name) |
39 | 77 | creds = sts_role[:credentials] |
40 | | - Aws::Credentials.new(creds.access_key_id, creds.secret_access_key, creds.session_token) |
| 78 | + Aws::Credentials.new( creds.access_key_id, |
| 79 | + creds.secret_access_key, |
| 80 | + creds.session_token) |
41 | 81 | end |
42 | 82 |
|
43 | | - # TODO use keychain to save creds |
44 | | - def self.login(options, defaults, plane) |
45 | | - auth_file = "#{CREDS_FILE}.#{plane}" |
| 83 | + def self.target_plane_auth(region, profile_name) |
| 84 | + Aws.config[:credentials] = Aws::SharedCredentials.new(profile_name: profile_name) |
| 85 | + return Aws::CloudFormation::Client.new(region: region), Aws.config[:credentials].credentials |
| 86 | + end |
| 87 | + |
| 88 | + def self.valid_session?(region, creds) |
46 | 89 | begin |
47 | | - creds = YAML.load(File.read(auth_file)) |
48 | | - cf = Aws::CloudFormation::Client.new(region: defaults[:region], credentials: creds) |
49 | | - cf.list_stacks # testing that creds are still good |
50 | | - rescue => e |
51 | | - begin |
52 | | - profile_name = options[:profile] |
53 | | - Aws.config[:credentials] = Aws::SharedCredentials.new(profile_name: profile_name) |
54 | | - creds = get_creds(options.fetch(:username), defaults) |
55 | | - rescue KeyError => e |
56 | | - error = Rainbow("Error parsing #{CONFIG_FILE}, (#{e.message}), please ensure it is properly formatted").red |
57 | | - raise error |
58 | | - rescue => err |
59 | | - error = Rainbow(err.message).red |
60 | | - raise error |
61 | | - exit |
62 | | - end |
63 | | - # now save to yaml |
64 | | - File.open(auth_file, 'w') do |f| |
65 | | - f.write YAML.dump(creds) |
66 | | - end |
| 90 | + Aws::CloudFormation::Client.new(region: region, credentials: creds).list_stacks |
| 91 | + return true |
| 92 | + rescue Aws::CloudFormation::Errors::ExpiredToken => expired |
| 93 | + puts expired.message |
| 94 | + return false |
| 95 | + end |
| 96 | + end |
| 97 | + |
| 98 | + def self.get_auth_session(profile_name, username, config) |
| 99 | + Aws.config[:credentials] = Aws::SharedCredentials.new(profile_name: profile_name) |
| 100 | + |
| 101 | + get_creds(username, config) |
| 102 | + end |
| 103 | + |
| 104 | + def self.cloudformation_client(region, session) |
| 105 | + cf = Aws::CloudFormation::Client.new(region: region, credentials: session) |
| 106 | + return cf, session |
| 107 | + end |
67 | 108 |
|
68 | | - cf = Aws::CloudFormation::Client.new(region: defaults[:region], credentials: creds) |
| 109 | + def self.create_auth_file(file_name, session) |
| 110 | + File.open(file_name, 'w') do |f| |
| 111 | + f.write YAML.dump(session) |
69 | 112 | end |
70 | | - return cf, creds |
71 | 113 | end |
72 | 114 | end |
0 commit comments