@@ -58,7 +58,7 @@ function refresh_token!(session::Session, duration::Integer=session.duration)
5858 # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html#imds-considerations
5959 uri = URI (; scheme= " http" , host= IPv4_ADDRESS, path= " /latest/api/token" )
6060 r = try
61- _http_request (" PUT" , uri, headers; status_exception= false )
61+ _http_request (" PUT" , uri, headers; status_exception= false , retry = false )
6262 catch e
6363 # The IMDSv2 uses a default Time To Live (TTL) of 1 (also known as the hop limit) at
6464 # the IP layer to ensure token requests occur on the instance. When this occurs we
@@ -71,6 +71,7 @@ function refresh_token!(session::Session, duration::Integer=session.duration)
7171 " https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/" *
7272 " instancedata-data-retrieval.html#imds-considerations"
7373
74+ session. token = " "
7475 session. duration = 0
7576 session. expiration = typemax (Int64) # Use IMDSv1 indefinitely
7677 return session
@@ -86,6 +87,7 @@ function refresh_token!(session::Session, duration::Integer=session.duration)
8687 session. duration = duration
8788 session. expiration = t + duration
8889 elseif r. status == 404
90+ session. token = " "
8991 session. duration = 0
9092 session. expiration = typemax (Int64) # Use IMDSv1 indefinitely
9193 else
@@ -97,27 +99,58 @@ function refresh_token!(session::Session, duration::Integer=session.duration)
9799 return session
98100end
99101
100- function request (session:: Session , method:: AbstractString , path:: AbstractString ; kwargs... )
102+ function request (
103+ session:: Session , method:: AbstractString , path:: AbstractString ; status_exception= true
104+ )
105+ # Only allow the token to be refreshed once per call to `IMDS.request`.
106+ allow_refresh = true
107+
101108 # Attempt to generate token for use with IMDSv2. If we're unable to generate a token
102109 # we'll fall back on using IMDSv1. We prefer using IMDSv2 as instances can be configured
103110 # to disable IMDSv1 access: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-IMDS-new-instances.html#configure-IMDS-new-instances
104- token_expired (session) && refresh_token! (session)
105- headers = Pair{String,String}[]
106- ! isempty (session. token) && push! (headers, " X-aws-ec2-metadata-token" => session. token)
111+ if token_expired (session)
112+ refresh_token! (session)
113+ allow_refresh = false
114+ end
115+ headers = HTTP. Header[]
116+ if ! isempty (session. token)
117+ HTTP. setheader (headers, " X-aws-ec2-metadata-token" => session. token)
118+ end
107119
108120 # Only using the IPv4 endpoint as the IPv6 endpoint has to be explicitly enabled and
109121 # does not disable IPv4 support.
110122 # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-IMDS-new-instances.html#configure-IMDS-new-instances-ipv4-ipv6-endpoints
111123 uri = URI (; scheme= " http" , host= IPv4_ADDRESS, path)
112- return _http_request (method, uri, headers; kwargs... )
124+
125+ # Refresh the token and immediately retry if we encounter "HTTP 401 Unauthorized".
126+ #
127+ # > When token usage is set to `required` (IMDSv2), requests without a valid token or
128+ # > with an expired token receive a `401 - Unauthorized` HTTP error code.
129+ # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html#instance-metadata-v2-how-it-works
130+ retry_delays = [0 ]
131+ function retry_check (s, e, request, response, response_body)
132+ if allow_refresh && e isa StatusError && e. status == 401 && ! isempty (session. token)
133+ refresh_token! (session)
134+ allow_refresh = false
135+ HTTP. setheader (request. headers, " X-aws-ec2-metadata-token" => session. token)
136+ return true
137+ else
138+ return false
139+ end
140+ end
141+
142+ return _http_request (method, uri, headers; retry_delays, retry_check, status_exception)
113143end
114144
115145function _http_request (args... ; status_exception= true , kwargs... )
116146 response = try
117- # Always throw status exceptions so we can determine if the IMDS service is available
118- @mock HTTP. request (
119- args... ; connect_timeout= 1 , retry= false , kwargs... , status_exception= true
120- )
147+ # Override the user's `status_exception` so we can consistently handle status
148+ # exceptions. Additionally, by forcing `status_exception=true` this allows retries
149+ # to work.
150+ #
151+ # Additionally, we set a low connect timeout to have faster responses when
152+ # attempting to connect to IMDS outside of EC2.
153+ @mock HTTP. request (args... ; connect_timeout= 1 , kwargs... , status_exception= true )
121154 catch e
122155 # When running outside of an EC2 instance the link-local address will be unavailable
123156 # and connections will fail. On EC2 instances where IMDS is disabled a HTTP 403 is
0 commit comments