aws_assume_role 1.1.0-universal-freebsd
Sign up to get free protection for your applications and to get access to all the features.
- 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 +24 -0
- data/CHANGELOG.md +61 -0
- data/Gemfile +18 -0
- data/LICENSE.md +201 -0
- data/README.md +303 -0
- data/Rakefile +63 -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
|