Skip to content

Commit 86fd345

Browse files
authored
implement IMDSv2 (#1380)
* implement IMDS version 2 in instance provider * fallback to IMDSv1
1 parent 7a8cf5a commit 86fd345

File tree

2 files changed

+41
-7
lines changed

2 files changed

+41
-7
lines changed

docs/authentication/ec2-metadata.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,4 @@ When you run code within an EC2 instance (or EKS, Lambda), AsyncAws is able to f
1010
When running a single application on the Server, this is the simplest way to grant permissions to the application. You
1111
have nothing to configure on the application, you only grant permissions on the Role attached to the instance.
1212

13-
AsyncAWS uses the IMDSv1 protocol to read instance metadata. So this authentication method won't work if the v1
14-
protocol is disabled.
13+
AsyncAWS uses the IMDSv2 protocol to read instance metadata and fallback to the IMDSv1 protocol if it isn't supported.

src/Core/src/Credentials/InstanceProvider.php

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,38 +17,55 @@
1717
use Symfony\Contracts\HttpClient\ResponseInterface;
1818

1919
/**
20-
* Provides Credentials from the running EC2 metadata server using the IMDS version 1.
20+
* Provides Credentials from the running EC2 metadata server using the IMDSv1 and IMDSv2.
2121
*
2222
* @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html
2323
*
2424
* @author Jérémy Derussé <jeremy@derusse.com>
2525
*/
2626
final class InstanceProvider implements CredentialProvider
2727
{
28-
private const ENDPOINT = 'http://169.254.169.254/latest/meta-data/iam/security-credentials';
28+
private const TOKEN_ENDPOINT = 'http://169.254.169.254/latest/api/token';
29+
private const METADATA_ENDPOINT = 'http://169.254.169.254/latest/meta-data/iam/security-credentials';
2930

3031
private $logger;
3132

3233
private $httpClient;
3334

3435
private $timeout;
3536

36-
public function __construct(?HttpClientInterface $httpClient = null, ?LoggerInterface $logger = null, float $timeout = 1.0)
37+
private $tokenTtl;
38+
39+
public function __construct(?HttpClientInterface $httpClient = null, ?LoggerInterface $logger = null, float $timeout = 1.0, int $tokenTtl = 21600)
3740
{
3841
$this->logger = $logger ?? new NullLogger();
3942
$this->httpClient = $httpClient ?? HttpClient::create();
4043
$this->timeout = $timeout;
44+
$this->tokenTtl = $tokenTtl;
4145
}
4246

4347
public function getCredentials(Configuration $configuration): ?Credentials
4448
{
49+
$token = $this->getToken();
50+
$headers = [];
51+
52+
if (null !== $token) {
53+
$headers = ['X-aws-ec2-metadata-token' => $token];
54+
}
55+
4556
try {
4657
// Fetch current Profile
47-
$response = $this->httpClient->request('GET', self::ENDPOINT, ['timeout' => $this->timeout]);
58+
$response = $this->httpClient->request('GET', self::METADATA_ENDPOINT, [
59+
'timeout' => $this->timeout,
60+
'headers' => $headers,
61+
]);
4862
$profile = $response->getContent();
4963

5064
// Fetch credentials from profile
51-
$response = $this->httpClient->request('GET', self::ENDPOINT . '/' . $profile, ['timeout' => $this->timeout]);
65+
$response = $this->httpClient->request('GET', self::METADATA_ENDPOINT . '/' . $profile, [
66+
'timeout' => $this->timeout,
67+
'headers' => $headers,
68+
]);
5269
$result = $this->toArray($response);
5370

5471
if ('Success' !== $result['Code']) {
@@ -106,4 +123,22 @@ private function toArray(ResponseInterface $response): array
106123

107124
return $content;
108125
}
126+
127+
private function getToken(): ?string
128+
{
129+
try {
130+
$response = $this->httpClient->request('PUT', self::TOKEN_ENDPOINT,
131+
[
132+
'timeout' => $this->timeout,
133+
'headers' => ['X-aws-ec2-metadata-token-ttl-seconds' => $this->tokenTtl],
134+
]
135+
);
136+
137+
return $response->getContent();
138+
} catch (TransportExceptionInterface|HttpExceptionInterface $e) {
139+
$this->logger->info('Failed to fetch metadata token for IMDSv2, fallback to IMDSv1.', ['exception' => $e]);
140+
141+
return null;
142+
}
143+
}
109144
}

0 commit comments

Comments
 (0)