Instance metadata (IMDS – Instance Metadata Service) – data about an EC2 instance, such as information about AMI, IP, hostname, etc.
You can also add User Data to Instance Metadata to store some parameters, which can then be retrieved inside the instance.
See Instance metadata and user data and Instance metadata categories.
From the beginning of the IMDS, a request/response access model was implemented in AWS, i.e. to get access it was enough to make an HTTP request from the host. Later, a session-oriented system was implemented, when a token had to be obtained for access, and this system received the v2 index. See Add defense in depth against open firewalls, reverse proxies, and SSRF vulnerabilities with enhancements to the EC2 Instance Metadata Service and Use IMDSv2 .
Contents
An example of working with IMDSv1
For example, let’s start EC2, and add some value to User Data:
Now, try curl
the address 169.254.169.254:
[simterm]
$ curl http://169.254.169.254/latest/user-data somedata: somevalue
[/simterm]
Or get the metadata of the instance:
[simterm]
$ curl http://169.254.169.254/latest/meta-data ami-id ami-launch-index ami-manifest-path block-device-mapping/ events/ hostname identity-credentials/ instance-action instance-id instance-life-cycle instance-type local-hostname local-ipv4 mac metrics/ network/ placement/ profile public-hostname public-ipv4 public-keys/ reservation-id security-groups services/
[/simterm]
Or the same, but from a Docker container:
[simterm]
root@ip-172-31-30-97:/home/ubuntu# docker run -ti alpine/curl curl http://169.254.169.254/latest/meta-data/ Unable to find image 'alpine/curl:latest' locally latest: Pulling from alpine/curl 59bf1c3509f3: Pull complete da353f38084f: Pull complete 05df90dbd213: Pull complete Digest: sha256:81372de8c566f2d731bde924bed45230018e6d7c21d051c15e283eb8e06dfa2d Status: Downloaded newer image for alpine/curl:latest ami-id ami-launch-index ami-manifest-path block-device-mapping/ events/ hostname ...
[/simterm]
Accessing IMDS from Kubernetes Pod
That is, in the case of Kubernetes, any Pod also can get this data.
To check, let’s create a pod:
apiVersion: v1 kind: Pod metadata: name: test-imds namespace: default spec: containers: - name: test-imds image: alpine/curl:latest command: - sleep - "3600" imagePullPolicy: IfNotPresent restartPolicy: Always
Run it:
[simterm]
$ kk apply -f pod-imds.yaml
[/simterm]
And run the same request with curl
from this Pod:
[simterm]
$ kk exec -ti test-imds -- curl http://169.254.169.254/latest/meta-data/ ami-id ami-launch-index ami-manifest-path autoscaling/ block-device-mapping/ events/ hostname iam/ ...
[/simterm]
IMDS Security Credentials: the issue
Among other data, IMDS can return the Access/Secret keys and token used to access the Instance IAM Role.
Let’s get security credentials:
[simterm]
$ curl http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance { "Code" : "Success", "LastUpdated" : "2023-03-28T12:48:33Z", "Type" : "AWS-HMAC", "AccessKeyId" : "ASI***BND", "SecretAccessKey" : "4Yz***8Bx", "Token" : "IQo***GaQ=", "Expiration" : "2023-03-28T18:51:13Z" }
[/simterm]
And using these Access/Secret keys, we can do everything that the instance is allowed, and, for example, if the IAM role has the AdminAccess
permissions connected to the instance, we can get these rights.
Let’s check – add a role with access to S3 buckets to the instance:
Check the role in the metadata:
[simterm]
root@ip-172-31-30-97:/home/ubuntu# curl http://169.254.169.254/latest/meta-data/iam/info { "Code" : "Success", "LastUpdated" : "2023-03-28T13:34:51Z", "InstanceProfileArn" : "arn:aws:iam::514***799:instance-profile/IMDSTestS3ReadOnlyAccess", "InstanceProfileId" : "AIPAXPNJUS3H7XEB7UT24" }
[/simterm]
And get the keys and token:
[simterm]
root@ip-172-31-30-97:/home/ubuntu# curl http://169.254.169.254/latest/meta-data/iam/security-credentials/IMDSTestS3ReadOnlyAccess { "Code" : "Success", "LastUpdated" : "2023-03-28T13:35:16Z", "Type" : "AWS-HMAC", "AccessKeyId" : "ASI***3PJ", "SecretAccessKey" : "IfC***t2n", "Token" : "IQo***o4a", "Expiration" : "2023-03-28T20:09:51Z" }
[/simterm]
Add them to our working machine in ~/.aws/credentials
:
[testiam] aws_access_key_id = ASI***3PJ aws_secret_access_key = IfC***t2n aws_session_token = IQo***o4a
And create a profile in the ~/.aws/config
:
[profile testiam] region = eu-central-1 output = json
Check the access now:
[simterm]
$ aws --profile testiam s3 ls 2022-12-19 16:43:06 cronjob-test 2023-02-14 15:02:28 gitlab-s3-cache-test ...
[/simterm]
Not too good, right?
Access permissions to IMDS
To prevent this, you can simply fully disable IMDS, or switch to the IMDS version 2.
Disabling IMDS
For EC2, it is done through the AWS CLI:
[simterm]
$ aws --profile internal ec2 modify-instance-metadata-options --instance-id i-0b0c0e351255ba78c --http-endpoint disabled { "InstanceId": "i-0b0c0e351255ba78c", "InstanceMetadataOptions": { "State": "pending", "HttpTokens": "optional", "HttpPutResponseHopLimit": 1, "HttpEndpoint": "disabled", "HttpProtocolIpv6": "disabled", "InstanceMetadataTags": "disabled" } }
[/simterm]
Or through the AWS Console – EC2 > Instance Settings > Modify instance metadata options :
And now, when requesting meta-data, we will have the 403 – Forbidden error:
[simterm]
root@ip-172-31-30-97:/home/ubuntu# curl http://169.254.169.254/latest/meta-data/iam/info <?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>403 - Forbidden</title> ...
[/simterm]
Switching to IMDS v2
If access is necessary, you can disable the IMDS v1 and use only IMDS v2 by adding the mandatory use of the token with the parameter --http-tokens
:
[simterm]
$ aws --profile internal ec2 modify-instance-metadata-options --instance-id i-0b0c0e351255ba78c --http-endpoint enabled --http-tokens required { "InstanceId": "i-0b0c0e351255ba78c", "InstanceMetadataOptions": { "State": "pending", "HttpTokens": "required", "HttpPutResponseHopLimit": 1, "HttpEndpoint": "enabled", "HttpProtocolIpv6": "disabled", "InstanceMetadataTags": "disabled" } }
[/simterm]
Now we have 401 – Unauthorized error:
[simterm]
root@ip-172-31-30-97:/home/ubuntu# curl http://169.254.169.254/latest/meta-data/iam/info <?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>401 - Unauthorized</title> ...
[/simterm]
But if we will add a token, everything will work.
Let’s get the token:
[simterm]
root@ip-172-31-30-97:/home/ubuntu# TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`
[/simterm]
Check it:
[simterm]
root@ip-172-31-30-97:/home/ubuntu# echo $TOKEN AQA***Vyg==
[/simterm]
And now run curl
again with the header X-aws-ec2-metadata-token
:
[simterm]
root@ip-172-31-30-97:/home/ubuntu# curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/info { "Code" : "Success", "LastUpdated" : "2023-03-28T13:34:51Z", "InstanceProfileArn" : "arn:aws:iam::514***799:instance-profile/IMDSTestS3ReadOnlyAccess", "InstanceProfileId" : "AIPAXPNJUS3H7XEB7UT24" }
[/simterm]
When using Terraform modules to create Node Groups, pay attention to the options. For example, cloudposse/terraform-aws-eks-node-group has IMDSv2 enabled by default, see Behavior changes.
IMDS v2, and Docker
In the case when containers are used, with IMDSv2 enabled, there may be problems when receiving the token, for example:
[simterm]
root@64cbbd918977:/# curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" root@64cbbd918977:/# echo $? 56
[/simterm]
To prevent this, add a parameter http-put-response-hop-limit
with a value greater than 1, since a call from a container adds another network hop when passing a request from a client to IMDS: the first is a request from the host itself, and the second is from a container on this host:
[simterm]
$ aws --profile internal ec2 modify-instance-metadata-options --http-endpoint enabled --http-tokens required --http-put-response-hop-limit 2 --instance-id i-0b0c0e351255ba78c { "InstanceId": "i-0b0c0e351255ba78c", "InstanceMetadataOptions": { "State": "pending", "HttpTokens": "required", "HttpPutResponseHopLimit": 2, "HttpEndpoint": "enabled", ...
[/simterm]
And run again:
[simterm]
root@64cbbd918977:/# TOKEN=`curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` root@64cbbd918977:/# curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/info { "Code" : "Success", "LastUpdated" : "2023-04-10T10:46:45Z", "InstanceProfileArn" : "arn:aws:iam::514***799:instance-profile/IMDSTestS3ReadOnlyAccess", "InstanceProfileId" : "AIP***T24" }
[/simterm]
Done.