Instance Metadata Service (IMDSv2) for EC2 Credentials
Every EC2 instance can ask a special local endpoint, "who am I and what may I do?"
Busca en todas las páginas de la documentación
Every EC2 instance can ask a special local endpoint, "who am I and what may I do?"
That endpoint is the Instance Metadata Service (IMDS), reachable at the link-local address 169.254.169.254. When an instance has an attached instance profile, IMDS serves the role's temporary credentials, and the SDK reads them as the last link in the credential chain.
IMDSv2 is the hardened, session-oriented version you should require everywhere.
On an EC2 instance with an instance profile, the default client resolves IMDS credentials with no configuration. This is the entire "recipe."
# --- Python (boto3) ---
import boto3
# On EC2 with an instance profile, the chain reads IMDSv2 automatically.
s3 = boto3.client("s3", region_name="us-east-1")
print([b["Name"] for b in s3.list_buckets()["Buckets"]])// --- TypeScript (AWS SDK v3) ---
import { S3Client, ListBucketsCommand } from "@aws-sdk/client-s3";
// The default chain reads IMDSv2 automatically as its last link.
const s3 = new S3Client({ region: "us-east-1" });
const out = await s3.send(new ListBucketsCommand({}));
console.log(out.Buckets?.map((b) => b.Name));When to reach for this:
You rarely call IMDS by hand, but seeing the raw IMDSv2 flow explains what the SDK does and why v2 is safer.
IMDSv2 is a two-step handshake: PUT to get a session token, then GET the metadata with that token in a header.
# 1. Get a session token (valid up to 6 hours). The PUT is what v1 lacked.
TOKEN=$(curl -sX PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
# 2. Use the token to read the instance-profile credentials.
ROLE=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/)
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
"http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE"
# -> JSON: AccessKeyId, SecretAccessKey, Token, ExpirationThe SDK does exactly this internally, then refreshes before Expiration. You can force the SDK to require v2 and set retry behavior:
# --- Python (boto3) ---
import os
import boto3
from botocore.config import Config
# Refuse the insecure v1 fallback at the SDK level.
os.environ["AWS_EC2_METADATA_V1_DISABLED"] = "true"
cfg = Config(retries={"max_attempts": 2, "mode": "standard"})
s3 = boto3.client("s3", region_name="us-east-1", config=cfg)// --- TypeScript (AWS SDK v3) ---
import { S3Client } from "@aws-sdk/client-s3";
import { fromInstanceMetadata } from "@aws-sdk/credential-providers";
// Explicit IMDS provider with bounded timeout/retries.
const s3 = new S3Client({
region: "us-east-1",
credentials: fromInstanceMetadata({ timeout: 1000, maxRetries: 2 }),
});What this demonstrates:
AWS_EC2_METADATA_V1_DISABLED=true makes boto3 refuse the legacy v1 flow.fromInstanceMetadata when you want explicit control.IMDSv1 answered a plain GET with credentials.
That made it a target for server-side request forgery (SSRF): if an app could be tricked into fetching an attacker-controlled URL, the attacker could aim it at 169.254.169.254 and exfiltrate the role's credentials.
IMDSv2 closes this by requiring a PUT to mint a session token first, and the token request carries headers (like a low TTL and a hop limit) that a naive proxied GET cannot forge. It defeats the common SSRF and open-proxy shapes that leaked v1 credentials.
IMDS responses have a PUT response hop limit - a TTL on the IP packet.
The default of 1 means only the instance itself can reach IMDS. Containers running on the instance add a network hop, so a limit of 1 blocks them.
Set the hop limit to at least 2 when containers on the instance need IMDS credentials:
aws ec2 modify-instance-metadata-options \
--instance-id i-0123456789abcdef0 \
--http-tokens required \
--http-put-response-hop-limit 2--http-tokens required enforces IMDSv2 at the instance level, rejecting v1 entirely - the strongest posture.
The instance-metadata provider is the last link in the credential chain.
It only runs if nothing earlier - env vars, shared files, container credentials - answered. On startup the SDK fetches a token, reads the role name, then reads the credentials, caching them until near expiry. A slow or blocked IMDS endpoint therefore shows up as a startup delay, which is why bounding the timeout matters.
--http-put-response-hop-limit 2.http-tokens optional keeps the SSRF-vulnerable path open. Fix: set --http-tokens required and AWS_EC2_METADATA_V1_DISABLED=true.http-endpoint disabled means no instance credentials at all. Fix: enable the endpoint, or supply credentials another way.169.254.169.254 stalls resolution. Fix: bound the IMDS timeout and retries so failure is fast.| Alternative | Use When | Don't Use When |
|---|---|---|
| IMDSv2 (instance profile) | Code runs directly on EC2 | You run on ECS/EKS with a task or pod role |
| ECS task role (container credentials) | Containers on ECS/Fargate | Plain EC2 without an orchestrator |
| IRSA / web identity | EKS pods needing per-service-account roles | Coarse node-level access is acceptable |
| Env vars / shared profile | Local dev or CI | Production compute that can carry a role |
A link-local endpoint at 169.254.169.254 that an EC2 instance queries for data about itself, including the temporary credentials of any attached instance-profile role.
It requires a session token from a PUT before any metadata GET, which blocks the SSRF-style attacks that could steal v1 credentials through a simple GET.
No. Both SDKs read IMDS automatically as the last link in the credential chain when no earlier provider answers.
Set the instance metadata options to --http-tokens required, and set AWS_EC2_METADATA_V1_DISABLED=true so the SDK also refuses v1.
The default hop limit of 1 stops packets that cross the container network boundary. Raise --http-put-response-hop-limit to 2.
No. They are temporary role credentials with an expiry, and the SDK refreshes them from IMDS before they lapse.
You request it on the PUT, up to six hours. The SDK manages the token lifetime for you.
Yes, with --http-endpoint disabled, but then the instance serves no credentials and you must supply them another way.
If an app can be tricked into fetching an attacker URL, v1's plain GET let attackers read credentials. The v2 PUT-token handshake defeats that class of attack.
The SDK may be waiting on a blocked or unreachable IMDS endpoint. Bound the IMDS timeout and retries so resolution fails fast.
Those platforms provide their own credential sources (task roles, IRSA) that sit ahead of IMDS in the chain. Let the chain choose rather than forcing IMDS.
IMDS returns no credentials for the iam/security-credentials path, and the chain finds nothing. Attach an instance profile with the required role.
Stack versions: This page was written for boto3 1.43.x (Python 3.10+) and the AWS SDK for JavaScript v3 (Node.js 18+).
Revisado por Chris St. John·Última actualización: 23 jul 2026