aws_assume_role 1.1.0-universal-openbsd

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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rubocop.yml +57 -0
  4. data/.ruby-version +1 -0
  5. data/.simplecov +22 -0
  6. data/.travis.yml +24 -0
  7. data/CHANGELOG.md +61 -0
  8. data/Gemfile +18 -0
  9. data/LICENSE.md +201 -0
  10. data/README.md +303 -0
  11. data/Rakefile +63 -0
  12. data/aws_assume_role.gemspec +56 -0
  13. data/bin/aws-assume-role +4 -0
  14. data/i18n/en.yml +109 -0
  15. data/lib/aws_assume_role.rb +4 -0
  16. data/lib/aws_assume_role/cli.rb +20 -0
  17. data/lib/aws_assume_role/cli/actions/abstract_action.rb +61 -0
  18. data/lib/aws_assume_role/cli/actions/configure_profile.rb +24 -0
  19. data/lib/aws_assume_role/cli/actions/configure_role_assumption.rb +22 -0
  20. data/lib/aws_assume_role/cli/actions/console.rb +70 -0
  21. data/lib/aws_assume_role/cli/actions/delete_profile.rb +22 -0
  22. data/lib/aws_assume_role/cli/actions/includes.rb +12 -0
  23. data/lib/aws_assume_role/cli/actions/list_profiles.rb +12 -0
  24. data/lib/aws_assume_role/cli/actions/migrate_profile.rb +20 -0
  25. data/lib/aws_assume_role/cli/actions/reset_environment.rb +50 -0
  26. data/lib/aws_assume_role/cli/actions/run.rb +36 -0
  27. data/lib/aws_assume_role/cli/actions/set_environment.rb +62 -0
  28. data/lib/aws_assume_role/cli/actions/test.rb +35 -0
  29. data/lib/aws_assume_role/cli/commands/configure.rb +32 -0
  30. data/lib/aws_assume_role/cli/commands/console.rb +19 -0
  31. data/lib/aws_assume_role/cli/commands/delete.rb +13 -0
  32. data/lib/aws_assume_role/cli/commands/environment.rb +34 -0
  33. data/lib/aws_assume_role/cli/commands/list.rb +12 -0
  34. data/lib/aws_assume_role/cli/commands/migrate.rb +13 -0
  35. data/lib/aws_assume_role/cli/commands/run.rb +19 -0
  36. data/lib/aws_assume_role/cli/commands/test.rb +20 -0
  37. data/lib/aws_assume_role/cli/includes.rb +3 -0
  38. data/lib/aws_assume_role/configuration.rb +30 -0
  39. data/lib/aws_assume_role/core_ext/aws-sdk/credential_provider_chain.rb +4 -0
  40. data/lib/aws_assume_role/core_ext/aws-sdk/includes.rb +9 -0
  41. data/lib/aws_assume_role/credentials/factories.rb +11 -0
  42. data/lib/aws_assume_role/credentials/factories/abstract_factory.rb +33 -0
  43. data/lib/aws_assume_role/credentials/factories/assume_role.rb +39 -0
  44. data/lib/aws_assume_role/credentials/factories/default_chain_provider.rb +113 -0
  45. data/lib/aws_assume_role/credentials/factories/environment.rb +26 -0
  46. data/lib/aws_assume_role/credentials/factories/includes.rb +15 -0
  47. data/lib/aws_assume_role/credentials/factories/instance_profile.rb +19 -0
  48. data/lib/aws_assume_role/credentials/factories/repository.rb +37 -0
  49. data/lib/aws_assume_role/credentials/factories/shared.rb +19 -0
  50. data/lib/aws_assume_role/credentials/factories/static.rb +18 -0
  51. data/lib/aws_assume_role/credentials/includes.rb +6 -0
  52. data/lib/aws_assume_role/credentials/providers/assume_role_credentials.rb +60 -0
  53. data/lib/aws_assume_role/credentials/providers/includes.rb +9 -0
  54. data/lib/aws_assume_role/credentials/providers/mfa_session_credentials.rb +119 -0
  55. data/lib/aws_assume_role/credentials/providers/shared_keyring_credentials.rb +41 -0
  56. data/lib/aws_assume_role/includes.rb +38 -0
  57. data/lib/aws_assume_role/logging.rb +27 -0
  58. data/lib/aws_assume_role/profile_configuration.rb +73 -0
  59. data/lib/aws_assume_role/runner.rb +40 -0
  60. data/lib/aws_assume_role/store/includes.rb +8 -0
  61. data/lib/aws_assume_role/store/keyring.rb +61 -0
  62. data/lib/aws_assume_role/store/serialization.rb +20 -0
  63. data/lib/aws_assume_role/store/shared_config_with_keyring.rb +250 -0
  64. data/lib/aws_assume_role/types.rb +31 -0
  65. data/lib/aws_assume_role/ui.rb +57 -0
  66. data/lib/aws_assume_role/vendored/aws.rb +4 -0
  67. data/lib/aws_assume_role/vendored/aws/README.md +2 -0
  68. data/lib/aws_assume_role/vendored/aws/assume_role_credentials.rb +67 -0
  69. data/lib/aws_assume_role/vendored/aws/includes.rb +9 -0
  70. data/lib/aws_assume_role/vendored/aws/refreshing_credentials.rb +58 -0
  71. data/lib/aws_assume_role/vendored/aws/shared_config.rb +223 -0
  72. data/lib/aws_assume_role/version.rb +5 -0
  73. metadata +438 -0
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../includes"
4
+ require_relative "../../vendored/aws"
5
+ require_relative "../../ui"
6
+ require_relative "../../logging"
7
+ module AwsAssumeRole::Credentials
8
+ module Providers end
9
+ 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
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "includes"
4
+ require_relative "logging"
5
+
6
+ class AwsAssumeRole::Runner < Dry::Struct
7
+ include AwsAssumeRole::Logging
8
+ constructor_type :schema
9
+ attribute :command, Dry::Types["coercible.array"].of(Dry::Types["strict.string"]).default([])
10
+ attribute :exit_on_error, Dry::Types["strict.bool"].default(true)
11
+ attribute :expected_exit_code, Dry::Types["strict.int"].default(0)
12
+ attribute :environment, Dry::Types["strict.hash"].default({})
13
+ attribute :credentials, Dry::Types["object"].optional
14
+
15
+ def initialize(options)
16
+ super(options)
17
+ command_to_exec = command.map(&:shellescape).join(" ")
18
+ process_credentials unless credentials.blank?
19
+ system environment, command_to_exec
20
+ exit_status = $CHILD_STATUS.exitstatus
21
+ process_error(exit_status) if exit_status != expected_exit_code
22
+ end
23
+
24
+ private
25
+
26
+ def process_credentials
27
+ cred_env = {
28
+ "AWS_ACCESS_KEY_ID" => credentials.credentials.access_key_id,
29
+ "AWS_SECRET_ACCESS_KEY" => credentials.credentials.secret_access_key,
30
+ "AWS_SESSION_TOKEN" => credentials.credentials.session_token,
31
+ }
32
+ @environment = environment.merge cred_env
33
+ end
34
+
35
+ def process_error(exit_status)
36
+ logger.error "#{command} failed with #{exit_status}"
37
+ exit exit_status if exit_on_error
38
+ raise "#{command} failed with #{exit_status}"
39
+ end
40
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../includes"
4
+
5
+ module AwsAssumeRole
6
+ module Store
7
+ end
8
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "includes"
4
+ require_relative "serialization"
5
+ require_relative "../configuration"
6
+ require_relative "../logging"
7
+
8
+ module AwsAssumeRole::Store::Keyring
9
+ include AwsAssumeRole
10
+ include AwsAssumeRole::Store
11
+ include AwsAssumeRole::Logging
12
+
13
+ module_function
14
+
15
+ KEYRING_KEY = "AwsAssumeRole".freeze
16
+
17
+ def semaphore
18
+ @semaphore ||= Mutex.new
19
+ end
20
+
21
+ def keyrings
22
+ @keyrings ||= {}
23
+ end
24
+
25
+ def try_backend_plugin
26
+ return if AwsAssumeRole::Config.backend_plugin.blank?
27
+ logger.info "Attempting to load #{AwsAssumeRole::Config.backend_plugin} plugin"
28
+ require AwsAssumeRole::Config.backend_plugin
29
+ end
30
+
31
+ def keyring(backend = AwsAssumeRole::Config.backend)
32
+ keyrings[backend] ||= begin
33
+ try_backend_plugin
34
+ klass = backend ? "Keyring::Backend::#{backend}".constantize : nil
35
+ logger.debug "Initializing #{klass} backend"
36
+ ::Keyring.new(klass)
37
+ end
38
+ end
39
+
40
+ def fetch(id, backend: nil)
41
+ logger.debug "Fetching #{id} from keyring"
42
+ fetched = keyring(backend).get_password(KEYRING_KEY, id)
43
+ raise Aws::Errors::NoSuchProfileError if fetched == "null" || fetched.nil? || !fetched
44
+ JSON.parse(fetched, symbolize_names: true)
45
+ end
46
+
47
+ def delete_credentials(id, backend: nil)
48
+ semaphore.synchronize do
49
+ keyring(backend).delete_password(KEYRING_KEY, id)
50
+ end
51
+ end
52
+
53
+ def save_credentials(id, credentials, expiration: nil, backend: nil)
54
+ credentials_to_persist = Serialization.credentials_to_hash(credentials)
55
+ credentials_to_persist[:expiration] = expiration if expiration
56
+ semaphore.synchronize do
57
+ keyring(backend).delete_password(KEYRING_KEY, id)
58
+ keyring(backend).set_password(KEYRING_KEY, id, credentials_to_persist.to_json)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AwsAssumeRole::Store::Serialization
4
+ module_function
5
+
6
+ def credentials_from_hash(credentials)
7
+ creds_for_deserialization = credentials.respond_to?("[]") ? credentials : credentials_to_hash(credentials)
8
+ Aws::Credentials.new(creds_for_deserialization[:access_key_id],
9
+ creds_for_deserialization[:secret_access_key],
10
+ creds_for_deserialization[:session_token])
11
+ end
12
+
13
+ def credentials_to_hash(credentials)
14
+ {
15
+ access_key_id: credentials.access_key_id,
16
+ secret_access_key: credentials.secret_access_key,
17
+ session_token: credentials.session_token,
18
+ }
19
+ end
20
+ end