aws-sdk-core 3.130.1 → 3.178.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +377 -1
  3. data/VERSION +1 -1
  4. data/lib/aws-defaults/default_configuration.rb +4 -4
  5. data/lib/aws-sdk-core/arn.rb +13 -0
  6. data/lib/aws-sdk-core/assume_role_credentials.rb +6 -11
  7. data/lib/aws-sdk-core/assume_role_web_identity_credentials.rb +8 -10
  8. data/lib/aws-sdk-core/binary/encode_handler.rb +12 -1
  9. data/lib/aws-sdk-core/credential_provider.rb +3 -0
  10. data/lib/aws-sdk-core/credential_provider_chain.rb +8 -5
  11. data/lib/aws-sdk-core/ecs_credentials.rb +116 -53
  12. data/lib/aws-sdk-core/endpoints/condition.rb +41 -0
  13. data/lib/aws-sdk-core/endpoints/endpoint.rb +17 -0
  14. data/lib/aws-sdk-core/endpoints/endpoint_rule.rb +75 -0
  15. data/lib/aws-sdk-core/endpoints/error_rule.rb +42 -0
  16. data/lib/aws-sdk-core/endpoints/function.rb +80 -0
  17. data/lib/aws-sdk-core/endpoints/matchers.rb +127 -0
  18. data/lib/aws-sdk-core/endpoints/reference.rb +31 -0
  19. data/lib/aws-sdk-core/endpoints/rule.rb +25 -0
  20. data/lib/aws-sdk-core/endpoints/rule_set.rb +52 -0
  21. data/lib/aws-sdk-core/endpoints/rules_provider.rb +37 -0
  22. data/lib/aws-sdk-core/endpoints/templater.rb +58 -0
  23. data/lib/aws-sdk-core/endpoints/tree_rule.rb +45 -0
  24. data/lib/aws-sdk-core/endpoints/url.rb +60 -0
  25. data/lib/aws-sdk-core/endpoints.rb +78 -0
  26. data/lib/aws-sdk-core/errors.rb +13 -0
  27. data/lib/aws-sdk-core/ini_parser.rb +1 -1
  28. data/lib/aws-sdk-core/instance_profile_credentials.rb +5 -0
  29. data/lib/aws-sdk-core/json/error_handler.rb +20 -1
  30. data/lib/aws-sdk-core/log/formatter.rb +6 -0
  31. data/lib/aws-sdk-core/pageable_response.rb +10 -1
  32. data/lib/aws-sdk-core/plugins/bearer_authorization.rb +67 -0
  33. data/lib/aws-sdk-core/plugins/checksum_algorithm.rb +1 -1
  34. data/lib/aws-sdk-core/plugins/credentials_configuration.rb +24 -0
  35. data/lib/aws-sdk-core/plugins/endpoint_discovery.rb +6 -2
  36. data/lib/aws-sdk-core/plugins/jsonvalue_converter.rb +34 -6
  37. data/lib/aws-sdk-core/plugins/recursion_detection.rb +14 -3
  38. data/lib/aws-sdk-core/plugins/regional_endpoint.rb +111 -30
  39. data/lib/aws-sdk-core/plugins/request_compression.rb +217 -0
  40. data/lib/aws-sdk-core/plugins/retries/error_inspector.rb +2 -1
  41. data/lib/aws-sdk-core/plugins/sign.rb +201 -0
  42. data/lib/aws-sdk-core/plugins/signature_v2.rb +1 -0
  43. data/lib/aws-sdk-core/plugins/signature_v4.rb +13 -7
  44. data/lib/aws-sdk-core/plugins/user_agent.rb +117 -14
  45. data/lib/aws-sdk-core/process_credentials.rb +6 -9
  46. data/lib/aws-sdk-core/refreshing_credentials.rb +2 -6
  47. data/lib/aws-sdk-core/refreshing_token.rb +71 -0
  48. data/lib/aws-sdk-core/rest/handler.rb +1 -1
  49. data/lib/aws-sdk-core/rest/request/headers.rb +2 -6
  50. data/lib/aws-sdk-core/shared_config.rb +101 -6
  51. data/lib/aws-sdk-core/sso_credentials.rb +85 -50
  52. data/lib/aws-sdk-core/sso_token_provider.rb +135 -0
  53. data/lib/aws-sdk-core/static_token_provider.rb +14 -0
  54. data/lib/aws-sdk-core/structure.rb +6 -4
  55. data/lib/aws-sdk-core/token.rb +31 -0
  56. data/lib/aws-sdk-core/token_provider.rb +15 -0
  57. data/lib/aws-sdk-core/token_provider_chain.rb +51 -0
  58. data/lib/aws-sdk-core/waiters/poller.rb +3 -1
  59. data/lib/aws-sdk-core/xml/error_handler.rb +7 -0
  60. data/lib/aws-sdk-core/xml/parser/engines/oga.rb +2 -0
  61. data/lib/aws-sdk-core.rb +14 -0
  62. data/lib/aws-sdk-sso/client.rb +71 -11
  63. data/lib/aws-sdk-sso/endpoint_parameters.rb +66 -0
  64. data/lib/aws-sdk-sso/endpoint_provider.rb +51 -0
  65. data/lib/aws-sdk-sso/endpoints.rb +72 -0
  66. data/lib/aws-sdk-sso/plugins/endpoints.rb +76 -0
  67. data/lib/aws-sdk-sso/types.rb +8 -43
  68. data/lib/aws-sdk-sso.rb +5 -1
  69. data/lib/aws-sdk-ssooidc/client.rb +626 -0
  70. data/lib/aws-sdk-ssooidc/client_api.rb +216 -0
  71. data/lib/aws-sdk-ssooidc/customizations.rb +1 -0
  72. data/lib/aws-sdk-ssooidc/endpoint_parameters.rb +66 -0
  73. data/lib/aws-sdk-ssooidc/endpoint_provider.rb +51 -0
  74. data/lib/aws-sdk-ssooidc/endpoints.rb +58 -0
  75. data/lib/aws-sdk-ssooidc/errors.rb +290 -0
  76. data/lib/aws-sdk-ssooidc/plugins/endpoints.rb +74 -0
  77. data/lib/aws-sdk-ssooidc/resource.rb +26 -0
  78. data/lib/aws-sdk-ssooidc/types.rb +502 -0
  79. data/lib/aws-sdk-ssooidc.rb +59 -0
  80. data/lib/aws-sdk-sts/client.rb +289 -245
  81. data/lib/aws-sdk-sts/endpoint_parameters.rb +78 -0
  82. data/lib/aws-sdk-sts/endpoint_provider.rb +112 -0
  83. data/lib/aws-sdk-sts/endpoints.rb +136 -0
  84. data/lib/aws-sdk-sts/plugins/endpoints.rb +84 -0
  85. data/lib/aws-sdk-sts/presigner.rb +13 -15
  86. data/lib/aws-sdk-sts/types.rb +87 -195
  87. data/lib/aws-sdk-sts.rb +5 -1
  88. data/lib/seahorse/client/async_base.rb +0 -1
  89. data/lib/seahorse/client/configuration.rb +1 -5
  90. data/lib/seahorse/client/h2/connection.rb +12 -11
  91. data/lib/seahorse/client/plugins/request_callback.rb +9 -9
  92. data/lib/seahorse/model/operation.rb +3 -0
  93. data/lib/seahorse/util.rb +4 -0
  94. metadata +56 -8
