aws-sdk-core 3.130.1 → 3.171.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +301 -1
  3. data/VERSION +1 -1
  4. data/lib/aws-sdk-core/arn.rb +13 -0
  5. data/lib/aws-sdk-core/assume_role_credentials.rb +6 -11
  6. data/lib/aws-sdk-core/assume_role_web_identity_credentials.rb +8 -10
  7. data/lib/aws-sdk-core/binary/encode_handler.rb +12 -1
  8. data/lib/aws-sdk-core/credential_provider_chain.rb +8 -5
  9. data/lib/aws-sdk-core/ecs_credentials.rb +116 -53
  10. data/lib/aws-sdk-core/endpoints/condition.rb +41 -0
  11. data/lib/aws-sdk-core/endpoints/endpoint.rb +17 -0
  12. data/lib/aws-sdk-core/endpoints/endpoint_rule.rb +75 -0
  13. data/lib/aws-sdk-core/endpoints/error_rule.rb +42 -0
  14. data/lib/aws-sdk-core/endpoints/function.rb +80 -0
  15. data/lib/aws-sdk-core/endpoints/matchers.rb +127 -0
  16. data/lib/aws-sdk-core/endpoints/reference.rb +31 -0
  17. data/lib/aws-sdk-core/endpoints/rule.rb +25 -0
  18. data/lib/aws-sdk-core/endpoints/rule_set.rb +52 -0
  19. data/lib/aws-sdk-core/endpoints/rules_provider.rb +37 -0
  20. data/lib/aws-sdk-core/endpoints/templater.rb +58 -0
  21. data/lib/aws-sdk-core/endpoints/tree_rule.rb +45 -0
  22. data/lib/aws-sdk-core/endpoints/url.rb +60 -0
  23. data/lib/aws-sdk-core/endpoints.rb +74 -0
  24. data/lib/aws-sdk-core/errors.rb +13 -0
  25. data/lib/aws-sdk-core/instance_profile_credentials.rb +5 -0
  26. data/lib/aws-sdk-core/json/error_handler.rb +10 -1
  27. data/lib/aws-sdk-core/pageable_response.rb +7 -0
  28. data/lib/aws-sdk-core/plugins/bearer_authorization.rb +67 -0
  29. data/lib/aws-sdk-core/plugins/credentials_configuration.rb +24 -0
  30. data/lib/aws-sdk-core/plugins/endpoint_discovery.rb +6 -2
  31. data/lib/aws-sdk-core/plugins/jsonvalue_converter.rb +34 -6
  32. data/lib/aws-sdk-core/plugins/recursion_detection.rb +14 -3
  33. data/lib/aws-sdk-core/plugins/regional_endpoint.rb +5 -0
  34. data/lib/aws-sdk-core/plugins/retries/error_inspector.rb +2 -1
  35. data/lib/aws-sdk-core/plugins/sign.rb +200 -0
  36. data/lib/aws-sdk-core/plugins/signature_v2.rb +1 -0
  37. data/lib/aws-sdk-core/plugins/signature_v4.rb +13 -7
  38. data/lib/aws-sdk-core/process_credentials.rb +6 -9
  39. data/lib/aws-sdk-core/refreshing_credentials.rb +2 -0
  40. data/lib/aws-sdk-core/refreshing_token.rb +71 -0
  41. data/lib/aws-sdk-core/rest/handler.rb +1 -1
  42. data/lib/aws-sdk-core/rest/request/headers.rb +2 -6
  43. data/lib/aws-sdk-core/shared_config.rb +76 -5
  44. data/lib/aws-sdk-core/sso_credentials.rb +84 -49
  45. data/lib/aws-sdk-core/sso_token_provider.rb +135 -0
  46. data/lib/aws-sdk-core/static_token_provider.rb +14 -0
  47. data/lib/aws-sdk-core/structure.rb +6 -4
  48. data/lib/aws-sdk-core/token.rb +31 -0
  49. data/lib/aws-sdk-core/token_provider.rb +15 -0
  50. data/lib/aws-sdk-core/token_provider_chain.rb +51 -0
  51. data/lib/aws-sdk-core/xml/error_handler.rb +7 -0
  52. data/lib/aws-sdk-core/xml/parser/engines/oga.rb +2 -0
  53. data/lib/aws-sdk-core.rb +14 -0
  54. data/lib/aws-sdk-sso/client.rb +51 -11
  55. data/lib/aws-sdk-sso/endpoint_parameters.rb +66 -0
  56. data/lib/aws-sdk-sso/endpoint_provider.rb +51 -0
  57. data/lib/aws-sdk-sso/endpoints.rb +71 -0
  58. data/lib/aws-sdk-sso/plugins/endpoints.rb +76 -0
  59. data/lib/aws-sdk-sso/types.rb +8 -43
  60. data/lib/aws-sdk-sso.rb +5 -1
  61. data/lib/aws-sdk-ssooidc/client.rb +606 -0
  62. data/lib/aws-sdk-ssooidc/client_api.rb +216 -0
  63. data/lib/aws-sdk-ssooidc/customizations.rb +1 -0
  64. data/lib/aws-sdk-ssooidc/endpoint_parameters.rb +66 -0
  65. data/lib/aws-sdk-ssooidc/endpoint_provider.rb +51 -0
  66. data/lib/aws-sdk-ssooidc/endpoints.rb +57 -0
  67. data/lib/aws-sdk-ssooidc/errors.rb +290 -0
  68. data/lib/aws-sdk-ssooidc/plugins/endpoints.rb +74 -0
  69. data/lib/aws-sdk-ssooidc/resource.rb +26 -0
  70. data/lib/aws-sdk-ssooidc/types.rb +502 -0
  71. data/lib/aws-sdk-ssooidc.rb +59 -0
  72. data/lib/aws-sdk-sts/client.rb +166 -138
  73. data/lib/aws-sdk-sts/endpoint_parameters.rb +78 -0
  74. data/lib/aws-sdk-sts/endpoint_provider.rb +109 -0
  75. data/lib/aws-sdk-sts/endpoints.rb +135 -0
  76. data/lib/aws-sdk-sts/plugins/endpoints.rb +84 -0
  77. data/lib/aws-sdk-sts/presigner.rb +13 -15
  78. data/lib/aws-sdk-sts/types.rb +79 -186
  79. data/lib/aws-sdk-sts.rb +5 -1
  80. data/lib/seahorse/client/async_base.rb +0 -1
  81. data/lib/seahorse/client/configuration.rb +2 -2
  82. data/lib/seahorse/client/h2/connection.rb +12 -11
  83. data/lib/seahorse/client/plugins/request_callback.rb +9 -9
  84. data/lib/seahorse/util.rb +4 -0
  85. metadata +55 -8
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sigv4'
4
+
5
+ module Aws
6
+ module Plugins
7
+ # @api private
8
+ class Sign < Seahorse::Client::Plugin
9
+ # These once had defaults. But now they are used as overrides to
10
+ # new endpoint and auth resolution.
11
+ option(:sigv4_signer)
12
+ option(:sigv4_name)
13
+ option(:sigv4_region)
14
+ option(:unsigned_operations, default: [])
15
+
16
+ supported_auth_types = %w[sigv4 bearer none]
17
+ supported_auth_types += ['sigv4a'] if Aws::Sigv4::Signer.use_crt?
18
+ SUPPORTED_AUTH_TYPES = supported_auth_types.freeze
19
+
20
+ def add_handlers(handlers, cfg)
21
+ operations = cfg.api.operation_names - cfg.unsigned_operations
22
+ handlers.add(Handler, step: :sign, operations: operations)
23
+ end
24
+
25
+ # @api private
26
+ # Return a signer with the `sign(context)` method
27
+ def self.signer_for(auth_scheme, config, region_override = nil)
28
+ case auth_scheme['name']
29
+ when 'sigv4', 'sigv4a'
30
+ SignatureV4.new(auth_scheme, config, region_override)
31
+ when 'bearer'
32
+ Bearer.new
33
+ else
34
+ NullSigner.new
35
+ end
36
+ end
37
+
38
+ class Handler < Seahorse::Client::Handler
39
+ def call(context)
40
+ # Skip signing if using sigv2 signing from s3_signer in S3
41
+ unless v2_signing?(context.config)
42
+ signer = Sign.signer_for(
43
+ context[:auth_scheme],
44
+ context.config,
45
+ context[:sigv4_region]
46
+ )
47
+ signer.sign(context)
48
+ end
49
+ @handler.call(context)
50
+ end
51
+
52
+ private
53
+
54
+ def v2_signing?(config)
55
+ # 's3' is legacy signing, 'v4' is default
56
+ config.respond_to?(:signature_version) &&
57
+ config.signature_version == 's3'
58
+ end
59
+ end
60
+
61
+ # @api private
62
+ class Bearer
63
+ def initialize
64
+ end
65
+
66
+ def sign(context)
67
+ if context.http_request.endpoint.scheme != 'https'
68
+ raise ArgumentError,
69
+ 'Unable to use bearer authorization on non https endpoint.'
70
+ end
71
+
72
+ token_provider = context.config.token_provider
73
+
74
+ raise Errors::MissingBearerTokenError unless token_provider&.set?
75
+
76
+ context.http_request.headers['Authorization'] =
77
+ "Bearer #{token_provider.token.token}"
78
+ end
79
+
80
+ def presign_url(*args)
81
+ raise ArgumentError, 'Bearer auth does not support presigned urls'
82
+ end
83
+
84
+ def sign_event(*args)
85
+ raise ArgumentError, 'Bearer auth does not support event signing'
86
+ end
87
+ end
88
+
89
+ # @api private
90
+ class SignatureV4
91
+ def initialize(auth_scheme, config, region_override = nil)
92
+ scheme_name = auth_scheme['name']
93
+
94
+ unless %w[sigv4 sigv4a].include?(scheme_name)
95
+ raise ArgumentError,
96
+ "Expected sigv4 or sigv4a auth scheme, got #{scheme_name}"
97
+ end
98
+
99
+ region = if scheme_name == 'sigv4a'
100
+ auth_scheme['signingRegionSet'].first
101
+ else
102
+ auth_scheme['signingRegion']
103
+ end
104
+ begin
105
+ @signer = Aws::Sigv4::Signer.new(
106
+ service: config.sigv4_name || auth_scheme['signingName'],
107
+ region: region_override || config.sigv4_region || region,
108
+ credentials_provider: config.credentials,
109
+ signing_algorithm: scheme_name.to_sym,
110
+ uri_escape_path: !!!auth_scheme['disableDoubleEncoding'],
111
+ unsigned_headers: %w[content-length user-agent x-amzn-trace-id]
112
+ )
113
+ rescue Aws::Sigv4::Errors::MissingCredentialsError
114
+ raise Aws::Errors::MissingCredentialsError
115
+ end
116
+ end
117
+
118
+ def sign(context)
119
+ req = context.http_request
120
+
121
+ apply_authtype(context, req)
122
+ reset_signature(req)
123
+ apply_clock_skew(context, req)
124
+
125
+ # compute the signature
126
+ begin
127
+ signature = @signer.sign_request(
128
+ http_method: req.http_method,
129
+ url: req.endpoint,
130
+ headers: req.headers,
131
+ body: req.body
132
+ )
133
+ rescue Aws::Sigv4::Errors::MissingCredentialsError
134
+ # Necessary for when credentials is explicitly set to nil
135
+ raise Aws::Errors::MissingCredentialsError
136
+ end
137
+ # apply signature headers
138
+ req.headers.update(signature.headers)
139
+
140
+ # add request metadata with signature components for debugging
141
+ context[:canonical_request] = signature.canonical_request
142
+ context[:string_to_sign] = signature.string_to_sign
143
+ end
144
+
145
+ def presign_url(*args)
146
+ @signer.presign_url(*args)
147
+ end
148
+
149
+ def sign_event(*args)
150
+ @signer.sign_event(*args)
151
+ end
152
+
153
+ private
154
+
155
+ def apply_authtype(context, req)
156
+ if context.operation['authtype'].eql?('v4-unsigned-body') &&
157
+ req.endpoint.scheme.eql?('https')
158
+ req.headers['X-Amz-Content-Sha256'] ||= 'UNSIGNED-PAYLOAD'
159
+ end
160
+ end
161
+
162
+ def reset_signature(req)
163
+ # in case this request is being re-signed
164
+ req.headers.delete('Authorization')
165
+ req.headers.delete('X-Amz-Security-Token')
166
+ req.headers.delete('X-Amz-Date')
167
+ req.headers.delete('x-Amz-Region-Set')
168
+ end
169
+
170
+ def apply_clock_skew(context, req)
171
+ if context.config.respond_to?(:clock_skew) &&
172
+ context.config.clock_skew &&
173
+ context.config.correct_clock_skew
174
+
175
+ endpoint = context.http_request.endpoint
176
+ skew = context.config.clock_skew.clock_correction(endpoint)
177
+ if skew.abs.positive?
178
+ req.headers['X-Amz-Date'] =
179
+ (Time.now.utc + skew).strftime('%Y%m%dT%H%M%SZ')
180
+ end
181
+ end
182
+ end
183
+
184
+ end
185
+
186
+ # @api private
187
+ class NullSigner
188
+
189
+ def sign(context)
190
+ end
191
+
192
+ def presign_url(*args)
193
+ end
194
+
195
+ def sign_event(*args)
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
@@ -3,6 +3,7 @@
3
3
  module Aws
