Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions gems/aws-sdk-core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
------------------

Expand Down
14 changes: 12 additions & 2 deletions gems/aws-sdk-core/lib/aws-sdk-core/ecs_credentials.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to double check - .body will always give a string and not an IO right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I believe it will always return a string. From https://ruby-doc.org/3.3.2/stdlibs/net/Net/HTTP.html regarding Net::HTTP.get:

Sends a GET request and returns the HTTP response body as a string.


response.body
end
Expand Down
18 changes: 14 additions & 4 deletions gems/aws-sdk-core/lib/aws-sdk-core/instance_profile_credentials.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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?
Expand Down Expand Up @@ -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

Expand All @@ -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

Expand Down
18 changes: 18 additions & 0 deletions gems/aws-sdk-core/spec/aws/ecs_credentials_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions gems/aws-sdk-core/spec/aws/instance_profile_credentials_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down