@@ -4,7 +4,31 @@ module Aws
4
4
  module Plugins
5
5
  # @api private
6
6
  class UserAgent < Seahorse::Client::Plugin
7
+ # @api private
7
8
  option(:user_agent_suffix)
9
+ # @api private
10
+ option(:user_agent_frameworks, default: [])
11
+
12
+ option(
13
+ :sdk_ua_app_id,
14
+ doc_type: 'String',
15
+ docstring: <<-DOCS) do |cfg|
16
+ A unique and opaque application ID that is appended to the
17
+ User-Agent header as app/<sdk_ua_app_id>. It should have a
18
+ maximum length of 50.
19
+ DOCS
20
+ app_id = ENV['AWS_SDK_UA_APP_ID']
21
+ app_id ||= Aws.shared_config.sdk_ua_app_id(profile: cfg.profile)
22
+ app_id
23
+ end
24
+
25
+ def self.feature(feature, &block)
26
+ Thread.current[:aws_sdk_core_user_agent_feature] ||= []
27
+ Thread.current[:aws_sdk_core_user_agent_feature] << "ft/#{feature}"
28
+ block.call
29
+ ensure
30
+ Thread.current[:aws_sdk_core_user_agent_feature].pop
31
+ end
8
32
 
9
33
  # @api private