4
4
  module Plugins
5
5
  # @api private
6
+ # Necessary to keep after Endpoints 2.0
6
7
  class SignatureV2 < Seahorse::Client::Plugin
7
8
 
8
9
  option(:v2_signer) do |cfg|
@@ -5,8 +5,11 @@ require 'aws-sigv4'
5
5
  module Aws
6
6
  module Plugins
7
7
  # @api private
8
+ # Necessary to exist after endpoints 2.0
8
9
  class SignatureV4 < Seahorse::Client::Plugin
9
10
 
11
+ V4_AUTH = %w[v4 v4-unsigned-payload v4-unsigned-body]
12
+
10
13
  option(:sigv4_signer) do |cfg|
11
14
  SignatureV4.build_signer(cfg)
12
15
  end
@@ -32,13 +35,16 @@ module Aws
32
35
  end
33
36
 
34
37
  option(:unsigned_operations) do |cfg|
35
- cfg.api.operation_names.inject([]) do |unsigned, operation_name|
36
- if cfg.api.operation(operation_name)['authtype'] == 'none' ||
37
- cfg.api.operation(operation_name)['authtype'] == 'custom'
38
- # Unsign requests that has custom apigateway authorizer as well
39
- unsigned << operation_name
40
- else
41
- unsigned
38
+ if cfg.api.metadata['signatureVersion'] == 'v4'
39
+ # select operations where authtype is set and is not v4
40
+ cfg.api.operation_names.select do |o|
41
+ cfg.api.operation(o)['authtype'] && !V4_AUTH.include?(cfg.api.operation(o)['authtype'])
42
+ end
43
+ else # service is not v4 auth
44
+ # select all operations where authtype is not v4
45
+ # (includes operations with no explicit authtype)
46
+ cfg.api.operation_names.select do |o|
47
+ !V4_AUTH.include?(cfg.api.operation(o)['authtype'])
42
48
  end
