diff --git a/Gemfile b/Gemfile index bca6dd2f5..826944378 100644 --- a/Gemfile +++ b/Gemfile @@ -6,9 +6,10 @@ gemspec group :test, :default do gem 'pry-nav' gem 'mime-types', '~> 3.1' + gem 'nokogiri', '~> 1.10.10' end group :test do gem "simplecov" gem "codeclimate-test-reporter", "~> 1.0.0" -end \ No newline at end of file +end diff --git a/lib/fog/aws/credential_fetcher.rb b/lib/fog/aws/credential_fetcher.rb index 930dd22c8..469eea6fd 100644 --- a/lib/fog/aws/credential_fetcher.rb +++ b/lib/fog/aws/credential_fetcher.rb @@ -1,3 +1,7 @@ +# frozen_string_literal: true + +require "nokogiri" + module Fog module AWS module CredentialFetcher @@ -9,6 +13,8 @@ module Fog CONTAINER_CREDENTIALS_HOST = "http://169.254.170.2" + STS_GLOBAL_ENDPOINT = "https://sts.amazonaws.com" + module ServiceMethods def fetch_credentials(options) if options[:use_iam_profile] && Fog.mocking? @@ -23,6 +29,30 @@ module Fog connection = options[:connection] || Excon.new(CONTAINER_CREDENTIALS_HOST) credential_path = options[:credential_path] || ENV["AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"] role_data = connection.get(:path => credential_path, :idempotent => true, :expects => 200).body + session = Fog::JSON.decode(role_data) + + if region.nil? + connection = options[:metadata_connection] || Excon.new(INSTANCE_METADATA_HOST) + token_header = fetch_credentials_token_header(connection, options[:disable_imds_v2]) + region = connection.get(:path => INSTANCE_METADATA_AZ, :idempotent => true, :expects => 200, :headers => token_header).body[0..-2] + end + elsif ENV["AWS_WEB_IDENTITY_TOKEN_FILE"] + params = { + :Action => "AssumeRoleWithWebIdentity", + :RoleArn => options[:role_arn] || ENV.fetch("AWS_ROLE_ARN"), + :RoleSessionName => options[:role_session_name] || ENV.fetch("AWS_ROLE_SESSION_NAME"), + :WebIdentityToken => File.read(options[:aws_web_identity_token_file] || ENV.fetch("AWS_WEB_IDENTITY_TOKEN_FILE")), + :Version => "2011-06-15", + } + connection = options[:connection] || Excon.new(STS_GLOBAL_ENDPOINT, :query => params) + document = Nokogiri::XML(connection.get(:idempotent => true, :expects => 200).body) + + session = { + "AccessKeyId" => document.css("AccessKeyId").children.text, + "SecretAccessKey" => document.css("SecretAccessKey").children.text, + "Token" => document.css("SessionToken").children.text, + "Expiration" => document.css("Expiration").children.text, + } if region.nil? connection = options[:metadata_connection] || Excon.new(INSTANCE_METADATA_HOST) @@ -34,16 +64,17 @@ module Fog token_header = fetch_credentials_token_header(connection, options[:disable_imds_v2]) role_name = connection.get(:path => INSTANCE_METADATA_PATH, :idempotent => true, :expects => 200, :headers => token_header).body role_data = connection.get(:path => INSTANCE_METADATA_PATH+role_name, :idempotent => true, :expects => 200, :headers => token_header).body + session = Fog::JSON.decode(role_data) + region ||= connection.get(:path => INSTANCE_METADATA_AZ, :idempotent => true, :expects => 200, :headers => token_header).body[0..-2] end - - session = Fog::JSON.decode(role_data) + credentials = {} credentials[:aws_access_key_id] = session['AccessKeyId'] credentials[:aws_secret_access_key] = session['SecretAccessKey'] credentials[:aws_session_token] = session['Token'] credentials[:aws_credentials_expire_at] = Time.xmlschema session['Expiration'] - + # set region by default to the one the instance is in. credentials[:region] = region #these indicate the metadata service is unavailable or has no profile setup diff --git a/tests/credentials_tests.rb b/tests/credentials_tests.rb index 4b9353b8b..b6d896425 100644 --- a/tests/credentials_tests.rb +++ b/tests/credentials_tests.rb @@ -60,6 +60,35 @@ Shindo.tests('AWS | credentials', ['aws']) do ENV['AWS_CONTAINER_CREDENTIALS_RELATIVE_URI'] = nil + ENV['AWS_WEB_IDENTITY_TOKEN_FILE'] = File.dirname(__FILE__) + '/lorem.txt' + ENV['AWS_ROLE_ARN'] = "dummyrole" + ENV['AWS_ROLE_SESSION_NAME'] = "dummyrolesessionname" + document = + ''\ + ''\ + ''\ + 'dummytoken'\ + 'dummysecret'\ + "#{expires_at.xmlschema}"\ + 'dummykey'\ + ''\ + ''\ + '' + + Excon.stub({method: :get, path: "/", idempotent: true}, { status: 200, body: document}) + + tests('#fetch_credentials token based') do + returns( + aws_access_key_id: 'dummykey', + aws_secret_access_key: 'dummysecret', + aws_session_token: 'dummytoken', + region: 'us-west-1', + aws_credentials_expire_at: expires_at + ) { Fog::AWS::Compute.fetch_credentials(use_iam_profile: true) } + end + + ENV['AWS_WEB_IDENTITY_TOKEN_FILE'] = nil + compute = Fog::AWS::Compute.new(use_iam_profile: true) tests('#refresh_credentials_if_expired') do @@ -100,6 +129,7 @@ Shindo.tests('AWS | credentials', ['aws']) do end ensure ENV['AWS_CONTAINER_CREDENTIALS_RELATIVE_URI'] = nil + ENV['AWS_WEB_IDENTITY_TOKEN_FILE'] = nil Excon.stubs.clear Excon.defaults[:mock] = old_mock_value Fog.mock! if fog_was_mocked