diff --git a/gems/aws-sdk-core/CHANGELOG.md b/gems/aws-sdk-core/CHANGELOG.md index 19ccc9d90c3..4355c7330c4 100644 --- a/gems/aws-sdk-core/CHANGELOG.md +++ b/gems/aws-sdk-core/CHANGELOG.md @@ -1,6 +1,8 @@ Unreleased Changes ------------------ +* Feature - Include HTTP status code and body in errors whehn retrieving ECS credentials and Instance Profile credentials. + 3.241.4 (2026-01-16) ------------------ diff --git a/gems/aws-sdk-core/lib/aws-sdk-core/ecs_credentials.rb b/gems/aws-sdk-core/lib/aws-sdk-core/ecs_credentials.rb index 6c18f128c4d..a593e9dbbce 100644 --- a/gems/aws-sdk-core/lib/aws-sdk-core/ecs_credentials.rb +++ b/gems/aws-sdk-core/lib/aws-sdk-core/ecs_credentials.rb @@ -15,7 +15,17 @@ class ECSCredentials include RefreshingCredentials # @api private - class Non200Response < RuntimeError; end + class Non200Response < RuntimeError + attr_reader :status_code, :body + + def initialize(status_code, body = nil) + @status_code = status_code + @body = body + msg = "HTTP #{status_code}" + msg += ": #{body}" if body && !body.empty? + super(msg) + end + end # Raised when the token file cannot be read. class TokenFileReadError < RuntimeError; end @@ -251,7 +261,7 @@ def http_get(connection, path) request = Net::HTTP::Get.new(path) set_authorization_token(request) response = connection.request(request) - raise Non200Response unless response.code.to_i == 200 + raise Non200Response.new(response.code.to_i, response.body) unless response.code.to_i == 200 response.body end diff --git a/gems/aws-sdk-core/lib/aws-sdk-core/instance_profile_credentials.rb b/gems/aws-sdk-core/lib/aws-sdk-core/instance_profile_credentials.rb index 338d76812b5..d75ffe15649 100644 --- a/gems/aws-sdk-core/lib/aws-sdk-core/instance_profile_credentials.rb +++ b/gems/aws-sdk-core/lib/aws-sdk-core/instance_profile_credentials.rb @@ -26,7 +26,17 @@ class InstanceProfileCredentials include RefreshingCredentials # @api private - class Non200Response < RuntimeError; end + class Non200Response < RuntimeError + attr_reader :status_code, :body + + def initialize(status_code, body = nil) + @status_code = status_code + @body = body + msg = "HTTP #{status_code}" + msg += ": #{body}" if body && !body.empty? + super(msg) + end + end # @api private class TokenRetrivalError < RuntimeError; end @@ -249,7 +259,7 @@ def fetch_credentials(conn) # The next retry should fetch it @token = nil @imds_v1_fallback = false - raise Non200Response + raise Non200Response.new(401, 'Token expired') end def token_set? @@ -278,7 +288,7 @@ def http_get(connection, path) when 401 raise TokenExpiredError else - raise Non200Response + raise Non200Response.new(response.code.to_i, response.body) end end @@ -298,7 +308,7 @@ def http_put(connection) when 400 raise TokenRetrivalError else - raise Non200Response + raise Non200Response.new(response.code.to_i, response.body) end end diff --git a/gems/aws-sdk-core/spec/aws/ecs_credentials_spec.rb b/gems/aws-sdk-core/spec/aws/ecs_credentials_spec.rb index 8c963c0d1d0..0d28aa5a67f 100644 --- a/gems/aws-sdk-core/spec/aws/ecs_credentials_spec.rb +++ b/gems/aws-sdk-core/spec/aws/ecs_credentials_spec.rb @@ -122,6 +122,24 @@ module Aws ECSCredentials.new end.to raise_error(ArgumentError, /without a credential path/) end + + it 'returns empty credentials on non-200 response with error details' do + stub_request(:get, "http://169.254.170.2#{path}") + .to_return(status: 429, body: 'Rate limit exceeded') + expect_any_instance_of(ECSCredentials).to receive(:warn) + .with(/Error retrieving ECS Credentials: HTTP 429: Rate limit exceeded/) + c = ECSCredentials.new(backoff: 0, retries: 0) + expect(c.set?).to be(false) + end + + it 'returns empty credentials on non-200 response without body' do + stub_request(:get, "http://169.254.170.2#{path}") + .to_return(status: 500, body: '') + expect_any_instance_of(ECSCredentials).to receive(:warn) + .with(/Error retrieving ECS Credentials: HTTP 500/) + c = ECSCredentials.new(backoff: 0, retries: 0) + expect(c.set?).to be(false) + end end context 'retries' do diff --git a/gems/aws-sdk-core/spec/aws/instance_profile_credentials_spec.rb b/gems/aws-sdk-core/spec/aws/instance_profile_credentials_spec.rb index 9d60d1b9896..0e644accc75 100644 --- a/gems/aws-sdk-core/spec/aws/instance_profile_credentials_spec.rb +++ b/gems/aws-sdk-core/spec/aws/instance_profile_credentials_spec.rb @@ -451,6 +451,26 @@ module Aws expect(c.credentials.session_token).to be(nil) expect(c.expiration).to be(nil) end + + it 'returns empty credentials on non-200 response from profile endpoint' do + stub_request(:get, "#{ipv4_endpoint_creds_path}profile-name") + .with(headers: { 'x-aws-ec2-metadata-token' => 'my-token' }) + .to_return(status: 404, body: 'Not Found') + expect_any_instance_of(InstanceProfileCredentials).to receive(:warn) + .with(/Error retrieving instance profile credentials: HTTP 404: Not Found/) + c = InstanceProfileCredentials.new(backoff: 0, retries: 0) + expect(c.set?).to be(false) + end + + it 'returns empty credentials on non-200 response from metadata service' do + stub_request(:get, ipv4_endpoint + path) + .with(headers: { 'x-aws-ec2-metadata-token' => 'my-token' }) + .to_return(status: 503, body: 'Service Unavailable') + expect_any_instance_of(InstanceProfileCredentials).to receive(:warn) + .with(/Error retrieving instance profile credentials: HTTP 503: Service Unavailable/) + c = InstanceProfileCredentials.new(backoff: 0, retries: 0) + expect(c.set?).to be(false) + end end end