43
49
  end
44
50
  end
@@ -1,19 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aws
4
-
5
4
  # A credential provider that executes a given process and attempts
6
- # to read its stdout to recieve a JSON payload containing the credentials
7
- #
8
- # Automatically handles refreshing credentials if an Expiration time is
9
- # provided in the credentials payload
10
- #
11
- # credentials = Aws::ProcessCredentials.new('/usr/bin/credential_proc').credentials
5
+ # to read its stdout to recieve a JSON payload containing the credentials.
12
6
  #
7
+ # credentials = Aws::ProcessCredentials.new('/usr/bin/credential_proc')
13
8
  # ec2 = Aws::EC2::Client.new(credentials: credentials)
14
9
  #
15
- # More documentation on process based credentials can be found here:
16
- # https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#sourcing-credentials-from-external-processes
10
+ # Automatically handles refreshing credentials if an Expiration time is
11
+ # provided in the credentials payload.
12
+ #
13
+ # @see https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#sourcing-credentials-from-external-processes
17
14
  class ProcessCredentials
18
15
 
19
16
  include CredentialProvider
@@ -20,6 +20,8 @@ module Aws
20
20
  SYNC_EXPIRATION_LENGTH = 300 # 5 minutes
21
21
  ASYNC_EXPIRATION_LENGTH = 600 # 10 minutes