10
34
  class Handler < Seahorse::Client::Handler
@@ -14,33 +38,112 @@ module Aws
14
38
  end
15
39
 
16
40
  def set_user_agent(context)
17
- ua = "aws-sdk-ruby3/#{CORE_GEM_VERSION}"
41
+ context.http_request.headers['User-Agent'] = UserAgent.new(context).to_s
42
+ end
43
+
44
+ class UserAgent
45
+ def initialize(context)
46
+ @context = context
47
+ end
48
+
49
+ def to_s
50
+ ua = "aws-sdk-ruby3/#{CORE_GEM_VERSION}"
51
+ ua += ' ua/2.0'
52
+ ua += " #{api_metadata}" if api_metadata
53
+ ua += " #{os_metadata}"
54
+ ua += " #{language_metadata}"
55
+ ua += " #{env_metadata}" if env_metadata
56
+ ua += " #{config_metadata}" if config_metadata
57
+ ua += " #{app_id}" if app_id
58
+ ua += " #{feature_metadata}" if feature_metadata
59
+ ua += " #{framework_metadata}" if framework_metadata
60
+ if @context.config.user_agent_suffix
61
+ ua += " #{@context.config.user_agent_suffix}"
62
+ end
63
+ ua.strip
64
+ end
65
+
66
+ private
18
67
 
19
- begin
20
- ua += " #{RUBY_ENGINE}/#{RUBY_VERSION}"
21
- rescue
22
- ua += " RUBY_ENGINE_NA/#{RUBY_VERSION}"
68
+ # Used to be gem_name/gem_version
69
+ def api_metadata
70
+ service_id = @context.config.api.metadata['serviceId']
71
+ return unless service_id
72
+
73
+ service_id = service_id.gsub(' ', '_').downcase
74
+ gem_version = @context[:gem_version]
75
+ "api/#{service_id}##{gem_version}"
76
+ end
77
+
78
+ # Used to be RUBY_PLATFORM
79
+ def os_metadata
80
+ os =
81
+ case RbConfig::CONFIG['host_os']
82
+ when /mac|darwin/
83
+ 'macos'
84
+ when /linux|cygwin/
85
+ 'linux'
86
+ when /mingw|mswin/
87
+ 'windows'
88
+ else
89
+ 'other'
90
+ end
91
+ metadata = "os/#{os}"
92
+ local_version = Gem::Platform.local.version
93
+ metadata += "##{local_version}" if local_version
94
+ metadata += " md/#{RbConfig::CONFIG['host_cpu']}"
95
+ metadata
23
96
  end
24
97
 
25
- ua += " #{RUBY_PLATFORM}"
98
+ # Used to be RUBY_ENGINE/RUBY_VERSION
99
+ def language_metadata
100
+ "lang/#{RUBY_ENGINE}##{RUBY_ENGINE_VERSION} md/#{RUBY_VERSION}"
101
+ end
102
+
103
+ def env_metadata
104
+ return unless (execution_env = ENV['AWS_EXECUTION_ENV'])
105
+
106
+ "exec-env/#{execution_env}"
107
+ end
26
108
 
