simplygenius-atmos 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -4
- data/exe/atmos +2 -2
- data/lib/{atmos.rb → simplygenius/atmos.rb} +9 -7
- data/lib/simplygenius/atmos/cli.rb +116 -0
- data/lib/simplygenius/atmos/commands/account.rb +69 -0
- data/lib/simplygenius/atmos/commands/apply.rb +24 -0
- data/lib/simplygenius/atmos/commands/auth_exec.rb +34 -0
- data/lib/simplygenius/atmos/commands/base_command.rb +16 -0
- data/lib/simplygenius/atmos/commands/bootstrap.rb +76 -0
- data/lib/simplygenius/atmos/commands/container.rb +62 -0
- data/lib/simplygenius/atmos/commands/destroy.rb +22 -0
- data/lib/simplygenius/atmos/commands/generate.rb +187 -0
- data/lib/simplygenius/atmos/commands/init.rb +22 -0
- data/lib/simplygenius/atmos/commands/new.rb +22 -0
- data/lib/simplygenius/atmos/commands/otp.rb +58 -0
- data/lib/simplygenius/atmos/commands/plan.rb +24 -0
- data/lib/simplygenius/atmos/commands/secret.rb +91 -0
- data/lib/simplygenius/atmos/commands/terraform.rb +56 -0
- data/lib/simplygenius/atmos/commands/user.rb +78 -0
- data/lib/simplygenius/atmos/config.rb +279 -0
- data/lib/simplygenius/atmos/exceptions.rb +13 -0
- data/lib/simplygenius/atmos/generator.rb +232 -0
- data/lib/simplygenius/atmos/ipc.rb +136 -0
- data/lib/simplygenius/atmos/ipc_actions/notify.rb +31 -0
- data/lib/simplygenius/atmos/ipc_actions/ping.rb +23 -0
- data/lib/simplygenius/atmos/logging.rb +164 -0
- data/lib/simplygenius/atmos/otp.rb +62 -0
- data/lib/simplygenius/atmos/plugin.rb +27 -0
- data/lib/simplygenius/atmos/plugin_manager.rb +120 -0
- data/lib/simplygenius/atmos/plugins/output_filter.rb +29 -0
- data/lib/simplygenius/atmos/plugins/prompt_notify.rb +21 -0
- data/lib/simplygenius/atmos/provider_factory.rb +23 -0
- data/lib/simplygenius/atmos/providers/aws/account_manager.rb +83 -0
- data/lib/simplygenius/atmos/providers/aws/auth_manager.rb +220 -0
- data/lib/simplygenius/atmos/providers/aws/container_manager.rb +118 -0
- data/lib/simplygenius/atmos/providers/aws/provider.rb +53 -0
- data/lib/simplygenius/atmos/providers/aws/s3_secret_manager.rb +51 -0
- data/lib/simplygenius/atmos/providers/aws/user_manager.rb +213 -0
- data/lib/simplygenius/atmos/settings_hash.rb +93 -0
- data/lib/simplygenius/atmos/source_path.rb +186 -0
- data/lib/simplygenius/atmos/template.rb +117 -0
- data/lib/simplygenius/atmos/terraform_executor.rb +297 -0
- data/lib/simplygenius/atmos/ui.rb +173 -0
- data/lib/simplygenius/atmos/utils.rb +54 -0
- data/lib/simplygenius/atmos/version.rb +5 -0
- data/templates/new/config/atmos.yml +21 -13
- data/templates/new/config/atmos/recipes.yml +16 -0
- data/templates/new/config/atmos/runtime.yml +9 -0
- metadata +46 -40
- data/lib/atmos/cli.rb +0 -105
- data/lib/atmos/commands/account.rb +0 -65
- data/lib/atmos/commands/apply.rb +0 -20
- data/lib/atmos/commands/auth_exec.rb +0 -29
- data/lib/atmos/commands/base_command.rb +0 -12
- data/lib/atmos/commands/bootstrap.rb +0 -72
- data/lib/atmos/commands/container.rb +0 -58
- data/lib/atmos/commands/destroy.rb +0 -18
- data/lib/atmos/commands/generate.rb +0 -90
- data/lib/atmos/commands/init.rb +0 -18
- data/lib/atmos/commands/new.rb +0 -18
- data/lib/atmos/commands/otp.rb +0 -54
- data/lib/atmos/commands/plan.rb +0 -20
- data/lib/atmos/commands/secret.rb +0 -87
- data/lib/atmos/commands/terraform.rb +0 -52
- data/lib/atmos/commands/user.rb +0 -74
- data/lib/atmos/config.rb +0 -208
- data/lib/atmos/exceptions.rb +0 -9
- data/lib/atmos/generator.rb +0 -199
- data/lib/atmos/generator_factory.rb +0 -93
- data/lib/atmos/ipc.rb +0 -132
- data/lib/atmos/ipc_actions/notify.rb +0 -27
- data/lib/atmos/ipc_actions/ping.rb +0 -19
- data/lib/atmos/logging.rb +0 -160
- data/lib/atmos/otp.rb +0 -61
- data/lib/atmos/provider_factory.rb +0 -19
- data/lib/atmos/providers/aws/account_manager.rb +0 -82
- data/lib/atmos/providers/aws/auth_manager.rb +0 -208
- data/lib/atmos/providers/aws/container_manager.rb +0 -116
- data/lib/atmos/providers/aws/provider.rb +0 -51
- data/lib/atmos/providers/aws/s3_secret_manager.rb +0 -49
- data/lib/atmos/providers/aws/user_manager.rb +0 -211
- data/lib/atmos/settings_hash.rb +0 -90
- data/lib/atmos/terraform_executor.rb +0 -267
- data/lib/atmos/ui.rb +0 -159
- data/lib/atmos/utils.rb +0 -50
- data/lib/atmos/version.rb +0 -3
@@ -0,0 +1,120 @@
|
|
1
|
+
require_relative '../atmos'
|
2
|
+
require_relative 'plugin'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module SimplyGenius
|
6
|
+
module Atmos
|
7
|
+
|
8
|
+
class PluginManager
|
9
|
+
include GemLogger::LoggerSupport
|
10
|
+
|
11
|
+
attr_reader :plugins
|
12
|
+
|
13
|
+
def initialize(plugins)
|
14
|
+
@plugins = []
|
15
|
+
Array(plugins).each do |plugin|
|
16
|
+
if plugin.is_a?(String)
|
17
|
+
name = plugin
|
18
|
+
plugin = SettingsHash.new
|
19
|
+
plugin[:name] = name
|
20
|
+
elsif plugin.is_a?(Hash)
|
21
|
+
plugin = SettingsHash.new(plugin)
|
22
|
+
if plugin[:name].blank?
|
23
|
+
logger.error "Invalid plugin definition, :name missing: #{plugin}"
|
24
|
+
next
|
25
|
+
end
|
26
|
+
else
|
27
|
+
logger.error "Invalid plugin definition: #{plugin}"
|
28
|
+
next
|
29
|
+
end
|
30
|
+
@plugins << plugin
|
31
|
+
end
|
32
|
+
|
33
|
+
@plugin_classes = Set.new
|
34
|
+
@plugin_instances = []
|
35
|
+
@output_filters = {}
|
36
|
+
end
|
37
|
+
|
38
|
+
def load_plugins
|
39
|
+
@plugins.each do |plugin|
|
40
|
+
load_plugin(plugin)
|
41
|
+
|
42
|
+
# Check for new plugin classes after each plugin load so that we can
|
43
|
+
# initialize them with their own config hash
|
44
|
+
Plugin.descendants.each do |plugin_class|
|
45
|
+
begin
|
46
|
+
if ! @plugin_classes.include?(plugin_class)
|
47
|
+
@plugin_classes << plugin_class
|
48
|
+
@plugin_instances << plugin_class.new(plugin)
|
49
|
+
end
|
50
|
+
rescue StandardError => e
|
51
|
+
logger.log_exception e, "Failed to initialize plugin: #{plugin_class}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def load_plugin(plugin)
|
58
|
+
begin
|
59
|
+
name = plugin[:name]
|
60
|
+
require_name = plugin[:require] || name.gsub('-', '/')
|
61
|
+
logger.debug("Loading plugin #{name} as #{require_name}")
|
62
|
+
require require_name
|
63
|
+
rescue LoadError, StandardError => e
|
64
|
+
logger.log_exception e, "Failed to load atmos plugin: #{name} - #{e.message}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def validate_output_filter_type(type)
|
69
|
+
raise "Invalid output filter type #{type}, must be one of [:stdout, :stderr]" unless [:stdout, :stderr].include?(type)
|
70
|
+
end
|
71
|
+
|
72
|
+
def register_output_filter(type, clazz)
|
73
|
+
validate_output_filter_type(type)
|
74
|
+
@output_filters[type.to_sym] ||= []
|
75
|
+
@output_filters[type.to_sym] << clazz
|
76
|
+
end
|
77
|
+
|
78
|
+
def output_filters(type, context)
|
79
|
+
validate_output_filter_type(type)
|
80
|
+
@output_filters[type.to_sym] ||= []
|
81
|
+
return OutputFilterCollection.new(@output_filters[type.to_sym].collect {|clazz| clazz.new(context) })
|
82
|
+
end
|
83
|
+
|
84
|
+
class OutputFilterCollection
|
85
|
+
include GemLogger::LoggerSupport
|
86
|
+
|
87
|
+
attr_accessor :filters
|
88
|
+
|
89
|
+
def initialize(filters)
|
90
|
+
@filters = filters
|
91
|
+
end
|
92
|
+
|
93
|
+
def filter_block
|
94
|
+
return Proc.new do |data|
|
95
|
+
@filters.inject(data) do |memo, obj|
|
96
|
+
begin
|
97
|
+
obj.filter(memo)
|
98
|
+
rescue StandardError => e
|
99
|
+
logger.log_exception e, "Output filter failed during filter: #{obj.class}"
|
100
|
+
memo
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def close
|
107
|
+
@filters.each do |f|
|
108
|
+
begin
|
109
|
+
f.close
|
110
|
+
rescue StandardError => e
|
111
|
+
logger.log_exception e, "Output filter failed during close: #{f.class}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative '../../atmos'
|
2
|
+
require_relative '../../atmos/ui'
|
3
|
+
|
4
|
+
module SimplyGenius
|
5
|
+
module Atmos
|
6
|
+
module Plugins
|
7
|
+
|
8
|
+
class OutputFilter
|
9
|
+
include GemLogger::LoggerSupport
|
10
|
+
include UI
|
11
|
+
|
12
|
+
attr_reader :context
|
13
|
+
|
14
|
+
def initialize(context)
|
15
|
+
@context = context
|
16
|
+
end
|
17
|
+
|
18
|
+
def filter(data)
|
19
|
+
raise "not implemented"
|
20
|
+
end
|
21
|
+
|
22
|
+
def close
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative '../../atmos'
|
2
|
+
require_relative 'output_filter'
|
3
|
+
|
4
|
+
module SimplyGenius
|
5
|
+
module Atmos
|
6
|
+
module Plugins
|
7
|
+
|
8
|
+
class PromptNotify < OutputFilter
|
9
|
+
|
10
|
+
def filter(data)
|
11
|
+
if data =~ /^[\e\[\dm\s]*Enter a value:[\e\[\dm\s]*$/
|
12
|
+
notify(message: "Terraform is waiting for user input")
|
13
|
+
end
|
14
|
+
data
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative '../atmos'
|
2
|
+
|
3
|
+
module SimplyGenius
|
4
|
+
module Atmos
|
5
|
+
|
6
|
+
class ProviderFactory
|
7
|
+
include GemLogger::LoggerSupport
|
8
|
+
|
9
|
+
def self.get(name)
|
10
|
+
@provider ||= begin
|
11
|
+
logger.debug("Loading provider: #{name}")
|
12
|
+
require "simplygenius/atmos/providers/#{name}/provider"
|
13
|
+
provider = "SimplyGenius::Atmos::Providers::#{name.camelize}::Provider".constantize
|
14
|
+
logger.debug("Loaded provider #{provider}")
|
15
|
+
provider.new(name)
|
16
|
+
end
|
17
|
+
return @provider
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require_relative '../../../atmos'
|
2
|
+
require 'aws-sdk-organizations'
|
3
|
+
|
4
|
+
module SimplyGenius
|
5
|
+
module Atmos
|
6
|
+
module Providers
|
7
|
+
module Aws
|
8
|
+
|
9
|
+
class AccountManager
|
10
|
+
include GemLogger::LoggerSupport
|
11
|
+
include FileUtils
|
12
|
+
|
13
|
+
def initialize(provider)
|
14
|
+
@provider = provider
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_account(env, name: nil, email: nil)
|
18
|
+
result = {}
|
19
|
+
org = ::Aws::Organizations::Client.new
|
20
|
+
resp = nil
|
21
|
+
name ||= "Atmos #{env} account"
|
22
|
+
|
23
|
+
begin
|
24
|
+
logger.info "Looking up organization"
|
25
|
+
resp = org.describe_organization()
|
26
|
+
logger.debug "Described organization: #{resp.to_h}"
|
27
|
+
rescue ::Aws::Organizations::Errors::AWSOrganizationsNotInUseException
|
28
|
+
logger.info "Organization doesn't exist, creating"
|
29
|
+
resp = org.create_organization()
|
30
|
+
logger.debug "Created organization: #{resp.to_h}"
|
31
|
+
end
|
32
|
+
|
33
|
+
if email.blank?
|
34
|
+
master_email = resp.organization.master_account_email
|
35
|
+
email = master_email.sub('@', "+#{env}@")
|
36
|
+
end
|
37
|
+
result[:email] = email
|
38
|
+
result[:name] = name
|
39
|
+
|
40
|
+
|
41
|
+
begin
|
42
|
+
logger.info "Creating account named #{name}"
|
43
|
+
resp = org.create_account(account_name: name, email: email)
|
44
|
+
rescue ::Aws::Organizations::Errors::FinalizingOrganizationException
|
45
|
+
logger.info "Waiting to retry account creation as the organization needs to finalize"
|
46
|
+
logger.info "This will eventually succeed after receiving a"
|
47
|
+
logger.info "'Consolidated Billing verification' email from AWS"
|
48
|
+
logger.info "You can leave this running or cancel and restart later."
|
49
|
+
sleep 60
|
50
|
+
retry
|
51
|
+
end
|
52
|
+
|
53
|
+
logger.debug "Created account: #{resp.to_h}"
|
54
|
+
|
55
|
+
status_id = resp.create_account_status.id
|
56
|
+
status = resp.create_account_status.state
|
57
|
+
account_id = resp.create_account_status.account_id
|
58
|
+
|
59
|
+
while status =~ /in_progress/i
|
60
|
+
logger.info "Waiting for account creation to complete, status: #{status}"
|
61
|
+
resp = org.describe_create_account_status(create_account_request_id: status_id)
|
62
|
+
logger.debug("Account creation status check: #{resp.to_h}")
|
63
|
+
status = resp.create_account_status.state
|
64
|
+
account_id = resp.create_account_status.account_id
|
65
|
+
sleep 5
|
66
|
+
end
|
67
|
+
|
68
|
+
if status =~ /failed/i
|
69
|
+
logger.error "Failed to create account: #{resp.create_account_status.failure_reason}"
|
70
|
+
exit(1)
|
71
|
+
end
|
72
|
+
|
73
|
+
result[:account_id] = account_id
|
74
|
+
|
75
|
+
return result
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
require_relative '../../../atmos'
|
2
|
+
require_relative '../../../atmos/utils'
|
3
|
+
require_relative '../../../atmos/ui'
|
4
|
+
require_relative '../../../atmos/otp'
|
5
|
+
require 'aws-sdk-core'
|
6
|
+
require 'aws-sdk-iam'
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
module SimplyGenius
|
10
|
+
module Atmos
|
11
|
+
module Providers
|
12
|
+
module Aws
|
13
|
+
|
14
|
+
class AuthManager
|
15
|
+
include GemLogger::LoggerSupport
|
16
|
+
include FileUtils
|
17
|
+
include UI
|
18
|
+
|
19
|
+
def initialize(provider)
|
20
|
+
@provider = provider
|
21
|
+
end
|
22
|
+
|
23
|
+
def authenticate(system_env, **opts, &block)
|
24
|
+
|
25
|
+
profile = system_env['AWS_PROFILE']
|
26
|
+
key = system_env['AWS_ACCESS_KEY_ID']
|
27
|
+
secret = system_env['AWS_SECRET_ACCESS_KEY']
|
28
|
+
if profile.blank? && (key.blank? || secret.blank?)
|
29
|
+
logger.warn("An aws profile or key/secret should be supplied via the environment")
|
30
|
+
end
|
31
|
+
|
32
|
+
# Handle bootstrapping a new env account. Newly created organization
|
33
|
+
# accounts only have the default role that can only be assumed by an
|
34
|
+
# iam user, so use that as the target for assume_role, and the root
|
35
|
+
# check below will ensure iam user
|
36
|
+
assume_role_name = nil
|
37
|
+
if opts[:bootstrap] && Atmos.config.atmos_env != 'ops'
|
38
|
+
# TODO: do this hack better
|
39
|
+
assume_role_name = Atmos.config["auth.bootstrap_assume_role_name"]
|
40
|
+
else
|
41
|
+
assume_role_name = opts[:role] || Atmos.config["auth.assume_role_name"]
|
42
|
+
end
|
43
|
+
account_id = Atmos.config.account_hash[Atmos.config.atmos_env].to_s
|
44
|
+
role_arn = "arn:aws:iam::#{account_id}:role/#{assume_role_name}"
|
45
|
+
|
46
|
+
user_name = nil
|
47
|
+
begin
|
48
|
+
sts = ::Aws::STS::Client.new
|
49
|
+
resp = sts.get_caller_identity
|
50
|
+
arn_pieces = resp.arn.split(":")
|
51
|
+
user_name = arn_pieces.last.split("/").last
|
52
|
+
|
53
|
+
# root credentials can't assume role, but they should have full
|
54
|
+
# access for the current account, so proceed (e.g. for bootstrap).
|
55
|
+
if arn_pieces.last == "root"
|
56
|
+
|
57
|
+
# We check the account of the caller to prevent root user of ops
|
58
|
+
# account from bootstrapping an env account, but still allow a
|
59
|
+
# root user of the env account itself to be able to bootstrap
|
60
|
+
# (i.e. to allow not organizational accounts to bootstrap using
|
61
|
+
# their root user)
|
62
|
+
if arn_pieces[-2] != account_id
|
63
|
+
logger.error <<~EOF
|
64
|
+
Account doesn't match credentials. Bootstrapping a new
|
65
|
+
account should be done as an iam user from the ops account or
|
66
|
+
using credentials for a root user of the env account.
|
67
|
+
EOF
|
68
|
+
exit(1)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Should only use root credentials for bootstrap, and thus we
|
72
|
+
# won't have role requirement for mfa, etc, even if root account
|
73
|
+
# uses mfa for login. Thus skip all the other stuff, to
|
74
|
+
# encourage/force use of non-root accounts for normal use
|
75
|
+
logger.warn("Using aws root credentials - should only be neccessary for bootstrap")
|
76
|
+
return block.call(Hash[system_env])
|
77
|
+
end
|
78
|
+
|
79
|
+
rescue ::Aws::STS::Errors::ServiceError => e
|
80
|
+
logger.error "Could not discover aws credentials"
|
81
|
+
exit(1)
|
82
|
+
end
|
83
|
+
|
84
|
+
auth_needed = true
|
85
|
+
cache_key = "#{user_name}-#{assume_role_name}"
|
86
|
+
credentials = read_auth_cache[cache_key]
|
87
|
+
|
88
|
+
if credentials.present?
|
89
|
+
logger.debug("Session cache present, checking expiration...")
|
90
|
+
expiration = Time.parse(credentials['expiration'])
|
91
|
+
session_renew_interval = (session_duration / 4).to_i
|
92
|
+
|
93
|
+
if Time.now > expiration
|
94
|
+
logger.debug "Session cache is expired, performing normal auth"
|
95
|
+
auth_needed = true
|
96
|
+
elsif Time.now > (expiration - session_renew_interval)
|
97
|
+
begin
|
98
|
+
# TODO: investigate making all info a warn so we don't pollute stdout for shell scripts
|
99
|
+
logger.info "Session approaching expiration, renewing..."
|
100
|
+
credentials = assume_role(role_arn, credentials: credentials, user_name: user_name)
|
101
|
+
write_auth_cache(cache_key => credentials)
|
102
|
+
auth_needed = false
|
103
|
+
rescue => e
|
104
|
+
logger.info "Failed to renew credentials using session cache, reason: #{e.message}"
|
105
|
+
auth_needed = true
|
106
|
+
end
|
107
|
+
else
|
108
|
+
logger.debug "Session cache is current, skipping auth"
|
109
|
+
auth_needed = false
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
if auth_needed
|
114
|
+
begin
|
115
|
+
logger.info "No active session cache, authenticating..."
|
116
|
+
|
117
|
+
credentials = assume_role(role_arn, user_name: user_name)
|
118
|
+
write_auth_cache(cache_key => credentials)
|
119
|
+
|
120
|
+
rescue ::Aws::STS::Errors::AccessDenied => e
|
121
|
+
if e.message !~ /explicit deny/
|
122
|
+
logger.debug "Access Denied, reason: #{e.message}"
|
123
|
+
end
|
124
|
+
|
125
|
+
logger.info "Normal auth failed, checking for mfa"
|
126
|
+
|
127
|
+
iam = ::Aws::IAM::Client.new
|
128
|
+
response = iam.list_mfa_devices(user_name: user_name)
|
129
|
+
mfa_serial = response.mfa_devices.first.try(:serial_number)
|
130
|
+
token = nil
|
131
|
+
if mfa_serial.present?
|
132
|
+
|
133
|
+
token = Otp.instance.generate(user_name)
|
134
|
+
if token.nil?
|
135
|
+
token = ask("Enter token to retry with mfa: ")
|
136
|
+
else
|
137
|
+
logger.info "Used integrated atmos mfa to generate token"
|
138
|
+
end
|
139
|
+
|
140
|
+
if token.blank?
|
141
|
+
logger.error "A MFA token must be supplied"
|
142
|
+
exit(1)
|
143
|
+
end
|
144
|
+
|
145
|
+
else
|
146
|
+
logger.error "MFA is not setup for your account, retry after doing so"
|
147
|
+
exit(1)
|
148
|
+
end
|
149
|
+
|
150
|
+
credentials = assume_role(role_arn, serial_number: mfa_serial, token_code: token, user_name: user_name)
|
151
|
+
write_auth_cache(cache_key => credentials)
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
process_env = {}
|
157
|
+
process_env['AWS_ACCESS_KEY_ID'] = credentials['access_key_id']
|
158
|
+
process_env['AWS_SECRET_ACCESS_KEY'] = credentials['secret_access_key']
|
159
|
+
process_env['AWS_SESSION_TOKEN'] = credentials['session_token']
|
160
|
+
logger.debug("Calling authentication target with env: #{process_env.inspect}")
|
161
|
+
block.call(Hash[system_env].merge(process_env))
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
def session_duration
|
167
|
+
@session_duration ||= (Atmos.config["auth.session_duration"] || 3600).to_i
|
168
|
+
end
|
169
|
+
|
170
|
+
def assume_role(role_arn, **opts)
|
171
|
+
# use Aws::AssumeRoleCredentials ?
|
172
|
+
if opts[:credentials]
|
173
|
+
c = opts.delete(:credentials)
|
174
|
+
creds = ::Aws::Credentials.new(
|
175
|
+
c[:access_key_id], c[:secret_access_key], c[:session_token]
|
176
|
+
)
|
177
|
+
client_opts = {credentials: creds}
|
178
|
+
else
|
179
|
+
client_opts = {}
|
180
|
+
end
|
181
|
+
|
182
|
+
user_name = opts.delete(:user_name)
|
183
|
+
if user_name
|
184
|
+
session_name = "Atmos-#{user_name}"
|
185
|
+
else
|
186
|
+
session_name = "Atmos"
|
187
|
+
end
|
188
|
+
|
189
|
+
sts = ::Aws::STS::Client.new(client_opts)
|
190
|
+
params = {
|
191
|
+
duration_seconds: session_duration,
|
192
|
+
role_session_name: session_name,
|
193
|
+
role_arn: role_arn
|
194
|
+
}.merge(opts)
|
195
|
+
logger.debug("Assuming role: #{params}")
|
196
|
+
resp = sts.assume_role(params)
|
197
|
+
return Utils::SymbolizedMash.new(resp.credentials.to_h)
|
198
|
+
end
|
199
|
+
|
200
|
+
def auth_cache_file
|
201
|
+
File.join(Atmos.config.auth_cache_dir, 'aws-assume-role.json')
|
202
|
+
end
|
203
|
+
|
204
|
+
def write_auth_cache(h)
|
205
|
+
File.open(auth_cache_file, 'w') do |f|
|
206
|
+
f.puts(JSON.pretty_generate(h))
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def read_auth_cache
|
211
|
+
data = JSON.parse(File.read(auth_cache_file)) rescue {}
|
212
|
+
Utils::SymbolizedMash.new(data)
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|