22
22
 
23
+ CLIENT_EXCLUDE_OPTIONS = Set.new([:before_refresh]).freeze
24
+
23
25
  def initialize(options = {})
24
26
  @mutex = Mutex.new
25
27
  @before_refresh = options.delete(:before_refresh) if Hash === options
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thread'
4
+
5
+ module Aws
6
+
7
+ # Module/mixin used by token provider classes that can be refreshed. This
8
+ # provides basic refresh logic in a thread-safe manner. Classes mixing in
9
+ # this module are expected to implement a #refresh method that populates
10
+ # the following instance variable:
11
+ #
12
+ # * `@token` [Token] - {Aws::Token} object with the `expiration` and `token`
13
+ # fields set.
14
+ #
15
+ # @api private
16
+ module RefreshingToken
17
+
18
+ def initialize(options = {})
19
+ @mutex = Mutex.new
20
+ @before_refresh = options.delete(:before_refresh) if Hash === options
21
+
22
+ @before_refresh.call(self) if @before_refresh
23
+ refresh
24
+ end
25
+
26
+ # @return [Token]
27
+ def token
28
+ refresh_if_near_expiration
29
+ @token
30
+ end
31
+
32
+ # @return [Time,nil]
33
+ def expiration
34
+ refresh_if_near_expiration
35
+ @expiration
36
+ end
37
+
38
+ # Refresh token.
39
+ # @return [void]
40
+ def refresh!
41
+ @mutex.synchronize do
42
+ @before_refresh.call(self) if @before_refresh
43
+ refresh
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ # Refreshes token if it is within
50
+ # 5 minutes of expiration.
51
+ def refresh_if_near_expiration
52
+ if near_expiration?
53
+ @mutex.synchronize do
54
+ if near_expiration?
55
+ @before_refresh.call(self) if @before_refresh
56
+ refresh
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ def near_expiration?
63
+ if @token && @token.expiration
64
+ # are we within 5 minutes of expiration?
65
+ (Time.now.to_i + 5 * 60) > @token.expiration.to_i
66
+ else
67
+ true
68
+ end
69
+ end
70
+ end
71
+ end
@@ -17,7 +17,7 @@ module Aws
17
17
 
