simplygenius-atmos 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|