From 4912978fb42e79d9e08e0cb85293a02014114711 Mon Sep 17 00:00:00 2001 From: Alex Woods Date: Mon, 23 Oct 2023 15:41:34 -0700 Subject: [PATCH 1/2] Allow using expired credentials in presigned url to support static stability --- gems/aws-sigv4/lib/aws-sigv4/signer.rb | 19 ++++--- gems/aws-sigv4/spec/signer_spec.rb | 72 ++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/gems/aws-sigv4/lib/aws-sigv4/signer.rb b/gems/aws-sigv4/lib/aws-sigv4/signer.rb index ea0ad46f388..ea8a1d7b225 100644 --- a/gems/aws-sigv4/lib/aws-sigv4/signer.rb +++ b/gems/aws-sigv4/lib/aws-sigv4/signer.rb @@ -423,7 +423,7 @@ def presign_url(options) params['X-Amz-Algorithm'] = 'AWS4-HMAC-SHA256' params['X-Amz-Credential'] = credential(creds, date) params['X-Amz-Date'] = datetime - params['X-Amz-Expires'] = presigned_url_expiration(options, expiration).to_s + params['X-Amz-Expires'] = presigned_url_expiration(options, expiration, Time.strptime(datetime, "%Y%m%dT%H%M%S%Z")).to_s params['X-Amz-Security-Token'] = creds.session_token if creds.session_token params['X-Amz-SignedHeaders'] = signed_headers(headers) @@ -722,12 +722,19 @@ def credentials_set?(credentials) !credentials.secret_access_key.empty? end - def presigned_url_expiration(options, expiration) + def presigned_url_expiration(options, expiration, datetime) expires_in = extract_expires_in(options) return expires_in unless expiration - expiration_seconds = (expiration - Time.now).to_i - [expires_in, expiration_seconds].min + expiration_seconds = (expiration - datetime).to_i + # In the static stability case, credentials may expire in the past + # but still be valid. For those cases, use the user configured + # expires_in and ingore expiration. + if expiration_seconds <= 0 + expires_in + else + [expires_in, expiration_seconds].min + end end ### CRT Code @@ -811,7 +818,7 @@ def crt_presign_url(options) headers = downcase_headers(options[:headers]) headers['host'] ||= host(url) - datetime = headers.delete('x-amz-date') + datetime = Time.strptime(headers.delete('x-amz-date'), "%Y%m%dT%H%M%S%Z") if headers['x-amz-date'] datetime ||= (options[:time] || Time.now) content_sha256 = headers.delete('x-amz-content-sha256') @@ -832,7 +839,7 @@ def crt_presign_url(options) use_double_uri_encode: @uri_escape_path, should_normalize_uri_path: @normalize_path, omit_session_token: @omit_session_token, - expiration_in_seconds: presigned_url_expiration(options, expiration) + expiration_in_seconds: presigned_url_expiration(options, expiration, datetime) ) http_request = Aws::Crt::Http::Message.new( http_method, url.to_s, headers diff --git a/gems/aws-sigv4/spec/signer_spec.rb b/gems/aws-sigv4/spec/signer_spec.rb index ee3f9759cc6..385cef1911d 100644 --- a/gems/aws-sigv4/spec/signer_spec.rb +++ b/gems/aws-sigv4/spec/signer_spec.rb @@ -603,6 +603,78 @@ module Sigv4 end end + + describe '#presign_url' do + let(:now) { Time.now } + + let(:credentials) do + { + access_key_id: 'akid', + secret_access_key: 'secret', + session_token: nil, + expiration: expiration + } + end + + let(:signer_options) do + { + service: 'SERVICE', + region: 'REGION', + credentials_provider: double(credentials: double(credentials), expiration: expiration), + } + end + + let(:presign_options) do + { + http_method: 'GET', + url: 'https://example.com', + expires_in: expires_in, + time: now + } + end + + let(:expires_in) { 60 } + + let(:signer) { Signer.new(signer_options) } + + context 'expiration is nil' do + let(:expiration) { nil } + + it 'creates a presigned url with the provided expires_at' do + url = signer.presign_url(presign_options) + expect(url.to_s).to include('X-Amz-Expires=60') + end + end + + context 'expiration is after expires_at' do + let(:expiration) { now + 3600 } + + it 'creates a presigned url with the provided expires_at' do + url = signer.presign_url(presign_options) + expect(url.to_s).to include('X-Amz-Expires=60') + end + end + + context 'expiration is before expires_at' do + let(:expiration) { now + 10 } + + it 'creates a presigned url that expires at the credential expiration time' do + url = signer.presign_url(presign_options) + expect(url.to_s).to include('X-Amz-Expires=10') + end + end + + context 'expired credentials (static stability)' do + let(:expiration) { now - 10 } + + it 'creates a presigned url with the provided expires_at' do + url = signer.presign_url(presign_options) + expect(url.to_s).to include('X-Amz-Expires=60') + end + end + + + end end end end From d92e6f74d92824bdb7bd4724c61f0206169e8d2c Mon Sep 17 00:00:00 2001 From: Alex Woods Date: Tue, 24 Oct 2023 12:43:32 -0700 Subject: [PATCH 2/2] Add Changelog --- gems/aws-sigv4/CHANGELOG.md | 2 ++ gems/aws-sigv4/spec/signer_spec.rb | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gems/aws-sigv4/CHANGELOG.md b/gems/aws-sigv4/CHANGELOG.md index 736311266ec..41e02edd701 100644 --- a/gems/aws-sigv4/CHANGELOG.md +++ b/gems/aws-sigv4/CHANGELOG.md @@ -1,6 +1,8 @@ Unreleased Changes ------------------ +* Issue - (Static Stability) use provided `expires_in` in presigned url when credentials are expired. + 1.6.0 (2023-06-28) ------------------ diff --git a/gems/aws-sigv4/spec/signer_spec.rb b/gems/aws-sigv4/spec/signer_spec.rb index 385cef1911d..e8b1e2b5ad3 100644 --- a/gems/aws-sigv4/spec/signer_spec.rb +++ b/gems/aws-sigv4/spec/signer_spec.rb @@ -672,8 +672,6 @@ module Sigv4 expect(url.to_s).to include('X-Amz-Expires=60') end end - - end end end