18
18
  def apply_request_id(context)
19
19
  h = context.http_response.headers
20
- context[:request_id] = h['x-amz-request-id'] || h['x-amzn-requestid']
20
+ context[:request_id] ||= h['x-amz-request-id'] || h['x-amzn-requestid']
21
21
  end
22
22
 
23
23
  end
@@ -53,12 +53,8 @@ module Aws
53
53
  return if !value || value.empty?
54
54
  headers[ref.location_name] = value
55
55
  .compact
56
- .map { |s| escape_header_list_string(s.to_s) }
57
- .join(",")
58
- end
59
-
60
- def escape_header_list_string(s)
61
- (s.include?('"') || s.include?(",")) ? "\"#{s.gsub('"', '\"')}\"" : s
56
+ .map { |s| Seahorse::Util.escape_header_list_string(s.to_s) }
57
+ .join(',')
62
58
  end
63
59
 
64
60
  def apply_header_map(headers, ref, values)
@@ -3,7 +3,11 @@
3
3
  module Aws
4
4
  # @api private
5
5
  class SharedConfig
6
- SSO_PROFILE_KEYS = %w[sso_start_url sso_region sso_account_id sso_role_name].freeze
6
+ SSO_CREDENTIAL_PROFILE_KEYS = %w[sso_account_id sso_role_name].freeze
7
+ SSO_PROFILE_KEYS = %w[sso_session sso_start_url sso_region sso_account_id sso_role_name].freeze
8
+ SSO_TOKEN_PROFILE_KEYS = %w[sso_session].freeze
9
+ SSO_SESSION_KEYS = %w[sso_region sso_start_url].freeze
10
+
7
11
 
8
12
  # @return [String]
9
13
  attr_reader :credentials_path
@@ -51,10 +55,12 @@ module Aws
51
55
  @config_enabled = options[:config_enabled]
52
56
  @credentials_path = options[:credentials_path] ||
53
57
  determine_credentials_path
58
+ @credentials_path = File.expand_path(@credentials_path) if @credentials_path
54
59
  @parsed_credentials = {}
55
60
  load_credentials_file if loadable?(@credentials_path)
56
61
  if @config_enabled
57
62
  @config_path = options[:config_path] || determine_config_path
63
+ @config_path = File.expand_path(@config_path) if @config_path
58
64
  load_config_file if loadable?(@config_path)
59
65
  end
60
66
  end
@@ -149,6 +155,18 @@ module Aws
149
155
  credentials
150
156
  end
151
157
 
