11# frozen_string_literal: true
22
33require_relative "openssl"
4+ require_relative "user_interaction"
45
56##
67# S3URISigner implements AWS SigV4 for S3 Source to avoid a dependency on the aws-sdk-* gems
78# More on AWS SigV4: https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
89class Gem ::S3URISigner
10+ include Gem ::UserInteraction
11+
912 class ConfigurationError < Gem ::Exception
1013 def initialize ( message )
1114 super message
@@ -147,17 +150,40 @@ def ec2_metadata_credentials_json
147150 require_relative "request/connection_pools"
148151 require "json"
149152
150- iam_info = ec2_metadata_request ( EC2_IAM_INFO )
153+ # First try V2 fallback to V1
154+ res = nil
155+ begin
156+ res = ec2_metadata_credentials_imds_v2
157+ rescue InstanceProfileError
158+ alert_warning "Unable to access ec2 credentials via IMDSv2, falling back to IMDSv1"
159+ res = ec2_metadata_credentials_imds_v1
160+ end
161+ res
162+ end
163+
164+ def ec2_metadata_credentials_imds_v2
165+ token = ec2_metadata_token
166+ iam_info = ec2_metadata_request ( EC2_IAM_INFO , token :)
151167 # Expected format: arn:aws:iam::<id>:instance-profile/<role_name>
152168 role_name = iam_info [ "InstanceProfileArn" ] . split ( "/" ) . last
153- ec2_metadata_request ( EC2_IAM_SECURITY_CREDENTIALS + role_name )
169+ ec2_metadata_request ( EC2_IAM_SECURITY_CREDENTIALS + role_name , token : )
154170 end
155171
156- def ec2_metadata_request ( url )
157- uri = Gem ::URI ( url )
158- @request_pool ||= create_request_pool ( uri )
159- request = Gem ::Request . new ( uri , Gem ::Net ::HTTP ::Get , nil , @request_pool )
160- response = request . fetch
172+ def ec2_metadata_credentials_imds_v1
173+ iam_info = ec2_metadata_request ( EC2_IAM_INFO , token : nil )
174+ # Expected format: arn:aws:iam::<id>:instance-profile/<role_name>
175+ role_name = iam_info [ "InstanceProfileArn" ] . split ( "/" ) . last
176+ ec2_metadata_request ( EC2_IAM_SECURITY_CREDENTIALS + role_name , token : nil )
177+ end
178+
179+ def ec2_metadata_request ( url , token :)
180+ request = ec2_iam_request ( Gem ::URI ( url ) , Gem ::Net ::HTTP ::Get )
181+
182+ response = request . fetch do |req |
183+ if token
184+ req . add_field "X-aws-ec2-metadata-token" , token
185+ end
186+ end
161187
162188 case response
163189 when Gem ::Net ::HTTPOK then
@@ -167,13 +193,34 @@ def ec2_metadata_request(url)
167193 end
168194 end
169195
196+ def ec2_metadata_token
197+ request = ec2_iam_request ( Gem ::URI ( EC2_IAM_TOKEN ) , Gem ::Net ::HTTP ::Put )
198+
199+ response = request . fetch do |req |
200+ req . add_field "X-aws-ec2-metadata-token-ttl-seconds" , 60
201+ end
202+
203+ case response
204+ when Gem ::Net ::HTTPOK then
205+ response . body
206+ else
207+ raise InstanceProfileError . new ( "Unable to fetch AWS metadata from #{ uri } : #{ response . message } #{ response . code } " )
208+ end
209+ end
210+
211+ def ec2_iam_request ( uri , verb )
212+ @request_pool ||= create_request_pool ( uri )
213+ Gem ::Request . new ( uri , verb , nil , @request_pool )
214+ end
215+
170216 def create_request_pool ( uri )
171217 proxy_uri = Gem ::Request . proxy_uri ( Gem ::Request . get_proxy_from_env ( uri . scheme ) )
172218 certs = Gem ::Request . get_cert_files
173219 Gem ::Request ::ConnectionPools . new ( proxy_uri , certs ) . pool_for ( uri )
174220 end
175221
176222 BASE64_URI_TRANSLATE = { "+" => "%2B" , "/" => "%2F" , "=" => "%3D" , "\n " => "" } . freeze
223+ EC2_IAM_TOKEN = "http://169.254.169.254/latest/api/token"
177224 EC2_IAM_INFO = "http://169.254.169.254/latest/meta-data/iam/info"
178225 EC2_IAM_SECURITY_CREDENTIALS = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
179226end
0 commit comments