fog-aws 3.33.1 → 3.33.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7cc7ce33126b4fdeebc3302a81326921168f6e815a11f16c25961b223c49923
4
- data.tar.gz: 89c207e1063763f94a4ccf6044aa5f302384a46aee5776b02489639ec631c9e1
3
+ metadata.gz: 342e946932ceb6c12ce470df9e37ffa2f55b9d6a024093b713e6f45feae9f6e0
4
+ data.tar.gz: ff426f59d76f172e598dbea47141718ad28c593bc8ab369b7ff49aba59439163
5
5
  SHA512:
6
- metadata.gz: fb49f18ada74faebdcee9a9dec6e47ee550454810aa8251c88734b5b5384989a23f3347d6357ab9c8c04ab6375335e65be98484f22d9817d381196482456fcc4
7
- data.tar.gz: 4b87d40f2b4506d3305c681c96b04631c551566c4974d4ca82713d7bc2243a74e01533f3598fc2834ba95b79404bbfbdae802212f4b6fb4ec47673823f11932b
6
+ metadata.gz: 3d388030bcc2d14866b610127a8d82b37803d10d98be2156abccd1a49ab983de1b03a54fcd55731808a2f25506b02aed6bb1d0ca327ef9be9763945e0cec8bd5
7
+ data.tar.gz: e26cffa774d294514ef9d89eef55f86dd933d0d4e6afd5858d42b19fa798febb44b030bc209fab4840e03291e9727911961a80db0f6dcedbdfbd7946f287d80f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## [v3.33.3](https://github.com/fog/fog-aws/tree/v3.33.3) (2026-08-28)
4
+
5
+ [Full Changelog](https://github.com/fog/fog-aws/compare/v3.33.0...v3.33.1)
6
+
7
+ **Merged pull requests:**
8
+
9
+ - fix thread safety of shared Fog::Storage connections [\#759](https://github.com/fog/fog-aws/pull/759) ([stanhu](https://github.com/stanhu))
10
+
11
+ - fix flaky describe_spot_price_history [\#760](https://github.com/fog/fog-aws/pull/760) ([stanhu](https://github.com/stanhu))
12
+
13
+ - Bump actions/checkout from 6 to 7 [\#758](https://github.com/fog/fog-aws/pull/758) ([dependabot](https://github.com/apps/dependabot))
14
+
15
+ - Bump actions/dependency-review-action from 4 to 5 [\#757](https://github.com/fog/fog-aws/pull/757) ([dependabot](https://github.com/apps/dependabot))
16
+
17
+ - Bump fog/.github/.github/workflows/ci.yml from 1.5.0 to 1.6.0 [\#756](https://github.com/fog/fog-aws/pull/756) ([dependabot](https://github.com/apps/dependabot))
18
+
19
+ ## [v3.33.2](https://github.com/fog/fog-aws/tree/v3.33.2) (2026-04-20)
20
+
21
+ [Full Changelog](https://github.com/fog/fog-aws/compare/v3.33.0...v3.33.1)
22
+
23
+ **Merged pull requests:**
24
+
25
+ - bug(aws/storage): include non-standard port in Host header for signature v4 [\#755](https://github.com/fog/fog-aws/pull/755) ([stanhu](https://github.com/stanhu))
26
+ - Bump actions/checkout from 5 to 6 [\#753](https://github.com/fog/fog-aws/pull/753) ([dependabot](https://github.com/apps/dependabot))
27
+
3
28
  ## [v3.33.1](https://github.com/fog/fog-aws/tree/v3.33.1) (2025-11-03)
4
29
 
5
30
  [Full Changelog](https://github.com/fog/fog-aws/compare/v3.33.0...v3.33.1)
@@ -86,7 +86,9 @@ module Fog
86
86
  end
87
87
  zones = all_zones if zones.nil?
88
88
 
89
- max_results = params['MaxResults'] || Fog::Mock.random_numbers(3).to_i
89
+ # Fog::Mock.random_numbers(3) can return "000", which .to_i turns
90
+ # into 0 and then trips the validation below, so guarantee at least 1.
91
+ max_results = params['MaxResults'] || [Fog::Mock.random_numbers(3).to_i, 1].max
90
92
  if !(max_results.is_a?(Integer) && max_results > 0)
91
93
  max_results_error = "InvalidParameterValue => Invalid value '#{max_results}' for maxResults"
92
94
  raise Fog::AWS::Compute::Error, max_results_error
@@ -25,15 +25,18 @@ module Fog
25
25
  def post_object_hidden_fields(options = {})
26
26
  options = options.dup
27
27
  if policy = options['policy']
28
+ # Grab the correlated credential state once so the credential,
29
+ # security token, and signature all come from the same snapshot.
30
+ credentials = @credentials
28
31
  date = Fog::Time.now
29
- credential = "#{@aws_access_key_id}/#{@signer.credential_scope(date)}"
32
+ credential = "#{credentials.aws_access_key_id}/#{credentials.signer.credential_scope(date)}"
30
33
  extra_conditions = [
31
34
  {'x-amz-date' => date.to_iso8601_basic},
32
35
  {'x-amz-credential' => credential},
33
36
  {'x-amz-algorithm' => Fog::AWS::SignatureV4::ALGORITHM}
34
37
  ]
35
38
 
36
- extra_conditions << {'x-amz-security-token' => @aws_session_token } if @aws_session_token
39
+ extra_conditions << {'x-amz-security-token' => credentials.aws_session_token } if credentials.aws_session_token
37
40
 
38
41
  policy_with_auth_fields = policy.merge('conditions' => policy['conditions'] + extra_conditions)
39
42
 
@@ -41,10 +44,10 @@ module Fog
41
44
  options['X-Amz-Credential'] = credential
42
45
  options['X-Amz-Date'] = date.to_iso8601_basic
43
46
  options['X-Amz-Algorithm'] = Fog::AWS::SignatureV4::ALGORITHM
44
- if @aws_session_token
45
- options['X-Amz-Security-Token'] = @aws_session_token
47
+ if credentials.aws_session_token
48
+ options['X-Amz-Security-Token'] = credentials.aws_session_token
46
49
  end
47
- options['X-Amz-Signature'] = @signer.derived_hmac(date).sign(options['policy']).unpack('H*').first
50
+ options['X-Amz-Signature'] = credentials.signer.derived_hmac(date).sign(options['policy']).unpack('H*').first
48
51
  end
49
52
  options
50
53
  end
@@ -129,6 +129,32 @@ module Fog
129
129
  request :upload_part
130
130
  request :upload_part_copy
131
131
 
132
+ # Immutable snapshot of the credential-derived state needed to sign a
133
+ # request: the access key id, secret, session token, and the matching
134
+ # signer (v4) or HMAC (v2). These values are correlated. Under
135
+ # +use_iam_profile+ they are all replaced together whenever credentials
136
+ # are refreshed.
137
+ #
138
+ # Bundling them into one frozen object stored in a single instance
139
+ # variable lets a caller read a consistent set with one atomic
140
+ # reference read (see Real#request). Reading the fields as separate
141
+ # instance variables lets a refresh land between two reads and pair, for
142
+ # example, an old session token with a new signer, which S3 rejects with
143
+ # a 403. Keeping this connection safe to share across threads depends on
144
+ # never mutating an instance; build a new one and swap the reference.
145
+ class Credentials
146
+ attr_reader :aws_access_key_id, :aws_secret_access_key, :aws_session_token, :signer, :hmac
147
+
148
+ def initialize(aws_access_key_id: nil, aws_secret_access_key: nil, aws_session_token: nil, signer: nil, hmac: nil)
149
+ @aws_access_key_id = aws_access_key_id
150
+ @aws_secret_access_key = aws_secret_access_key
151
+ @aws_session_token = aws_session_token
152
+ @signer = signer
153
+ @hmac = hmac
154
+ freeze
155
+ end
156
+ end
157
+
132
158
  module Utils
133
159
  attr_accessor :region
134
160
  attr_accessor :disable_content_md5_validation
@@ -145,9 +171,10 @@ module Fog
145
171
  attr_reader :max_copy_chunk_size
146
172
 
147
173
  def cdn
174
+ credentials = @credentials
148
175
  @cdn ||= Fog::AWS::CDN.new(
149
- :aws_access_key_id => @aws_access_key_id,
150
- :aws_secret_access_key => @aws_secret_access_key,
176
+ :aws_access_key_id => credentials.aws_access_key_id,
177
+ :aws_secret_access_key => credentials.aws_secret_access_key,
151
178
  :use_iam_profile => @use_iam_profile
152
179
  )
153
180
  end
@@ -223,6 +250,10 @@ module Fog
223
250
  end
224
251
 
225
252
  def v4_signed_params_for_url(params, expires)
253
+ # Read the correlated credential state once, as a single reference,
254
+ # so a concurrent refresh cannot pair a stale session token with a
255
+ # freshly rebuilt signer.
256
+ credentials = @credentials
226
257
  now = Fog::Time.now
227
258
 
228
259
  expires = expires - now.to_i
@@ -232,35 +263,36 @@ module Fog
232
263
  params[:query]['X-Amz-Expires'] = expires
233
264
  params[:query]['X-Amz-Date'] = now.to_iso8601_basic
234
265
 
235
- if @aws_session_token
236
- params[:query]['X-Amz-Security-Token'] = @aws_session_token
266
+ if credentials.aws_session_token
267
+ params[:query]['X-Amz-Security-Token'] = credentials.aws_session_token
237
268
  end
238
269
 
239
270
  params = request_params(params)
240
271
  params[:headers][:host] = params[:host]
241
272
  params[:headers][:host] += ":#{params[:port]}" if params.fetch(:port, nil)
242
273
 
243
- signature_query_params = @signer.signature_parameters(params, now, "UNSIGNED-PAYLOAD")
274
+ signature_query_params = credentials.signer.signature_parameters(params, now, "UNSIGNED-PAYLOAD")
244
275
  params[:query] = (params[:query] || {}).merge(signature_query_params)
245
276
  params
246
277
  end
247
278
 
248
279
  def v2_signed_params_for_url(params, expires)
249
- if @aws_session_token
280
+ credentials = @credentials
281
+ if credentials.aws_session_token
250
282
  params[:headers]||= {}
251
- params[:headers]['x-amz-security-token'] = @aws_session_token
283
+ params[:headers]['x-amz-security-token'] = credentials.aws_session_token
252
284
  end
253
- signature = signature_v2(params, expires)
285
+ signature = signature_v2(params, expires, credentials.hmac)
254
286
 
255
287
  params = request_params(params)
256
288
 
257
289
  signature_query_params = {
258
- 'AWSAccessKeyId' => @aws_access_key_id,
290
+ 'AWSAccessKeyId' => credentials.aws_access_key_id,
259
291
  'Signature' => signature,
260
292
  'Expires' => expires,
261
293
  }
262
294
  params[:query] = (params[:query] || {}).merge(signature_query_params)
263
- params[:query]['x-amz-security-token'] = @aws_session_token if @aws_session_token
295
+ params[:query]['x-amz-security-token'] = credentials.aws_session_token if credentials.aws_session_token
264
296
  params
265
297
  end
266
298
 
@@ -511,25 +543,32 @@ module Fog
511
543
  end
512
544
 
513
545
  def data
514
- self.class.data[@region][@aws_access_key_id]
546
+ self.class.data[@region][@credentials.aws_access_key_id]
515
547
  end
516
548
 
517
549
  def reset_data
518
- self.class.data[@region].delete(@aws_access_key_id)
550
+ self.class.data[@region].delete(@credentials.aws_access_key_id)
519
551
  end
520
552
 
521
553
  def setup_credentials(options)
522
554
  @aws_credentials_refresh_threshold_seconds = options[:aws_credentials_refresh_threshold_seconds]
523
-
524
- @aws_access_key_id = options[:aws_access_key_id]
525
- @aws_secret_access_key = options[:aws_secret_access_key]
526
- @aws_session_token = options[:aws_session_token]
527
555
  @aws_credentials_expire_at = options[:aws_credentials_expire_at]
528
556
 
529
- @signer = Fog::AWS::SignatureV4.new( @aws_access_key_id, @aws_secret_access_key, @region, 's3')
557
+ aws_access_key_id = options[:aws_access_key_id]
558
+ aws_secret_access_key = options[:aws_secret_access_key]
559
+ signer = Fog::AWS::SignatureV4.new(aws_access_key_id, aws_secret_access_key, @region, 's3')
560
+
561
+ # Swap the whole credential set as one reference so concurrent
562
+ # readers always observe a complete, self-consistent snapshot.
563
+ @credentials = Credentials.new(
564
+ :aws_access_key_id => aws_access_key_id,
565
+ :aws_secret_access_key => aws_secret_access_key,
566
+ :aws_session_token => options[:aws_session_token],
567
+ :signer => signer
568
+ )
530
569
  end
531
570
 
532
- def signature_v2(params, expires)
571
+ def signature_v2(params, expires, hmac = nil)
533
572
  'foo'
534
573
  end
535
574
 
@@ -612,16 +651,30 @@ module Fog
612
651
  @aws_credentials_refresh_threshold_seconds = options[:aws_credentials_refresh_threshold_seconds]
613
652
  end
614
653
 
615
- @aws_access_key_id = options[:aws_access_key_id]
616
- @aws_secret_access_key = options[:aws_secret_access_key]
617
- @aws_session_token = options[:aws_session_token]
618
654
  @aws_credentials_expire_at = options[:aws_credentials_expire_at]
619
655
 
656
+ aws_access_key_id = options[:aws_access_key_id]
657
+ aws_secret_access_key = options[:aws_secret_access_key]
658
+
659
+ signer = hmac = nil
620
660
  if @signature_version == 4
621
- @signer = Fog::AWS::SignatureV4.new(@aws_access_key_id, @aws_secret_access_key, @region, 's3')
661
+ signer = Fog::AWS::SignatureV4.new(aws_access_key_id, aws_secret_access_key, @region, 's3')
622
662
  elsif @signature_version == 2
623
- @hmac = Fog::HMAC.new('sha1', @aws_secret_access_key)
624
- end
663
+ hmac = Fog::HMAC.new('sha1', aws_secret_access_key)
664
+ end
665
+
666
+ # Publish the new credentials as a single frozen reference. This is
667
+ # the only write to the correlated credential state, so a reader
668
+ # that grabs @credentials once (see #request) can never observe a
669
+ # torn mix of old and new values, even while a refresh is running
670
+ # on another thread.
671
+ @credentials = Credentials.new(
672
+ :aws_access_key_id => aws_access_key_id,
673
+ :aws_secret_access_key => aws_secret_access_key,
674
+ :aws_session_token => options[:aws_session_token],
675
+ :signer => signer,
676
+ :hmac => hmac
677
+ )
625
678
  end
626
679
 
627
680
  def connection(scheme, host, port)
@@ -641,25 +694,41 @@ module Fog
641
694
  def request(params, &block)
642
695
  refresh_credentials_if_expired
643
696
 
697
+ # Read the correlated credential state exactly once, into a local, so
698
+ # a concurrent refresh cannot swap the signer out from under the
699
+ # session token partway through this request. See the Credentials
700
+ # class for the full rationale.
701
+ perform_request(params, @credentials, &block)
702
+ end
703
+
704
+ # Signs and dispatches a single request using the given credential
705
+ # snapshot. Splitting this out of #request lets the redirect retry in
706
+ # #_request reuse one consistent snapshot (with a signer rebuilt for
707
+ # the new region) instead of re-reading, and keeps #request's public
708
+ # signature unchanged.
709
+ def perform_request(params, credentials, &block)
710
+ signer = credentials.signer
711
+
644
712
  date = Fog::Time.now
645
713
 
646
714
  params = params.dup
647
715
  stringify_query_keys(params)
648
716
  params[:headers] = (params[:headers] || {}).dup
649
717
 
650
- params[:headers]['x-amz-security-token'] = @aws_session_token if @aws_session_token
718
+ params[:headers]['x-amz-security-token'] = credentials.aws_session_token if credentials.aws_session_token
651
719
 
652
720
  if @signature_version == 2
653
721
  expires = date.to_date_header
654
722
  params[:headers]['Date'] = expires
655
- params[:headers]['Authorization'] = "AWS #{@aws_access_key_id}:#{signature_v2(params, expires)}"
723
+ params[:headers]['Authorization'] = "AWS #{credentials.aws_access_key_id}:#{signature_v2(params, expires, credentials.hmac)}"
656
724
  end
657
725
 
658
726
  params = request_params(params)
659
727
  scheme = params.delete(:scheme)
660
728
  host = params.delete(:host)
661
- port = params.delete(:port) || DEFAULT_SCHEME_PORT[scheme]
662
- params[:headers]['Host'] = host
729
+ port = params.delete(:port)
730
+ params[:headers]['Host'] = port ? "#{host}:#{port}" : host
731
+ port ||= DEFAULT_SCHEME_PORT[scheme]
663
732
 
664
733
 
665
734
  if @signature_version == 4
@@ -679,12 +748,12 @@ module Fog
679
748
  else
680
749
  params[:headers]['x-amz-content-sha256'] ||= OpenSSL::Digest::SHA256.hexdigest(params[:body] || '')
681
750
  end
682
- signature_components = @signer.signature_components(params, date, params[:headers]['x-amz-content-sha256'])
683
- params[:headers]['Authorization'] = @signer.components_to_header(signature_components)
751
+ signature_components = signer.signature_components(params, date, params[:headers]['x-amz-content-sha256'])
752
+ params[:headers]['Authorization'] = signer.components_to_header(signature_components)
684
753
 
685
754
  if params[:body].respond_to?(:read) && @enable_signature_v4_streaming
686
755
  body = params.delete :body
687
- params[:request_block] = S3Streamer.new(body, signature_components['X-Amz-Signature'], @signer, date)
756
+ params[:request_block] = S3Streamer.new(body, signature_components['X-Amz-Signature'], signer, date)
688
757
  end
689
758
  end
690
759
  # FIXME: ToHashParser should make this not needed
@@ -712,23 +781,32 @@ module Fog
712
781
  new_params[:bucket_name] = %r{<Bucket>([^<]*)</Bucket>}.match(body).captures.first
713
782
  new_params[:host] = %r{<Endpoint>([^<]*)</Endpoint>}.match(body).captures.first
714
783
  # some errors provide it directly
715
- @new_region = %r{<Region>([^<]*)</Region>}.match(body) ? Regexp.last_match.captures.first : nil
784
+ new_region = %r{<Region>([^<]*)</Region>}.match(body) ? Regexp.last_match.captures.first : nil
716
785
  end
717
786
  Fog::Logger.warning("fog: followed redirect to #{host}, connecting to the matching region will be more performant")
718
- original_region, original_signer = @region, @signer
719
- @region = @new_region || case new_params[:host]
787
+ new_region ||= case new_params[:host]
720
788
  when /s3.amazonaws.com/, /s3-external-1.amazonaws.com/
721
789
  DEFAULT_REGION
722
790
  else
723
791
  %r{s3[\.\-]([^\.]*).amazonaws.com}.match(new_params[:host]).captures.first
724
792
  end
793
+ # Take a fresh, self-consistent snapshot and build a signer for the
794
+ # redirect region locally, then re-dispatch with it. We deliberately
795
+ # do not mutate @region/@signer: doing so would corrupt any
796
+ # concurrent request sharing this connection.
797
+ credentials = @credentials
725
798
  if @signature_version == 4
726
- @signer = Fog::AWS::SignatureV4.new(@aws_access_key_id, @aws_secret_access_key, @region, 's3')
799
+ redirect_signer = Fog::AWS::SignatureV4.new(credentials.aws_access_key_id, credentials.aws_secret_access_key, new_region, 's3')
800
+ credentials = Credentials.new(
801
+ :aws_access_key_id => credentials.aws_access_key_id,
802
+ :aws_secret_access_key => credentials.aws_secret_access_key,
803
+ :aws_session_token => credentials.aws_session_token,
804
+ :signer => redirect_signer,
805
+ :hmac => credentials.hmac
806
+ )
727
807
  original_params[:headers].delete('Authorization')
728
808
  end
729
- response = request(original_params.merge(new_params), &block)
730
- @region, @signer = original_region, original_signer
731
- response
809
+ perform_request(original_params.merge(new_params), credentials, &block)
732
810
  end
733
811
 
734
812
  # See http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html
@@ -792,7 +870,7 @@ DATA
792
870
  end
793
871
  end
794
872
 
795
- def signature_v2(params, expires)
873
+ def signature_v2(params, expires, hmac = @credentials.hmac)
796
874
  headers = params[:headers] || {}
797
875
 
798
876
  string_to_sign =
@@ -843,7 +921,7 @@ DATA
843
921
  end
844
922
  canonical_resource << query_string
845
923
  string_to_sign << canonical_resource
846
- signed_string = @hmac.sign(string_to_sign)
924
+ signed_string = hmac.sign(string_to_sign)
847
925
  Base64.encode64(signed_string).chomp!
848
926
  end
849
927
 
@@ -1,5 +1,5 @@
1
1
  module Fog
2
2
  module AWS
3
- VERSION = '3.33.1'.freeze
3
+ VERSION = '3.33.3'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fog-aws
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.33.1
4
+ version: 3.33.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Lane
@@ -1499,7 +1499,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1499
1499
  - !ruby/object:Gem::Version
1500
1500
  version: '0'
1501
1501
  requirements: []
1502
- rubygems_version: 3.6.9
1502
+ rubygems_version: 4.0.3
1503
1503
  specification_version: 4
1504
1504
  summary: Module for the 'fog' gem to support Amazon Web Services.
1505
1505
  test_files: []