158
+ # Attempts to load from shared config or shared credentials file.
159
+ # Will always attempt first to load from the shared credentials
160
+ # file, if present.
161
+ def sso_token_from_config(opts = {})
162
+ p = opts[:profile] || @profile_name
163
+ token = sso_token_from_profile(@parsed_credentials, p)
164
+ if @parsed_config
165
+ token ||= sso_token_from_profile(@parsed_config, p)
166
+ end
167
+ token
168
+ end
169
+
152
170
  # Add an accessor method (similar to attr_reader) to return a configuration value
153
171
  # Uses the get_config_value below to control where
154
172
  # values are loaded from
@@ -314,13 +332,66 @@ module Aws
314
332
  def sso_credentials_from_profile(cfg, profile)
315
333
  if @parsed_config &&
316
334
  (prof_config = cfg[profile]) &&
317
- !(prof_config.keys & SSO_PROFILE_KEYS).empty?
335
+ !(prof_config.keys & SSO_CREDENTIAL_PROFILE_KEYS).empty?
336
+
337
+ if sso_session_name = prof_config['sso_session']
338
+ sso_session = cfg["sso-session #{sso_session_name}"]
339
+ unless sso_session
340
+ raise ArgumentError,
341
+ "sso-session #{sso_session_name} must be defined in the config file. " \
342
+ "Referenced by profile #{profile}"
343
+ end
344
+ sso_region = sso_session['sso_region']
345
+ sso_start_url = sso_session['sso_start_url']
346
+
347
+ # validate sso_region and sso_start_url don't conflict if set on profile and session
348
+ if prof_config['sso_region'] && prof_config['sso_region'] != sso_region
349
+ raise ArgumentError,
350
+ "sso-session #{sso_session_name}'s sso_region (#{sso_region}) " \
351
+ "does not match the profile #{profile}'s sso_region (#{prof_config['sso_region']}'"
352
+ end
353
+ if prof_config['sso_start_url'] && prof_config['sso_start_url'] != sso_start_url
354
+ raise ArgumentError,
355
+ "sso-session #{sso_session_name}'s sso_start_url (#{sso_start_url}) " \
356
+ "does not match the profile #{profile}'s sso_start_url (#{prof_config['sso_start_url']}'"
357
+ end
358
+ else
359
+ sso_region = prof_config['sso_region']
360
+ sso_start_url = prof_config['sso_start_url']
361
+ end
318
362
 
319
363
  SSOCredentials.new(
320
- sso_start_url: prof_config['sso_start_url'],
321
- sso_region: prof_config['sso_region'],
322
364
  sso_account_id: prof_config['sso_account_id'],
323
- sso_role_name: prof_config['sso_role_name']
365
+ sso_role_name: prof_config['sso_role_name'],
366
+ sso_session: prof_config['sso_session'],
367
+ sso_region: sso_region,
368
+ sso_start_url: prof_config['sso_start_url']
369
+ )
370
+ end
371
+ end
372
+
373
+ # If the required sso_ profile values are present, attempt to construct
374
+ # SSOTokenProvider
375
+ def sso_token_from_profile(cfg, profile)
376
+ if @parsed_config &&
377
+ (prof_config = cfg[profile]) &&
378
+ !(prof_config.keys & SSO_TOKEN_PROFILE_KEYS).empty?
379
+
380
+ sso_session_name = prof_config['sso_session']
381
+ sso_session = cfg["sso-session #{sso_session_name}"]
382
+ unless sso_session
383
+ raise ArgumentError,
384
+ "sso-session #{sso_session_name} must be defined in the config file." \
385
+ "Referenced by profile #{profile}"
386
+ end
387
+
388
+ unless sso_session['sso_region']
389
+ raise ArgumentError, "sso-session #{sso_session_name} missing required parameter: sso_region"
390
+ end
391
+
392
+ SSOTokenProvider.new(
393
+ sso_session: sso_session_name,
394
+ sso_region: sso_session['sso_region']
324
395
  )
325
396
  end
326
397
  end
