From f7ba772833cc3797541d9b1b5a2a5a672e7b3bc8 Mon Sep 17 00:00:00 2001 From: Matt Muller Date: Mon, 20 Nov 2023 18:59:16 -0500 Subject: [PATCH] Revert "Generalized http ecs (#2952)" This reverts commit 5bcd45c809b7c578d5490d0152947d832c642a66. --- gems/aws-sdk-core/CHANGELOG.md | 2 - .../lib/aws-sdk-core/ecs_credentials.rb | 86 +---- .../spec/aws/ecs_credentials_request.json | 159 --------- .../spec/aws/ecs_credentials_response.json | 72 ----- .../spec/aws/ecs_credentials_spec.rb | 301 +++++------------- 5 files changed, 98 insertions(+), 522 deletions(-) delete mode 100644 gems/aws-sdk-core/spec/aws/ecs_credentials_request.json delete mode 100644 gems/aws-sdk-core/spec/aws/ecs_credentials_response.json diff --git a/gems/aws-sdk-core/CHANGELOG.md b/gems/aws-sdk-core/CHANGELOG.md index f4c18ab570a..ef3dd10fbe6 100644 --- a/gems/aws-sdk-core/CHANGELOG.md +++ b/gems/aws-sdk-core/CHANGELOG.md @@ -1,8 +1,6 @@ Unreleased Changes ------------------ -* Feature - Support `AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE` in `ECSCredentials` and also allow for ECS and EKS link-local http addresses. - 3.187.1 (2023-11-20) ------------------ 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 f2dcc879dce..f0f5d36e765 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 @@ -6,7 +6,7 @@ module Aws # An auto-refreshing credential provider that loads credentials from - # instances running in containers. + # instances running in ECS. # # ecs_credentials = Aws::ECSCredentials.new(retries: 3) # ec2 = Aws::EC2::Client.new(credentials: ecs_credentials) @@ -17,12 +17,6 @@ class ECSCredentials # @api private class Non200Response < RuntimeError; end - # Raised when the token file cannot be read. - class TokenFileReadError < RuntimeError; end - - # Raised when the token file is invalid. - class InvalidTokenError < RuntimeError; end - # These are the errors we trap when attempting to talk to the # instance metadata service. Any of these imply the service # is not present, no responding or some other non-recoverable @@ -47,7 +41,7 @@ class InvalidTokenError < RuntimeError; end # is set and `credential_path` is not set. # @option options [String] :credential_path By default, the value of the # AWS_CONTAINER_CREDENTIALS_RELATIVE_URI environment variable. - # @option options [String] :endpoint The container credential endpoint. + # @option options [String] :endpoint The ECS credential endpoint. # By default, this is the value of the AWS_CONTAINER_CREDENTIALS_FULL_URI # environment variable. This value is ignored if `credential_path` or # ENV['AWS_CONTAINER_CREDENTIALS_RELATIVE_URI'] is set. @@ -70,6 +64,7 @@ def initialize(options = {}) endpoint = options[:endpoint] || ENV['AWS_CONTAINER_CREDENTIALS_FULL_URI'] initialize_uri(options, credential_path, endpoint) + @authorization_token = ENV['AWS_CONTAINER_AUTHORIZATION_TOKEN'] @retries = options[:retries] || 5 @http_open_timeout = options[:http_open_timeout] || 5 @@ -108,18 +103,11 @@ def initialize_relative_uri(options, path) def initialize_full_uri(endpoint) uri = URI.parse(endpoint) - validate_full_uri_scheme!(uri) validate_full_uri!(uri) - @host = uri.hostname + @host = uri.host @port = uri.port @scheme = uri.scheme - @credential_path = uri.request_uri - end - - def validate_full_uri_scheme!(full_uri) - return if full_uri.is_a?(URI::HTTP) || full_uri.is_a?(URI::HTTPS) - - raise ArgumentError, "'#{full_uri}' must be a valid HTTP or HTTPS URI" + @credential_path = uri.path end # Validate that the full URI is using a loopback address if scheme is http. @@ -127,24 +115,19 @@ def validate_full_uri!(full_uri) return unless full_uri.scheme == 'http' begin - return if valid_ip_address?(IPAddr.new(full_uri.host)) + return if ip_loopback?(IPAddr.new(full_uri.host)) rescue IPAddr::InvalidAddressError addresses = Resolv.getaddresses(full_uri.host) - return if addresses.all? { |addr| valid_ip_address?(IPAddr.new(addr)) } + return if addresses.all? { |addr| ip_loopback?(IPAddr.new(addr)) } end raise ArgumentError, - 'AWS_CONTAINER_CREDENTIALS_FULL_URI must use a local loopback '\ - 'or an ECS or EKS link-local address when using the http scheme.' - end - - def valid_ip_address?(ip_address) - ip_loopback?(ip_address) || ecs_or_eks_ip?(ip_address) + 'AWS_CONTAINER_CREDENTIALS_FULL_URI must use a loopback '\ + 'address when using the http scheme.' end # loopback? method is available in Ruby 2.5+ # Replicate the logic here. - # loopback (IPv4 127.0.0.0/8, IPv6 ::1/128) def ip_loopback?(ip_address) case ip_address.family when Socket::AF_INET @@ -156,20 +139,6 @@ def ip_loopback?(ip_address) end end - # Verify that the IP address is a link-local address from ECS or EKS. - # ECS container host (IPv4 `169.254.170.2`) - # EKS container host (IPv4 `169.254.170.23`, IPv6 `fd00:ec2::23`) - def ecs_or_eks_ip?(ip_address) - case ip_address.family - when Socket::AF_INET - [0xa9feaa02, 0xa9feaa17].include?(ip_address) - when Socket::AF_INET6 - ip_address == 0xfd00_0ec2_0000_0000_0000_0000_0000_0023 - else - false - end - end - def backoff(backoff) case backoff when Proc then backoff @@ -205,36 +174,10 @@ def get_credentials http_get(conn, @credential_path) end end - rescue TokenFileReadError, InvalidTokenError - raise rescue StandardError '{}' end - def fetch_authorization_token - if (path = ENV['AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE']) - fetch_authorization_token_file(path) - elsif (token = ENV['AWS_CONTAINER_AUTHORIZATION_TOKEN']) - token - end - end - - def fetch_authorization_token_file(path) - File.read(path).strip - rescue Errno::ENOENT - raise TokenFileReadError, - 'AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE is set '\ - "but the file doesn't exist: #{path}" - end - - def validate_authorization_token!(token) - return unless token.include?("\r\n") - - raise InvalidTokenError, - 'Invalid Authorization token: token contains '\ - 'a newline and carriage return character.' - end - def open_connection http = Net::HTTP.new(@host, @port, nil) http.open_timeout = @http_open_timeout @@ -247,27 +190,18 @@ def open_connection def http_get(connection, path) request = Net::HTTP::Get.new(path) - set_authorization_token(request) + request['Authorization'] = @authorization_token if @authorization_token response = connection.request(request) raise Non200Response unless response.code.to_i == 200 response.body end - def set_authorization_token(request) - if (authorization_token = fetch_authorization_token) - validate_authorization_token!(authorization_token) - request['Authorization'] = authorization_token - end - end - def retry_errors(error_classes, options = {}) max_retries = options[:max_retries] retries = 0 begin yield - rescue TokenFileReadError, InvalidTokenError - raise rescue *error_classes => _e raise unless retries < max_retries diff --git a/gems/aws-sdk-core/spec/aws/ecs_credentials_request.json b/gems/aws-sdk-core/spec/aws/ecs_credentials_request.json deleted file mode 100644 index 498f676f85f..00000000000 --- a/gems/aws-sdk-core/spec/aws/ecs_credentials_request.json +++ /dev/null @@ -1,159 +0,0 @@ -[ - { - "description": "should reject forbidden host in full URI", - "env": { - "AWS_CONTAINER_CREDENTIALS_FULL_URI": "http://192.168.1.1/endpoint" - }, - "expect": { - "type": "error", - "reason": "'192.168.1.1' is not an allowed host" - } - }, - { - "description": "should reject forbidden link-local host in full URI", - "env": { - "AWS_CONTAINER_CREDENTIALS_FULL_URI": "http://169.254.170.3/endpoint" - }, - "expect": { - "type": "error", - "reason": "169.254.170.3' is not an allowed host" - } - }, - { - "description": "should reject invalid token file path", - "env": { - "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI": "/endpoint", - "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE": "/full/path/to/token/file" - }, - "token_file": { - "type": "error", - "errno": "ENOENT" - }, - "expect": { - "type": "error", - "reason": "failed to read authorization token from '/full/path/to/token/file': no such file or directory" - } - }, - { - "description": "https URI", - "env": { - "AWS_CONTAINER_CREDENTIALS_FULL_URI": "https://awscredentials.amazonaws.com/credentials" - }, - "expect": { - "type": "success", - "request": { - "method": "GET", - "uri": "https://awscredentials.amazonaws.com/credentials", - "headers": {} - } - } - }, - { - "description": "http loopback(v4) URI", - "env": { - "AWS_CONTAINER_CREDENTIALS_FULL_URI": "http://127.0.0.2/credentials" - }, - "expect": { - "type": "success", - "request": { - "method": "GET", - "uri": "http://127.0.0.2/credentials", - "headers": {} - } - } - }, - { - "description": "http loopback(v6) URI", - "env": { - "AWS_CONTAINER_CREDENTIALS_FULL_URI": "http://[::1]/credentials" - }, - "expect": { - "type": "success", - "request": { - "method": "GET", - "uri": "http://[::1]/credentials", - "headers": {} - } - } - }, - { - "description": "http link-local ECS URI", - "env": { - "AWS_CONTAINER_CREDENTIALS_FULL_URI": "http://169.254.170.2/credentials" - }, - "expect": { - "type": "success", - "request": { - "method": "GET", - "uri": "http://169.254.170.2/credentials", - "headers": {} - } - } - }, - { - "description": "http link-local EKS URI", - "env": { - "AWS_CONTAINER_CREDENTIALS_FULL_URI": "http://169.254.170.23/credentials" - }, - "expect": { - "type": "success", - "request": { - "method": "GET", - "uri": "http://169.254.170.23/credentials", - "headers": {} - } - } - }, - { - "description": "complex full URI", - "env": { - "AWS_CONTAINER_CREDENTIALS_FULL_URI": "http://127.0.0.1:8080/credentials?foo=bar%20baz" - }, - "expect": { - "type": "success", - "request": { - "method": "GET", - "uri": "http://127.0.0.1:8080/credentials?foo=bar%20baz", - "headers": {} - } - } - }, - { - "description": "auth token from file", - "env": { - "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI": "/credentials-relative", - "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE": "/path/to/token" - }, - "token_file": { - "type": "success", - "content": "Basic static%20token" - }, - "expect": { - "type": "success", - "request": { - "method": "GET", - "uri": "http://169.254.170.2/credentials-relative", - "headers": { - "Authorization": "Basic static%20token" - } - } - } - }, - { - "description": "auth token from env", - "env": { - "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI": "/credentials-relative", - "AWS_CONTAINER_AUTHORIZATION_TOKEN": "Basic static%20token2" - }, - "expect": { - "type": "success", - "request": { - "method": "GET", - "uri": "http://169.254.170.2/credentials-relative", - "headers": { - "Authorization": "Basic static%20token2" - } - } - } - } -] \ No newline at end of file diff --git a/gems/aws-sdk-core/spec/aws/ecs_credentials_response.json b/gems/aws-sdk-core/spec/aws/ecs_credentials_response.json deleted file mode 100644 index 28341da7f14..00000000000 --- a/gems/aws-sdk-core/spec/aws/ecs_credentials_response.json +++ /dev/null @@ -1,72 +0,0 @@ -[ - { - "description": "application/json error response", - "response": { - "status": 401, - "headers": { - "Content-Type": "application/json" - }, - "body": "{\"Code\":\"TokenNotFound\"}" - }, - "expect": { - "type": "error", - "reason": "received error response from credentials endpoint: 401 Unauthorized", - "metadata": { - "Code": "TokenNotFound" - } - } - }, - { - "description": "4xx error response", - "response": { - "status": 429 - }, - "expect": { - "type": "error", - "reason": "received non-2xx response from credentials endpoint: 429 Too Many Requests", - "metadata": {} - } - }, - { - "description": "3xx error response", - "response": { - "status": 301 - }, - "expect": { - "type": "error", - "reason": "received non-2xx response from credentials endpoint: 301 Moved Permanently", - "metadata": {} - } - }, - { - "description": "5xx error response", - "response": { - "status": 500 - }, - "expect": { - "type": "error", - "reason": "received non-2xx response from credentials endpoint: 500 Internal Server Error", - "metadata": {} - } - }, - { - "description": "success", - "response": { - "status": 200, - "headers": { - "Content-Type": "application/json" - }, - "body": "{\"Foo\":\"Bar\",\"AccessKeyId\":\"foo\",\"SecretAccessKey\":\"bar\",\"Token\":\"baz\",\"AccountId\":\"qux\",\"Expiration\":\"2023-06-30T00:00:00.000Z\"}" - }, - "expect": { - "type": "success", - "credentials": { - "access_key_id": "foo", - "secret_access_key": "bar", - "session_token": "baz", - "account_id": "qux", - "expiration": "2023-06-30T00:00:00.000Z" - } - } - } -] \ No newline at end of file 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 f3b749fc903..f26d01d406b 100644 --- a/gems/aws-sdk-core/spec/aws/ecs_credentials_spec.rb +++ b/gems/aws-sdk-core/spec/aws/ecs_credentials_spec.rb @@ -15,8 +15,7 @@ module Aws ].each do |error_class| it "returns no credentials for #{error_class}" do stub_request(:get, "http://169.254.170.2#{path}").to_raise(error_class) - credentials = ECSCredentials.new(credential_path: path, backoff: 0, retries: 0) - expect(credentials.set?).to be(false) + expect(ECSCredentials.new(credential_path: path, backoff: 0).set?).to be(false) end end end @@ -56,7 +55,7 @@ module Aws end it 'populates credentials from the instance profile' do - c = ECSCredentials.new(backoff: 0, retries: 0) + c = ECSCredentials.new(backoff: 0) expect(c.credentials.access_key_id).to eq('akid') expect(c.credentials.secret_access_key).to eq('secret') expect(c.credentials.session_token).to eq('session-token') @@ -72,10 +71,58 @@ module Aws expect(c.expiration.to_s).to eq(expiration2.to_s) end + it 'retries if the first load fails' do + stub_request(:get, "http://169.254.170.2#{path}") + .to_return(status: 200, body: resp2) + c = ECSCredentials.new(backoff: 0) + expect(c.credentials.access_key_id).to eq('akid-2') + expect(c.credentials.secret_access_key).to eq('secret-2') + expect(c.credentials.session_token).to eq('session-token-2') + expect(c.expiration.to_s).to eq(expiration2.to_s) + end + + it 'retries if get profile response is invalid JSON' do + stub_request(:get, "http://169.254.170.2#{path}") + .to_return(status: 200, body: ' ') + .to_return(status: 200, body: '') + .to_return(status: 200, body: '{') + .to_return(status: 200, body: resp2) + c = ECSCredentials.new(backoff: 0) + expect(c.credentials.access_key_id).to eq('akid-2') + expect(c.credentials.secret_access_key).to eq('secret-2') + expect(c.credentials.session_token).to eq('session-token-2') + expect(c.expiration.to_s).to eq(expiration2.to_s) + end + + it 'retries invalid JSON exactly 3 times' do + stub_request(:get, "http://169.254.170.2#{path}") + .to_return(status: 200, body: '') + .to_return(status: 200, body: ' ') + .to_return(status: 200, body: '{') + .to_return(status: 200, body: ' ') + expect do + ECSCredentials.new(backoff: 0) + end.to raise_error( + Aws::Errors::MetadataParserError, + 'Failed to parse metadata service response.' + ) + end + + it 'retries errors parsing expiration time 3 times' do + stub_request(:get, "http://169.254.170.2#{path}") + .to_return(status: 200, body: '{ "Expiration": "Expiration" }') + .to_return(status: 200, body: '{ "Expiration": "Expiration" }') + .to_return(status: 200, body: '{ "Expiration": "Expiration" }') + .to_return(status: 200, body: '{ "Expiration": "Expiration" }') + expect do + ECSCredentials.new(backoff: 0) + end.to raise_error(ArgumentError) + end + it "ignores ENV['AWS_CONTAINER_CREDENTIALS_FULL_URI'] if also set" do # is not stubbed and should not be used ENV['AWS_CONTAINER_CREDENTIALS_FULL_URI'] = 'https://amazon.com:1234/path' - c = ECSCredentials.new(backoff: 0, retries: 0) + c = ECSCredentials.new(backoff: 0) expect(c.credentials.access_key_id).to eq('akid') expect(c.credentials.secret_access_key).to eq('secret') expect(c.credentials.session_token).to eq('session-token') @@ -118,110 +165,37 @@ module Aws end.to raise_error(ArgumentError, /without a credential path/) end end + end - context 'retries' do - it 'defaults to 5' do - stub_request(:get, "http://169.254.170.2#{path}").to_raise(SocketError) - expect(ECSCredentials.new(backoff: 0).retries).to be(5) - end - - it 'retries with exponential backoff' do - expected_request = - stub_request(:get, "http://169.254.170.2#{path}") - .to_raise(Errno::ECONNREFUSED) - expect(Kernel).to receive(:sleep).with(1) - expect(Kernel).to receive(:sleep).with(2) - expect(Kernel).to receive(:sleep).with(4) - ECSCredentials.new( - backoff: ->(n) { Kernel.sleep(2**n) }, - retries: 3 - ) - assert_requested(expected_request, times: 4) - end - - it 'retries if the first load fails' do - stub_request(:get, "http://169.254.170.2#{path}") - .to_return(status: 200, body: resp2) - c = ECSCredentials.new(backoff: 0, retries: 0) - expect(c.credentials.access_key_id).to eq('akid-2') - expect(c.credentials.secret_access_key).to eq('secret-2') - expect(c.credentials.session_token).to eq('session-token-2') - expect(c.expiration.to_s).to eq(expiration2.to_s) - end - - it 'retries if get profile response is invalid JSON' do - stub_request(:get, "http://169.254.170.2#{path}") - .to_return(status: 200, body: ' ') - .to_return(status: 200, body: '') - .to_return(status: 200, body: '{') - .to_return(status: 200, body: resp2) - c = ECSCredentials.new(backoff: 0, retries: 0) - expect(c.credentials.access_key_id).to eq('akid-2') - expect(c.credentials.secret_access_key).to eq('secret-2') - expect(c.credentials.session_token).to eq('session-token-2') - expect(c.expiration.to_s).to eq(expiration2.to_s) - end - - it 'retries invalid JSON exactly 3 times' do - stub_request(:get, "http://169.254.170.2#{path}") - .to_return(status: 200, body: '') - .to_return(status: 200, body: ' ') - .to_return(status: 200, body: '{') - .to_return(status: 200, body: ' ') - expect do - ECSCredentials.new(backoff: 0, retries: 0) - end.to raise_error( - Aws::Errors::MetadataParserError, - 'Failed to parse metadata service response.' - ) - end - - it 'retries errors parsing expiration time 3 times' do - stub_request(:get, "http://169.254.170.2#{path}") - .to_return(status: 200, body: '{ "Expiration": "Expiration" }') - .to_return(status: 200, body: '{ "Expiration": "Expiration" }') - .to_return(status: 200, body: '{ "Expiration": "Expiration" }') - .to_return(status: 200, body: '{ "Expiration": "Expiration" }') - expect do - ECSCredentials.new(backoff: 0, retries: 0) - end.to raise_error(ArgumentError) - end + context 'retries' do + before(:each) do + ENV['AWS_CONTAINER_CREDENTIALS_RELATIVE_URI'] = path end - context 'validating token' do - context 'AWS_CONTAINER_AUTHORIZATION_TOKEN' do - before do - ENV['AWS_CONTAINER_AUTHORIZATION_TOKEN'] = "bad\r\ntoken" - end - - it 'validates the token for carriage return and newline' do - expect do - ECSCredentials.new(backoff: 0, retries: 0) - end.to raise_error(ECSCredentials::InvalidTokenError) - end - end + it 'defaults to 0' do + stub_request(:get, "http://169.254.170.2#{path}").to_raise(SocketError) + expect(ECSCredentials.new(backoff: 0).retries).to be(5) + end - context 'AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE' do - before do - expect(File).to receive(:read).and_return("bad\r\ntoken") - ENV['AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE'] = '/some/path/to/file' - end - - it 'validates the token for carriage return and newline' do - expect do - ECSCredentials.new(backoff: 0, retries: 0) - end.to raise_error(ECSCredentials::InvalidTokenError) - end - end + it 'keeps trying "retries" times, with exponential backoff' do + expected_request = stub_request(:get, "http://169.254.170.2#{path}") + .to_raise(Errno::ECONNREFUSED) + expect(Kernel).to receive(:sleep).with(1) + expect(Kernel).to receive(:sleep).with(2) + expect(Kernel).to receive(:sleep).with(4) + ECSCredentials.new( + backoff: ->(n) { Kernel.sleep(2**n) }, + retries: 3 + ) + assert_requested(expected_request, times: 4) end end - # This section is redundant with json runner tests, but keeping anyway - context 'AWS_CONTAINER_CREDENTIALS_FULL_URI is set' do + context 'AWS_CONTAINER_CREDENTIALS_FULL_URI' do let(:full_uri) { 'https://amazon.com:1234/path' } - let(:loopback_uri) { URI('http://localhost/path') } - let(:loopback_ip) { URI('http://127.0.0.1/path') } - let(:loopback_ipv6) { URI('http://[::1]/path') } + let(:loopback_uri) { 'http://localhost/path' } + let(:loopback_ip) { 'http://127.0.0.1/path' } + let(:loopback_ipv6) { 'http://[::1]/path' } let(:expiration) { Time.now.utc + 3600 } let(:resp) { <<~JSON.strip } @@ -245,19 +219,19 @@ module Aws %w[205.251.242.103 52.94.236.248 54.239.28.85] ) expect do - ECSCredentials.new(backoff: 0, retries: 0) + ECSCredentials.new(backoff: 0) end.to raise_error(ArgumentError, /loopback/) end it 'raises for an http IP that is not a loopback' do ENV['AWS_CONTAINER_CREDENTIALS_FULL_URI'] = 'http://205.251.242.103/path' expect do - ECSCredentials.new(backoff: 0, retries: 0) + ECSCredentials.new(backoff: 0) end.to raise_error(ArgumentError, /loopback/) end it 'uses the full uri if https' do - c = ECSCredentials.new(backoff: 0, retries: 0) + c = ECSCredentials.new(backoff: 0) expect(c.credentials.access_key_id).to eq('akid-full') expect(c.credentials.secret_access_key).to eq('secret-full') expect(c.credentials.session_token).to eq('session-token-full') @@ -265,9 +239,9 @@ module Aws end it 'uses an http URI if it is a loopback' do - ENV['AWS_CONTAINER_CREDENTIALS_FULL_URI'] = loopback_uri.to_s + ENV['AWS_CONTAINER_CREDENTIALS_FULL_URI'] = loopback_uri stub_request(:get, loopback_uri).to_return(status: 200, body: resp) - c = ECSCredentials.new(backoff: 0, retries: 0) + c = ECSCredentials.new(backoff: 0) expect(c.credentials.access_key_id).to eq('akid-full') expect(c.credentials.secret_access_key).to eq('secret-full') expect(c.credentials.session_token).to eq('session-token-full') @@ -275,9 +249,9 @@ module Aws end it 'uses an http IP if it is a loopback' do - ENV['AWS_CONTAINER_CREDENTIALS_FULL_URI'] = loopback_ip.to_s + ENV['AWS_CONTAINER_CREDENTIALS_FULL_URI'] = loopback_ip stub_request(:get, loopback_ip).to_return(status: 200, body: resp) - c = ECSCredentials.new(backoff: 0, retries: 0) + c = ECSCredentials.new(backoff: 0) expect(c.credentials.access_key_id).to eq('akid-full') expect(c.credentials.secret_access_key).to eq('secret-full') expect(c.credentials.session_token).to eq('session-token-full') @@ -285,119 +259,20 @@ module Aws end it 'uses an http IPv6 if it is a loopback' do - ENV['AWS_CONTAINER_CREDENTIALS_FULL_URI'] = loopback_ipv6.to_s + ENV['AWS_CONTAINER_CREDENTIALS_FULL_URI'] = loopback_ipv6 stub_request(:get, loopback_ipv6).to_return(status: 200, body: resp) - c = ECSCredentials.new(backoff: 0, retries: 0) + c = ECSCredentials.new(backoff: 0) expect(c.credentials.access_key_id).to eq('akid-full') expect(c.credentials.secret_access_key).to eq('secret-full') expect(c.credentials.session_token).to eq('session-token-full') expect(c.expiration.to_s).to eq(expiration.to_s) end - end - - context 'generalized HTTP endpoint: request' do - file = File.expand_path('ecs_credentials_request.json', __dir__) - test_cases = Aws::Json.load_file(file) - - def setup_env(env) - return unless env - - env.each do |k, v| - ENV[k] = v - end - end - - def setup_token_file(token_file) - return unless token_file - - if token_file['type'] == 'error' - if token_file['errno'] == 'ENOENT' - expect(File).to receive(:read).and_raise(Errno::ENOENT) - end - elsif token_file['type'] == 'success' - expect(File).to receive(:read).and_return(token_file['content']) - end - end - - def setup_request(request) - method = request['method'].downcase.to_sym - uri = URI(request['uri']) - headers = request['headers'] - - resp = { body: '{}', status: 200 } - if headers.empty? - stub_request(method, uri).and_return(**resp) - else - stub_request(method, uri).with(headers: headers).and_return(**resp) - end - end - - test_cases.each do |test_case| - it "test_case: #{test_case['description']}" do - setup_env(test_case['env']) - setup_token_file(test_case['token_file']) - expect = test_case['expect'] - - if expect['type'] == 'error' - error = ArgumentError - if expect['reason'] =~ /failed to read authorization token/ - error = ECSCredentials::TokenFileReadError - end - expect { ECSCredentials.new }.to raise_error(error) - elsif expect['type'] == 'success' - setup_request(expect['request']) - ECSCredentials.new - end - end - end - end - - context 'generalized HTTP endpoint: response' do - before do - ENV['AWS_CONTAINER_CREDENTIALS_RELATIVE_URI'] = path - end - file = File.expand_path('ecs_credentials_response.json', __dir__) - test_cases = Aws::Json.load_file(file) - - def setup_response(response) - stub_request(:get, "http://169.254.170.2#{path}").to_return( - status: response['status'], - headers: response['headers'], - body: response['body'] - ) - end - - def handle_expectation(expect) - # hacky, but test cases assume we throw errors - # our credential providers just return nil when not set - case expect['reason'] - when /301 Moved Permanently/, /401 Unauthorized/, - /429 Too Many Requests/, /500 Internal Server Error/ - creds = ECSCredentials.new(backoff: 0, retries: 0) - expect(creds.set?).to be(false) - else - expect { ECSCredentials.new(backoff: 0, retries: 0) } - .to raise_error(RuntimeError) - end - end - - test_cases.each do |test_case| - it "test_case: #{test_case['description']}" do - setup_response(test_case['response']) - expect = test_case['expect'] - - if expect['type'] == 'error' - handle_expectation(expect) - elsif expect['type'] == 'success' - c = ECSCredentials.new(backoff: 0, retries: 0) - credentials = expect['credentials'] - expect(c.credentials.access_key_id).to eq(credentials['access_key_id']) - expect(c.credentials.secret_access_key).to eq(credentials['secret_access_key']) - expect(c.credentials.session_token).to eq(credentials['session_token']) - expect(c.expiration).to eq(Time.parse(credentials['expiration'])) - end - end + it 'uses an authorization token if provided' do + ENV['AWS_CONTAINER_AUTHORIZATION_TOKEN'] = 'token' + ECSCredentials.new(backoff: 0) + expect(WebMock).to have_requested(:get, full_uri) + .with(headers: { 'Authorization' => 'token' }) end end end