aws_assume_role 1.0.6-linux
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 +7 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +57 -0
- data/.ruby-version +1 -0
- data/.simplecov +22 -0
- data/.travis.yml +21 -0
- data/CHANGELOG.md +57 -0
- data/Gemfile +18 -0
- data/LICENSE.md +201 -0
- data/README.md +303 -0
- data/Rakefile +47 -0
- data/aws_assume_role.gemspec +56 -0
- data/bin/aws-assume-role +4 -0
- data/i18n/en.yml +109 -0
- data/lib/aws_assume_role/cli/actions/abstract_action.rb +61 -0
- data/lib/aws_assume_role/cli/actions/configure_profile.rb +24 -0
- data/lib/aws_assume_role/cli/actions/configure_role_assumption.rb +22 -0
- data/lib/aws_assume_role/cli/actions/console.rb +70 -0
- data/lib/aws_assume_role/cli/actions/delete_profile.rb +22 -0
- data/lib/aws_assume_role/cli/actions/includes.rb +12 -0
- data/lib/aws_assume_role/cli/actions/list_profiles.rb +12 -0
- data/lib/aws_assume_role/cli/actions/migrate_profile.rb +20 -0
- data/lib/aws_assume_role/cli/actions/reset_environment.rb +50 -0
- data/lib/aws_assume_role/cli/actions/run.rb +36 -0
- data/lib/aws_assume_role/cli/actions/set_environment.rb +62 -0
- data/lib/aws_assume_role/cli/actions/test.rb +35 -0
- data/lib/aws_assume_role/cli/commands/configure.rb +32 -0
- data/lib/aws_assume_role/cli/commands/console.rb +19 -0
- data/lib/aws_assume_role/cli/commands/delete.rb +13 -0
- data/lib/aws_assume_role/cli/commands/environment.rb +34 -0
- data/lib/aws_assume_role/cli/commands/list.rb +12 -0
- data/lib/aws_assume_role/cli/commands/migrate.rb +13 -0
- data/lib/aws_assume_role/cli/commands/run.rb +19 -0
- data/lib/aws_assume_role/cli/commands/test.rb +20 -0
- data/lib/aws_assume_role/cli/includes.rb +3 -0
- data/lib/aws_assume_role/cli.rb +20 -0
- data/lib/aws_assume_role/configuration.rb +30 -0
- data/lib/aws_assume_role/core_ext/aws-sdk/credential_provider_chain.rb +4 -0
- data/lib/aws_assume_role/core_ext/aws-sdk/includes.rb +9 -0
- data/lib/aws_assume_role/credentials/factories/abstract_factory.rb +33 -0
- data/lib/aws_assume_role/credentials/factories/assume_role.rb +39 -0
- data/lib/aws_assume_role/credentials/factories/default_chain_provider.rb +113 -0
- data/lib/aws_assume_role/credentials/factories/environment.rb +26 -0
- data/lib/aws_assume_role/credentials/factories/includes.rb +15 -0
- data/lib/aws_assume_role/credentials/factories/instance_profile.rb +19 -0
- data/lib/aws_assume_role/credentials/factories/repository.rb +37 -0
- data/lib/aws_assume_role/credentials/factories/shared.rb +19 -0
- data/lib/aws_assume_role/credentials/factories/static.rb +18 -0
- data/lib/aws_assume_role/credentials/factories.rb +11 -0
- data/lib/aws_assume_role/credentials/includes.rb +6 -0
- data/lib/aws_assume_role/credentials/providers/assume_role_credentials.rb +60 -0
- data/lib/aws_assume_role/credentials/providers/includes.rb +9 -0
- data/lib/aws_assume_role/credentials/providers/mfa_session_credentials.rb +119 -0
- data/lib/aws_assume_role/credentials/providers/shared_keyring_credentials.rb +41 -0
- data/lib/aws_assume_role/includes.rb +38 -0
- data/lib/aws_assume_role/logging.rb +27 -0
- data/lib/aws_assume_role/profile_configuration.rb +73 -0
- data/lib/aws_assume_role/runner.rb +40 -0
- data/lib/aws_assume_role/store/includes.rb +8 -0
- data/lib/aws_assume_role/store/keyring.rb +61 -0
- data/lib/aws_assume_role/store/serialization.rb +20 -0
- data/lib/aws_assume_role/store/shared_config_with_keyring.rb +250 -0
- data/lib/aws_assume_role/types.rb +31 -0
- data/lib/aws_assume_role/ui.rb +57 -0
- data/lib/aws_assume_role/vendored/aws/README.md +2 -0
- data/lib/aws_assume_role/vendored/aws/assume_role_credentials.rb +67 -0
- data/lib/aws_assume_role/vendored/aws/includes.rb +9 -0
- data/lib/aws_assume_role/vendored/aws/refreshing_credentials.rb +58 -0
- data/lib/aws_assume_role/vendored/aws/shared_config.rb +223 -0
- data/lib/aws_assume_role/vendored/aws.rb +4 -0
- data/lib/aws_assume_role/version.rb +5 -0
- data/lib/aws_assume_role.rb +4 -0
- metadata +438 -0
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "includes"
|
4
|
+
require_relative "../../logging"
|
5
|
+
require_relative "../../profile_configuration"
|
6
|
+
require_relative "abstract_factory"
|
7
|
+
require_relative "environment"
|
8
|
+
require_relative "repository"
|
9
|
+
require_relative "instance_profile"
|
10
|
+
require_relative "assume_role"
|
11
|
+
require_relative "shared"
|
12
|
+
require_relative "static"
|
13
|
+
|
14
|
+
class AwsAssumeRole::Credentials::Factories::DefaultChainProvider < Dry::Struct
|
15
|
+
constructor_type :schema
|
16
|
+
include AwsAssumeRole::Credentials::Factories
|
17
|
+
include AwsAssumeRole::Logging
|
18
|
+
|
19
|
+
attribute :access_key_id, Dry::Types["strict.string"].optional
|
20
|
+
attribute :credentials, Dry::Types["object"].optional
|
21
|
+
attribute :duration_seconds, Dry::Types["coercible.int"].optional
|
22
|
+
attribute :external_id, Dry::Types["strict.string"].optional
|
23
|
+
attribute :instance_profile_credentials_retries, Dry::Types["strict.int"].default(0)
|
24
|
+
attribute :instance_profile_credentials_timeout, Dry::Types["coercible.float"].default(1.0)
|
25
|
+
attribute :mfa_serial, Dry::Types["strict.string"].optional
|
26
|
+
attribute :no_profile, Dry::Types["strict.bool"].default(false)
|
27
|
+
attribute :path, Dry::Types["strict.string"].optional
|
28
|
+
attribute :persist_session, Dry::Types["strict.bool"].default(true)
|
29
|
+
attribute :profile_name, Dry::Types["strict.string"].optional
|
30
|
+
attribute :profile, Dry::Types["strict.string"].optional
|
31
|
+
attribute :region, Dry::Types["strict.string"].optional
|
32
|
+
attribute :role_arn, Dry::Types["strict.string"].optional
|
33
|
+
attribute :role_session_name, Dry::Types["strict.string"].optional
|
34
|
+
attribute :secret_access_key, Dry::Types["strict.string"].optional
|
35
|
+
attribute :serial_number, Dry::Types["strict.string"].optional
|
36
|
+
attribute :session_token, Dry::Types["strict.string"].optional
|
37
|
+
attribute :source_profile, Dry::Types["strict.string"].optional
|
38
|
+
attribute :use_mfa, Dry::Types["strict.bool"].default(false)
|
39
|
+
attribute :yubikey_oath_name, Dry::Types["strict.string"].optional
|
40
|
+
|
41
|
+
def self.new(options)
|
42
|
+
if options.respond_to? :resolve
|
43
|
+
finalize_instance new_with_seahorse(options)
|
44
|
+
else
|
45
|
+
finalize_instance(options)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.finalize_instance(options)
|
50
|
+
new_opts = options.to_h
|
51
|
+
new_opts[:profile_name] ||= new_opts[:profile]
|
52
|
+
new_opts[:original_profile] = new_opts[:profile_name]
|
53
|
+
instance = allocate
|
54
|
+
instance.send(:initialize, new_opts)
|
55
|
+
instance
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.new_with_seahorse(resolver)
|
59
|
+
keys = resolver.resolve
|
60
|
+
options = keys.map do |k|
|
61
|
+
[k, resolver.send(k)]
|
62
|
+
end
|
63
|
+
finalize_instance(options.to_h)
|
64
|
+
end
|
65
|
+
|
66
|
+
def resolve(nil_with_role_not_set: false, explicit_default_profile: false)
|
67
|
+
resolve_final_credentials(explicit_default_profile)
|
68
|
+
# nil_creds = Aws::Credentials.new(nil, nil, nil)
|
69
|
+
return nil if (nil_with_role_not_set &&
|
70
|
+
@role_arn &&
|
71
|
+
@credentials.credentials.session_token.nil?) || @credentials.nil?
|
72
|
+
@credentials
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_h
|
76
|
+
to_hash
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def resolve_final_credentials(explicit_default_profile = false)
|
82
|
+
resolve_credentials(:credential_provider, true, explicit_default_profile)
|
83
|
+
return @credentials if @credentials && @credentials.set? && !use_mfa && !role_arn
|
84
|
+
resolve_credentials(:second_factor_provider, true, explicit_default_profile)
|
85
|
+
return @credentials if @credentials && @credentials.set?
|
86
|
+
resolve_credentials(:instance_role_provider, true, explicit_default_profile)
|
87
|
+
return @credentials if @credentials && @credentials.set?
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def resolve_credentials(type, break_if_successful = false, explicit_default_profile = false)
|
92
|
+
factories_to_try = Repository.factories[type]
|
93
|
+
factories_to_try.each do |x|
|
94
|
+
options = to_h
|
95
|
+
options[:credentials] = credentials if credentials && credentials.set?
|
96
|
+
logger.debug "About to try credential lookup with #{x}"
|
97
|
+
factory = x.new(options)
|
98
|
+
@region ||= factory.region
|
99
|
+
@profile ||= factory.profile
|
100
|
+
@role_arn ||= factory.role_arn
|
101
|
+
next unless factory.credentials && factory.credentials.set?
|
102
|
+
logger.debug "Profile currently #{@profile}"
|
103
|
+
next if explicit_default_profile && (@profile == "default") && (@profile != @original_profile)
|
104
|
+
@credentials ||= factory.credentials
|
105
|
+
logger.debug "Got #{@credentials}"
|
106
|
+
break if break_if_successful
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
module AwsAssumeRole
|
112
|
+
DefaultProvider = AwsAssumeRole::Credentials::Factories::DefaultChainProvider
|
113
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "abstract_factory"
|
4
|
+
|
5
|
+
class AwsAssumeRole::Credentials::Factories::Environment < AwsAssumeRole::Credentials::Factories::AbstractFactory
|
6
|
+
type :credential_provider
|
7
|
+
priority 10
|
8
|
+
|
9
|
+
def initialize(_options, **)
|
10
|
+
key = %w[AWS_ACCESS_KEY_ID AMAZON_ACCESS_KEY_ID AWS_ACCESS_KEY]
|
11
|
+
secret = %w[AWS_SECRET_ACCESS_KEY AMAZON_SECRET_ACCESS_KEY AWS_SECRET_KEY]
|
12
|
+
token = %w[AWS_SESSION_TOKEN AMAZON_SESSION_TOKEN]
|
13
|
+
region = %w[AWS_DEFAULT_REGION]
|
14
|
+
profile = %w[AWS_PROFILE]
|
15
|
+
@credentials = Aws::Credentials.new(envar(key), envar(secret), envar(token))
|
16
|
+
@region = envar(region)
|
17
|
+
@profile = envar(profile)
|
18
|
+
end
|
19
|
+
|
20
|
+
def envar(keys)
|
21
|
+
keys.each do |key|
|
22
|
+
return ENV[key] if ENV.key?(key)
|
23
|
+
end
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../includes"
|
4
|
+
require_relative "../../logging"
|
5
|
+
require_relative "../../vendored/aws"
|
6
|
+
require_relative "../../../aws_assume_role"
|
7
|
+
|
8
|
+
module AwsAssumeRole::Credentials
|
9
|
+
module Factories
|
10
|
+
Types = Dry::Types.module
|
11
|
+
include AwsAssumeRole
|
12
|
+
include AwsAssumeRole::Logging
|
13
|
+
include AwsAssumeRole::Vendored::Aws
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "abstract_factory"
|
4
|
+
|
5
|
+
class AwsAssumeRole::Credentials::Factories::InstanceProfile < AwsAssumeRole::Credentials::Factories::AbstractFactory
|
6
|
+
type :instance_role_provider
|
7
|
+
priority 40
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
options[:retries] ||= options[:instance_profile_credentials_retries] || 0
|
11
|
+
options[:http_open_timeout] ||= options[:instance_profile_credentials_timeout] || 1
|
12
|
+
options[:http_read_timeout] ||= options[:instance_profile_credentials_timeout] || 1
|
13
|
+
@credentials = if ENV["AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"]
|
14
|
+
Aws::ECSCredentials.new(options)
|
15
|
+
else
|
16
|
+
Aws::InstanceProfileCredentials.new(options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "includes"
|
4
|
+
require_relative "abstract_factory"
|
5
|
+
|
6
|
+
class AwsAssumeRole::Credentials::Factories::Repository
|
7
|
+
include AwsAssumeRole::Credentials::Factories
|
8
|
+
|
9
|
+
SubFactoryRepositoryType = Types::Hash.schema(Types::Coercible::Int => Types::Strict::Array)
|
10
|
+
|
11
|
+
FactoryRepositoryType = Types::Hash.schema(
|
12
|
+
credential_provider: SubFactoryRepositoryType,
|
13
|
+
second_factor_provider: SubFactoryRepositoryType,
|
14
|
+
instance_role_provider: SubFactoryRepositoryType,
|
15
|
+
)
|
16
|
+
|
17
|
+
def self.factories
|
18
|
+
repository.keys.map { |t| [t, flatten_factory_type_list(t)] }.to_h
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.repository
|
22
|
+
@repository ||= FactoryRepositoryType[
|
23
|
+
credential_provider: {},
|
24
|
+
second_factor_provider: {},
|
25
|
+
instance_role_provider: {},
|
26
|
+
]
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.register_factory(klass, type, priority)
|
30
|
+
repository[type][priority] ||= []
|
31
|
+
repository[type][priority] << klass
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.flatten_factory_type_list(type)
|
35
|
+
repository[type].keys.sort.map { |x| @repository[type][x] }.flatten
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "abstract_factory"
|
4
|
+
require_relative "../providers/shared_keyring_credentials"
|
5
|
+
|
6
|
+
class AwsAssumeRole::Credentials::Factories::Shared < AwsAssumeRole::Credentials::Factories::AbstractFactory
|
7
|
+
type :credential_provider
|
8
|
+
priority 30
|
9
|
+
|
10
|
+
def initialize(options = {})
|
11
|
+
logger.debug "Shared Factory initiated with #{options}"
|
12
|
+
@profile = options[:profile]
|
13
|
+
@credentials = AwsAssumeRole::Credentials::Providers::SharedKeyringCredentials.new(options)
|
14
|
+
@region = @credentials.region
|
15
|
+
@role_arn = @credentials.role_arn
|
16
|
+
rescue Aws::Errors::NoSuchProfileError
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "abstract_factory"
|
4
|
+
|
5
|
+
class AwsAssumeRole::Credentials::Factories::Static < AwsAssumeRole::Credentials::Factories::AbstractFactory
|
6
|
+
type :credential_provider
|
7
|
+
priority 0
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@credentials = Aws::Credentials.new(
|
11
|
+
options[:access_key_id],
|
12
|
+
options[:secret_access_key],
|
13
|
+
options[:session_token],
|
14
|
+
)
|
15
|
+
@region = options[:region]
|
16
|
+
@profile = options[:profile]
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "factories/repository"
|
4
|
+
require_relative "factories/abstract_factory"
|
5
|
+
require_relative "factories/default_chain_provider"
|
6
|
+
require_relative "factories/assume_role"
|
7
|
+
require_relative "factories/environment"
|
8
|
+
require_relative "factories/instance_profile"
|
9
|
+
require_relative "factories/shared_keyring"
|
10
|
+
require_relative "factories/shared"
|
11
|
+
require_relative "factories/static"
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "includes"
|
4
|
+
require "set"
|
5
|
+
|
6
|
+
class AwsAssumeRole::Credentials::Providers::AssumeRoleCredentials
|
7
|
+
include AwsAssumeRole::Vendored::Aws::CredentialProvider
|
8
|
+
include AwsAssumeRole::Vendored::Aws::RefreshingCredentials
|
9
|
+
|
10
|
+
# @option options [required, String] :role_arn
|
11
|
+
# @option options [required, String] :role_session_name
|
12
|
+
# @option options [String] :policy
|
13
|
+
# @option options [Integer] :duration_seconds
|
14
|
+
# @option options [String] :external_id
|
15
|
+
# @option options [STS::Client] :client
|
16
|
+
#
|
17
|
+
#
|
18
|
+
|
19
|
+
STS_KEYS = %i[role_arn role_session_name policy duration_seconds external_id client credentials region].freeze
|
20
|
+
|
21
|
+
def initialize(options = {})
|
22
|
+
client_opts = {}
|
23
|
+
@assume_role_params = {}
|
24
|
+
options.each_pair do |key, value|
|
25
|
+
if self.class.assume_role_options.include?(key)
|
26
|
+
@assume_role_params[key] = value
|
27
|
+
else
|
28
|
+
next unless STS_KEYS.include?(key)
|
29
|
+
client_opts[key] = value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
@client = client_opts[:client] || ::Aws::STS::Client.new(client_opts)
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [STS::Client]
|
37
|
+
attr_reader :client
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def refresh
|
42
|
+
c = @client.assume_role(@assume_role_params).credentials
|
43
|
+
@credentials = ::Aws::Credentials.new(
|
44
|
+
c.access_key_id,
|
45
|
+
c.secret_access_key,
|
46
|
+
c.session_token,
|
47
|
+
)
|
48
|
+
@expiration = c.expiration
|
49
|
+
end
|
50
|
+
|
51
|
+
class << self
|
52
|
+
# @api private
|
53
|
+
def assume_role_options
|
54
|
+
@aro ||= begin
|
55
|
+
input = ::Aws::STS::Client.api.operation(:assume_role).input
|
56
|
+
Set.new(input.shape.member_names)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "includes"
|
4
|
+
require_relative "../../types"
|
5
|
+
require_relative "../../configuration"
|
6
|
+
begin
|
7
|
+
require "smartcard"
|
8
|
+
require "yubioath"
|
9
|
+
SMARTCARD_SUPPORT = true
|
10
|
+
rescue LoadError
|
11
|
+
SMARTCARD_SUPPORT = false
|
12
|
+
end
|
13
|
+
|
14
|
+
class AwsAssumeRole::Credentials::Providers::MfaSessionCredentials < Dry::Struct
|
15
|
+
constructor_type :schema
|
16
|
+
include AwsAssumeRole::Vendored::Aws::CredentialProvider
|
17
|
+
include AwsAssumeRole::Vendored::Aws::RefreshingCredentials
|
18
|
+
include AwsAssumeRole::Ui
|
19
|
+
include AwsAssumeRole::Logging
|
20
|
+
|
21
|
+
attribute :permanent_credentials, Dry::Types["object"].optional
|
22
|
+
attribute :credentials, Dry::Types["object"].optional
|
23
|
+
attribute :expiration, Dry::Types["strict.time"].default(Time.now)
|
24
|
+
attribute :first_time, Dry::Types["strict.bool"].default(true)
|
25
|
+
attribute :persist_session, Dry::Types["strict.bool"].default(true)
|
26
|
+
attribute :duration_seconds, Dry::Types["coercible.int"].default(3600)
|
27
|
+
attribute :region, AwsAssumeRole::Types::Region.optional
|
28
|
+
attribute :serial_number, AwsAssumeRole::Types::MfaSerial.optional.default("automatic")
|
29
|
+
attribute :yubikey_oath_name, Dry::Types["strict.string"].optional
|
30
|
+
|
31
|
+
def initialize(options)
|
32
|
+
options.each { |key, value| instance_variable_set("@#{key}", value) }
|
33
|
+
@permanent_credentials ||= @credentials
|
34
|
+
@credentials = nil
|
35
|
+
@serial_number = resolve_serial_number(serial_number)
|
36
|
+
AwsAssumeRole::Vendored::Aws::RefreshingCredentials.instance_method(:initialize).bind(self).call(options)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def keyring_username
|
42
|
+
@keyring_username ||= "#{@identity.to_json}|#{@serial_number}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def sts_client
|
46
|
+
@sts_client ||= Aws::STS::Client.new(region: @region, credentials: @permanent_credentials)
|
47
|
+
end
|
48
|
+
|
49
|
+
def prompt_for_token
|
50
|
+
text = @first_time ? t("options.mfa_token.first_time") : t("options.mfa_token.other_times")
|
51
|
+
Ui.input.ask text
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialized
|
55
|
+
@first_time = false
|
56
|
+
end
|
57
|
+
|
58
|
+
def refresh
|
59
|
+
return set_credentials_from_keyring if @persist_session && @first_time
|
60
|
+
refresh_using_mfa if near_expiration?
|
61
|
+
broadcast(:mfa_completed)
|
62
|
+
end
|
63
|
+
|
64
|
+
def retrieve_yubikey_token
|
65
|
+
raise t("options.mfa_token.smartcard_not_supported") unless SMARTCARD_SUPPORT
|
66
|
+
context = Smartcard::PCSC::Context.new
|
67
|
+
raise "Yubikey not found" unless context.readers.length == 1
|
68
|
+
reader_name = context.readers.first
|
69
|
+
card = Smartcard::PCSC::Card.new(context, reader_name, :shared)
|
70
|
+
codes = YubiOATH.new(card).calculate_all(timestamp: Time.now)
|
71
|
+
codes.fetch(BinData::String.new(@yubikey_oath_name))
|
72
|
+
end
|
73
|
+
|
74
|
+
def refresh_using_mfa
|
75
|
+
token_code = @yubikey_oath_name ? retrieve_yubikey_token : prompt_for_token
|
76
|
+
token = sts_client.get_session_token(
|
77
|
+
duration_seconds: @duration_seconds,
|
78
|
+
serial_number: @serial_number,
|
79
|
+
token_code: token_code,
|
80
|
+
)
|
81
|
+
initialized
|
82
|
+
instance_credentials token.credentials
|
83
|
+
persist_credentials if @persist_session
|
84
|
+
end
|
85
|
+
|
86
|
+
def credentials_from_keyring
|
87
|
+
@credentials_from_keyring ||= AwsAssumeRole::Store::Keyring.fetch keyring_username
|
88
|
+
rescue Aws::Errors::NoSuchProfileError
|
89
|
+
logger.debug "Key not found"
|
90
|
+
@credentials_from_keyring = nil
|
91
|
+
return nil
|
92
|
+
end
|
93
|
+
|
94
|
+
def persist_credentials
|
95
|
+
AwsAssumeRole::Store::Keyring.save_credentials keyring_username, @credentials, expiration: @expiration
|
96
|
+
end
|
97
|
+
|
98
|
+
def instance_credentials(credentials)
|
99
|
+
return unless credentials
|
100
|
+
@credentials = AwsAssumeRole::Store::Serialization.credentials_from_hash(credentials)
|
101
|
+
@expiration = credentials.respond_to?(:expiration) ? credentials.expiration : Time.parse(credentials[:expiration])
|
102
|
+
end
|
103
|
+
|
104
|
+
def set_credentials_from_keyring
|
105
|
+
instance_credentials credentials_from_keyring if credentials_from_keyring
|
106
|
+
initialized
|
107
|
+
refresh_using_mfa unless @credentials && !near_expiration?
|
108
|
+
end
|
109
|
+
|
110
|
+
def identity
|
111
|
+
@identity ||= sts_client.get_caller_identity
|
112
|
+
end
|
113
|
+
|
114
|
+
def resolve_serial_number(serial_number)
|
115
|
+
return serial_number unless serial_number.nil? || serial_number == "automatic"
|
116
|
+
user_name = identity.arn.split("/")[1]
|
117
|
+
"arn:aws:iam::#{identity.account}:mfa/#{user_name}"
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "includes"
|
4
|
+
require_relative "../../types"
|
5
|
+
|
6
|
+
class AwsAssumeRole::Credentials::Providers::SharedKeyringCredentials < ::Aws::SharedCredentials
|
7
|
+
include AwsAssumeRole::Logging
|
8
|
+
attr_reader :region, :role_arn
|
9
|
+
|
10
|
+
def initialize(options = {})
|
11
|
+
logger.debug "SharedKeyringCredentials initiated with #{options}"
|
12
|
+
@path = options[:path]
|
13
|
+
@path ||= AwsAssumeRole.shared_config.credentials_path
|
14
|
+
@profile_name = options[:profile_name] ||= options[:profile]
|
15
|
+
@profile_name ||= ENV["AWS_PROFILE"]
|
16
|
+
@profile_name ||= AwsAssumeRole.shared_config.profile_name
|
17
|
+
logger.debug "SharedKeyringCredentials resolved profile name #{@profile_name}"
|
18
|
+
config = determine_config(@path, @profile_name)
|
19
|
+
@role_arn = config.profile_hash(@profile_name)
|
20
|
+
@region = config.profile_region(@profile_name)
|
21
|
+
@role_arn = config.profile_role(@profile_name)
|
22
|
+
attempted_credential = config.credentials(options)
|
23
|
+
return unless attempted_credential && attempted_credential.set?
|
24
|
+
@credentials = attempted_credential
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def determine_config(path, profile_name)
|
30
|
+
if path && path == AwsAssumeRole.shared_config.credentials_path
|
31
|
+
logger.debug "SharedKeyringCredentials found shared credential path"
|
32
|
+
AwsAssumeRole.shared_config
|
33
|
+
else
|
34
|
+
logger.debug "SharedKeyringCredentials found custom credential path"
|
35
|
+
AwsAssumeRole::Store::SharedConfigWithKeyring.new(
|
36
|
+
credentials_path: path,
|
37
|
+
profile_name: profile_name,
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "i18n"
|
4
|
+
require "active_support/json"
|
5
|
+
require "active_support/core_ext/object/blank"
|
6
|
+
require "active_support/core_ext/string/inflections"
|
7
|
+
require "active_support/core_ext/hash/compact"
|
8
|
+
require "active_support/core_ext/hash/keys"
|
9
|
+
require "active_support/core_ext/hash/slice"
|
10
|
+
require "aws-sdk"
|
11
|
+
require "aws-sdk-core/ini_parser"
|
12
|
+
require "dry-configurable"
|
13
|
+
require "dry-struct"
|
14
|
+
require "dry-validation"
|
15
|
+
require "dry-types"
|
16
|
+
require "English"
|
17
|
+
require "gli"
|
18
|
+
require "highline"
|
19
|
+
require "inifile"
|
20
|
+
require "json"
|
21
|
+
require "keyring"
|
22
|
+
require "launchy"
|
23
|
+
require "logger"
|
24
|
+
require "open-uri"
|
25
|
+
require "pastel"
|
26
|
+
require "securerandom"
|
27
|
+
require "set"
|
28
|
+
require "thread"
|
29
|
+
require "time"
|
30
|
+
|
31
|
+
module AwsAssumeRole
|
32
|
+
module_function
|
33
|
+
|
34
|
+
def shared_config
|
35
|
+
enabled = ENV["AWS_SDK_CONFIG_OPT_OUT"] ? false : true
|
36
|
+
@shared_config ||= SharedConfigWithKeyring.new(config_enabled: enabled)
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "includes"
|
4
|
+
require_relative "configuration"
|
5
|
+
module AwsAssumeRole::Logging
|
6
|
+
module ClassMethods
|
7
|
+
def logger
|
8
|
+
@logger ||= begin
|
9
|
+
logger = Logger.new($stderr)
|
10
|
+
logger.level = AwsAssumeRole::Config.log_level
|
11
|
+
ENV["GLI_DEBUG"] = "true" if AwsAssumeRole::Config.log_level.zero?
|
12
|
+
logger
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module InstanceMethods
|
18
|
+
def logger
|
19
|
+
self.class.logger
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.included(base)
|
24
|
+
base.extend ClassMethods
|
25
|
+
base.include InstanceMethods
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "includes"
|
4
|
+
require_relative "logging"
|
5
|
+
|
6
|
+
class AwsAssumeRole::ProfileConfiguration < Dry::Struct
|
7
|
+
constructor_type :schema
|
8
|
+
include AwsAssumeRole::Logging
|
9
|
+
attribute :access_key_id, Dry::Types["strict.string"].optional
|
10
|
+
attribute :credentials, Dry::Types["object"].optional
|
11
|
+
attribute :secret_access_key, Dry::Types["strict.string"].optional
|
12
|
+
attribute :session_token, Dry::Types["strict.string"].optional
|
13
|
+
attribute :duration_seconds, Dry::Types["coercible.int"].optional
|
14
|
+
attribute :external_id, Dry::Types["strict.string"].optional
|
15
|
+
attribute :path, Dry::Types["strict.string"].optional
|
16
|
+
attribute :persist_session, Dry::Types["strict.bool"].optional.default(true)
|
17
|
+
attribute :profile, Dry::Types["strict.string"].optional
|
18
|
+
attribute :region, Dry::Types["strict.string"].optional
|
19
|
+
attribute :role_arn, Dry::Types["strict.string"].optional
|
20
|
+
attribute :role_session_name, Dry::Types["strict.string"].optional
|
21
|
+
attribute :serial_number, Dry::Types["strict.string"].optional
|
22
|
+
attribute :mfa_serial, Dry::Types["strict.string"].optional
|
23
|
+
attribute :yubikey_oath_name, Dry::Types["strict.string"].optional
|
24
|
+
attribute :use_mfa, Dry::Types["strict.bool"].optional.default(false)
|
25
|
+
attribute :no_profile, Dry::Types["strict.bool"].optional.default(false)
|
26
|
+
attribute :shell_type, Dry::Types["strict.string"].optional
|
27
|
+
attribute :source_profile, Dry::Types["strict.string"].optional
|
28
|
+
attribute :args, Dry::Types["strict.array"].optional.default([])
|
29
|
+
attribute :instance_profile_credentials_retries, Dry::Types["strict.int"].default(0)
|
30
|
+
attribute :instance_profile_credentials_timeout, Dry::Types["coercible.float"].default(1.0)
|
31
|
+
|
32
|
+
attr_writer :credentials
|
33
|
+
|
34
|
+
def self.merge_mfa_variable(options)
|
35
|
+
new_hash = options.key?(:mfa_serial) ? options.merge(serial_number: options[:mfa_serial]) : options
|
36
|
+
new_hash[:use_mfa] ||= new_hash.fetch(:serial_number, nil) ? true : false
|
37
|
+
if new_hash.key?(:serial_number) && new_hash[:serial_number] == "automatic"
|
38
|
+
new_hash.delete(:serial_number)
|
39
|
+
end
|
40
|
+
new_hash
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.new_from_cli(global_options, options, args)
|
44
|
+
options = global_options.merge options
|
45
|
+
options = options.map do |k, v|
|
46
|
+
[k.to_s.underscore.to_sym, v]
|
47
|
+
end.to_h
|
48
|
+
options[:args] = args
|
49
|
+
new merge_mfa_variable(options)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.new_from_credential_provider_initialization(options)
|
53
|
+
logger.debug "new_from_credential_provider_initialization with #{options.to_h}"
|
54
|
+
new_from_credential_provider(options, credentials: nil, delete: [])
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.new_from_credential_provider(options = {}, credentials: nil, delete: [])
|
58
|
+
option_hash = options.to_h
|
59
|
+
config = option_hash.fetch(:config, {}).to_h
|
60
|
+
hash_to_merge = option_hash.merge config
|
61
|
+
hash_to_merge.merge(credentials: credentials) if credentials
|
62
|
+
delete.each do |k|
|
63
|
+
hash_to_merge.delete k
|
64
|
+
end
|
65
|
+
hash = merge_mfa_variable(hash_to_merge)
|
66
|
+
logger.debug "new_from_credential_provider with #{hash}"
|
67
|
+
new hash
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_h
|
71
|
+
to_hash
|
72
|
+
end
|
73
|
+
end
|