@@ -1,45 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aws
4
- # An auto-refreshing credential provider that works by assuming a
5
- # role via {Aws::SSO::Client#get_role_credentials} using a cached access
6
- # token. This class does NOT implement the SSO login token flow - tokens
7
- # must generated and refreshed separately by running `aws login` from the
8
- # AWS CLI with the correct profile.
9
- #
10
- # For more background on AWS SSO see the official
11
- # {https://docs.aws.amazon.com/singlesignon/latest/userguide/what-is.html what is SSO Userguide}
12
- #
13
- # ## Refreshing Credentials from SSO
14
- #
15
- # The `SSOCredentials` will auto-refresh the AWS credentials from SSO. In
16
- # addition to AWS credentials expiring after a given amount of time, the
17
- # access token generated and cached from `aws login` will also expire.
18
- # Once this token expires, it will not be usable to refresh AWS credentials,
19
- # and another token will be needed. The SDK does not manage refreshing of
20
- # the token value, but this can be done by running `aws login` with the
21
- # correct profile.
22
- #
4
+ # An auto-refreshing credential provider that assumes a role via
5
+ # {Aws::SSO::Client#get_role_credentials} using a cached access
6
+ # token. When `sso_session` is specified, token refresh logic from
7
+ # {Aws::SSOTokenProvider} will be used to refresh the token if possible.
8
+ # This class does NOT implement the SSO login token flow - tokens
9
+ # must generated separately by running `aws login` from the
10
+ # AWS CLI with the correct profile. The `SSOCredentials` will
11
+ # auto-refresh the AWS credentials from SSO.
23
12
  #
24
13
  # # You must first run aws sso login --profile your-sso-profile
25
14
  # sso_credentials = Aws::SSOCredentials.new(
26
15
  # sso_account_id: '123456789',
27
16
  # sso_role_name: "role_name",
28
17
  # sso_region: "us-east-1",
29
- # sso_start_url: 'https://your-start-url.awsapps.com/start'
18
+ # sso_session: 'my_sso_session'
30
19
  # )
31
- #
32
20
  # ec2 = Aws::EC2::Client.new(credentials: sso_credentials)
33
21
  #
34
- # If you omit `:client` option, a new {SSO::Client} object will be
35
- # constructed.
22
+ # If you omit `:client` option, a new {Aws::SSO::Client} object will be
23
+ # constructed with additional options that were provided.
24
+ #
25
+ # @see Aws::SSO::Client#get_role_credentials
26
+ # @see https://docs.aws.amazon.com/singlesignon/latest/userguide/what-is.html
36
27
  class SSOCredentials
37
28
 
38
29
  include CredentialProvider
39
30
  include RefreshingCredentials
40
31
 
41
32
  # @api private
42
- SSO_REQUIRED_OPTS = [:sso_account_id, :sso_region, :sso_role_name, :sso_start_url].freeze
33
+ LEGACY_REQUIRED_OPTS = [:sso_start_url, :sso_account_id, :sso_region, :sso_role_name].freeze
34
+ TOKEN_PROVIDER_REQUIRED_OPTS = [:sso_session, :sso_account_id, :sso_region, :sso_role_name].freeze
43
35
 
44
36
  # @api private
45
37
  SSO_LOGIN_GUIDANCE = 'The SSO session associated with this profile has '\
@@ -49,17 +41,23 @@ module Aws
49
41
  # @option options [required, String] :sso_account_id The AWS account ID
50
42
  # that temporary AWS credentials will be resolved for
51
43
  #
52
- # @option options [required, String] :sso_region The AWS region where the
53
- # SSO directory for the given sso_start_url is hosted.
54
- #
55
44
  # @option options [required, String] :sso_role_name The corresponding
56
45
  # IAM role in the AWS account that temporary AWS credentials
57
46
  # will be resolved for.
58
47
  #
59
- # @option options [required, String] :sso_start_url The start URL is
60
- # provided by the SSO service via the console and is the URL used to
48
+ # @option options [required, String] :sso_region The AWS region where the
49
+ # SSO directory for the given sso_start_url is hosted.
50
+ #
51
+ # @option options [String] :sso_session The SSO Token used for fetching
52
+ # the token. If provided, refresh logic from the {Aws::SSOTokenProvider}
53
+ # will be used.
54
+ #
55
+ # @option options [String] :sso_start_url (legacy profiles) If provided,
56
+ # legacy token fetch behavior will be used, which does not support
57
+ # token refreshing. The start URL is provided by the SSO
58
+ # service via the console and is the URL used to
61
59
  # login to the SSO directory. This is also sometimes referred to as