27
- if context[:gem_name] && context[:gem_version]
28
- ua += " #{context[:gem_name]}/#{context[:gem_version]}"
109
+ def config_metadata
110
+ "cfg/retry-mode##{@context.config.retry_mode}"
29
111
  end
30
112
 
31
- if (execution_env = ENV['AWS_EXECUTION_ENV'])
32
- ua += " exec-env/#{execution_env}"
113
+ def app_id
114
+ return unless (app_id = @context.config.sdk_ua_app_id)
115
+
116
+ # Sanitize and only allow these characters
117
+ app_id = app_id.gsub(/[^!#$%&'*+\-.^_`|~0-9A-Za-z]/, '-')
118
+ "app/#{app_id}"
33
119
  end
34
120
 
35
- if context.config.user_agent_suffix
36
- ua += " #{context.config.user_agent_suffix}"
121
+ def feature_metadata
122
+ return unless Thread.current[:aws_sdk_core_user_agent_feature]
123
+
124
+ Thread.current[:aws_sdk_core_user_agent_feature].join(' ')
37
125
  end
38
126
 
39
- context.http_request.headers['User-Agent'] = ua.strip
127
+ def framework_metadata
128
+ if (frameworks_cfg = @context.config.user_agent_frameworks).empty?
129
+ return
130
+ end
131
+
132
+ # Frameworks may be aws-record, aws-sdk-rails, etc.
133
+ regex = /gems\/(?<name>#{frameworks_cfg.join('|')})-(?<version>\d+\.\d+\.\d+)/.freeze
134
+ frameworks = {}
135
+ Kernel.caller.each do |line|
136
+ match = line.match(regex)
137
+ next unless match
138
+
139
+ frameworks[match[:name]] = match[:version]
140
+ end
141
+ frameworks.map { |n, v| "lib/#{n}##{v}" }.join(' ')
142
+ end
40
143
  end
41
144
  end
42
145
 
43
- handler(Handler)
146
+ handler(Handler, priority: 1)
44
147
  end
45
148
  end
46
149
  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
@@ -34,12 +36,6 @@ module Aws
34
36
  @credentials
35
37
  end
36
38
 
37
- # @return [Time,nil]
38
- def expiration
39
- refresh_if_near_expiration!
40
- @expiration
41
- end
42
-
43
39
  # Refresh credentials.
44
40
  # @return [void]
45
41
  def refresh!
@@ -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,38 @@ 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
+
170
+ # Source a custom configured endpoint from the shared configuration file
171
+ #
172
+ # @param [Hash] options
173
+ # @option options [String] :profile
174
+ # @option options [String] :service_id
175
+ def configured_endpoint(opts = {})
176
+ # services section is only allowed in the shared config file (not credentials)
177
+ profile = opts[:profile] || @profile_name
178
+ service_id = opts[:service_id]&.gsub(" ", "_")&.downcase
179
+ if @parsed_config && (prof_config = @parsed_config[profile])
180
+ services_section_name = prof_config['services']
181
+ if (services_config = @parsed_config["services #{services_section_name}"]) &&
182
+ (service_config = services_config[service_id])
183
+ return service_config['endpoint_url'] if service_config['endpoint_url']
184
+ end
185
+ return prof_config['endpoint_url']
186
+ end
187
+ nil
188
+ end
189
+
152
190
  # Add an accessor method (similar to attr_reader) to return a configuration value
153
191
  # Uses the get_config_value below to control where
154
192
  # values are loaded from
@@ -179,7 +217,11 @@ module Aws
179
217
  :s3_use_arn_region,
180
218
  :s3_us_east_1_regional_endpoint,
181
219
  :s3_disable_multiregion_access_points,
182
- :defaults_mode
220
+ :defaults_mode,
221
+ :sdk_ua_app_id,
222
+ :disable_request_compression,
223
+ :request_min_compression_size_bytes,
224
+ :ignore_configured_endpoint_urls
183
225
  )
184
226
 
185
227
  private
@@ -314,13 +356,66 @@ module Aws
314
356
  def sso_credentials_from_profile(cfg, profile)
315
357
  if @parsed_config &&
316
358
  (prof_config = cfg[profile]) &&
317
- !(prof_config.keys & SSO_PROFILE_KEYS).empty?
359
+ !(prof_config.keys & SSO_CREDENTIAL_PROFILE_KEYS).empty?
360
+
361
+ if sso_session_name = prof_config['sso_session']
362
+ sso_session = cfg["sso-session #{sso_session_name}"]
363
+ unless sso_session
364
+ raise ArgumentError,
365
+ "sso-session #{sso_session_name} must be defined in the config file. " \
366
+ "Referenced by profile #{profile}"
367
+ end
368
+ sso_region = sso_session['sso_region']
369
+ sso_start_url = sso_session['sso_start_url']
370
+
371
+ # validate sso_region and sso_start_url don't conflict if set on profile and session
372
+ if prof_config['sso_region'] && prof_config['sso_region'] != sso_region
373
+ raise ArgumentError,
374
+ "sso-session #{sso_session_name}'s sso_region (#{sso_region}) " \
375
+ "does not match the profile #{profile}'s sso_region (#{prof_config['sso_region']}'"
376
+ end
377
+ if prof_config['sso_start_url'] && prof_config['sso_start_url'] != sso_start_url
378
+ raise ArgumentError,
379
+ "sso-session #{sso_session_name}'s sso_start_url (#{sso_start_url}) " \
380
+ "does not match the profile #{profile}'s sso_start_url (#{prof_config['sso_start_url']}'"
381
+ end
382
+ else
383
+ sso_region = prof_config['sso_region']
384
+ sso_start_url = prof_config['sso_start_url']
385
+ end
318
386
 
319
387
  SSOCredentials.new(
320
- sso_start_url: prof_config['sso_start_url'],
321
- sso_region: prof_config['sso_region'],
322
388
  sso_account_id: prof_config['sso_account_id'],
323
- sso_role_name: prof_config['sso_role_name']
389
+ sso_role_name: prof_config['sso_role_name'],
390
+ sso_session: prof_config['sso_session'],
391
+ sso_region: sso_region,
392
+ sso_start_url: prof_config['sso_start_url']
393
+ )
394
+ end
395
+ end
396
+
397
+ # If the required sso_ profile values are present, attempt to construct
398
+ # SSOTokenProvider
399
+ def sso_token_from_profile(cfg, profile)
400
+ if @parsed_config &&
401
+ (prof_config = cfg[profile]) &&
402
+ !(prof_config.keys & SSO_TOKEN_PROFILE_KEYS).empty?
403
+
404
+ sso_session_name = prof_config['sso_session']
405
+ sso_session = cfg["sso-session #{sso_session_name}"]
406
+ unless sso_session
407
+ raise ArgumentError,
408
+ "sso-session #{sso_session_name} must be defined in the config file." \
409
+ "Referenced by profile #{profile}"
410
+ end
411
+
412
+ unless sso_session['sso_region']
413
+ raise ArgumentError, "sso-session #{sso_session_name} missing required parameter: sso_region"
414
+ end
415
+
416
+ SSOTokenProvider.new(
417
+ sso_session: sso_session_name,
418
+ sso_region: sso_session['sso_region']
324
419
  )
325
420
  end
326
421
  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,19 +138,27 @@ 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,
123
158
  c.secret_access_key,
124
159
  c.session_token
125
160
  )
126
- @expiration = c.expiration
161
+ @expiration = Time.at(c.expiration / 1000.0)
127
162
  end
128
163
 
129
164
  def sso_cache_file