62
- # the "User Portal URL"
60
+ # the "User Portal URL".
63
61
  #
64
62
  # @option options [SSO::Client] :client Optional `SSO::Client`. If not
65
63
  # provided, a client will be constructed.
@@ -69,23 +67,52 @@ module Aws
69
67
  # with an instance of this object when
70
68
  # AWS credentials are required and need to be refreshed.
71
69
  def initialize(options = {})
70
+ options = options.select {|k, v| !v.nil? }
71
+ if (options[:sso_session])
72
+ missing_keys = TOKEN_PROVIDER_REQUIRED_OPTS.select { |k| options[k].nil? }
73
+ unless missing_keys.empty?
74
+ raise ArgumentError, "Missing required keys: #{missing_keys}"
75
+ end
76
+ @legacy = false
77
+ @sso_role_name = options.delete(:sso_role_name)
78
+ @sso_account_id = options.delete(:sso_account_id)
72
79
 
73
- missing_keys = SSO_REQUIRED_OPTS.select { |k| options[k].nil? }
74
- unless missing_keys.empty?
75
- raise ArgumentError, "Missing required keys: #{missing_keys}"
76
- end
80
+ # if client has been passed, don't pass through to SSOTokenProvider
81
+ @client = options.delete(:client)
82
+ options.delete(:sso_start_url)
83
+ @token_provider = Aws::SSOTokenProvider.new(options.dup)
84
+ @sso_session = options.delete(:sso_session)
85
+ @sso_region = options.delete(:sso_region)
86
+
87
+ unless @client
88
+ client_opts = {}
89
+ options.each_pair { |k,v| client_opts[k] = v unless CLIENT_EXCLUDE_OPTIONS.include?(k) }
90
+ client_opts[:region] = @sso_region
91
+ client_opts[:credentials] = nil
92
+ @client = Aws::SSO::Client.new(client_opts)
93
+ end
94
+ else # legacy behavior
95
+ missing_keys = LEGACY_REQUIRED_OPTS.select { |k| options[k].nil? }
96
+ unless missing_keys.empty?
97
+ raise ArgumentError, "Missing required keys: #{missing_keys}"
98
+ end
99
+ @legacy = true
100
+ @sso_start_url = options.delete(:sso_start_url)
101
+ @sso_region = options.delete(:sso_region)
102
+ @sso_role_name = options.delete(:sso_role_name)
103
+ @sso_account_id = options.delete(:sso_account_id)
77
104
 
78
- @sso_start_url = options.delete(:sso_start_url)
79
- @sso_region = options.delete(:sso_region)
80
- @sso_role_name = options.delete(:sso_role_name)
81
- @sso_account_id = options.delete(:sso_account_id)
105
+ # validate we can read the token file
106
+ read_cached_token
82
107
 
83
- # validate we can read the token file
84
- read_cached_token
108
+ client_opts = {}
109
+ options.each_pair { |k,v| client_opts[k] = v unless CLIENT_EXCLUDE_OPTIONS.include?(k) }
110
+ client_opts[:region] = @sso_region
111
+ client_opts[:credentials] = nil
112
+
113
+ @client = options[:client] || Aws::SSO::Client.new(client_opts)
114
+ end
85
115
 
86
- options[:region] = @sso_region
87
- options[:credentials] = nil
88
- @client = options[:client] || Aws::SSO::Client.new(options)
89
116
  @async_refresh = true
90
117
  super
91
118
  end
@@ -111,12 +138,20 @@ module Aws
111
138
  end
112
139
 
113
140
  def refresh
114
- cached_token = read_cached_token
115
- c = @client.get_role_credentials(
116
- account_id: @sso_account_id,
117
- role_name: @sso_role_name,
118
- access_token: cached_token['accessToken']
119
- ).role_credentials
141
+ c = if @legacy
142
+ cached_token = read_cached_token
143
+ @client.get_role_credentials(
144
+ account_id: @sso_account_id,
145
+ role_name: @sso_role_name,
146
+ access_token: cached_token['accessToken']
147
+ ).role_credentials
148
+ else
149
+ @client.get_role_credentials(
150
+ account_id: @sso_account_id,
151
+ role_name: @sso_role_name,
152
+ access_token: @token_provider.token.token
153
+ ).role_credentials
154
+ end
120
155
 
121
156
  @credentials = Credentials.new(
122
157
  c.access_key_id,