AWS: Instance Metadata Service v1 vs IMDS v2 та робота з Kubernetes Pod і Docker контейнерів

Автор |  11/04/2023
 

Instance metadata (IMDS – Instance Metadata Service) – дані про EC2 інстанс, такі як інформація про AMI, IP, ім’я хосту, і т.д.

Також до Instance Metadata можна додати User Data для зберігання якихось параметрів, які потім можна буде отримати всередині інстансу.

Див. Instance metadata and user data та Instance metadata categories.

Від початку, в AWS була реалізована request/response модель доступу до IMDS, тобто для отримання доступу достатньо було зробити HTTP-запит з хосту. Пізніше, була реалізована система session-oriented, коли для доступу вже треба було отримати токен, і ця система отримала індекс v2. Див. Add defense in depth against open firewalls, reverse proxies, and SSRF vulnerabilities with enhancements to the EC2 Instance Metadata Service та Use IMDSv2.

Приклад роботи з IMDSv1

Наприклад, запустимо EC2 та в User Data додамо якесь значення:

Та пробуємо curl на адресу 169.254.169.254:

[simterm]

$ curl http://169.254.169.254/latest/user-data
somedata: somevalue

[/simterm]

Або отримаємо саме метадату інстансу:

[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]

Або теж саме з Docker-контейнеру:

[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]

Доступ до IMDS із Kubernetes Pod

Тобто, і у випадку Kubernetes будь-який под має змогу отримати ці дані.

Для перевірки створимо под:

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

Запускаємо його:

[simterm]

$ kk apply -f pod-imds.yaml

[/simterm]

І з нього виконуємо той самий запит з curl:

[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

Крім інших даних, IMDS може повернути Access/Secret ключі та токен, які використовуються для отримання доступу до Instance IAM Role.

Отримаємо 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]

А використовуючи ці Access/Secret ключі, ми можемо робити все, що дозволено інстансу, і, наприклад, якщо до інстансу підключено ІAМ роль з AdminAccess – ми зможемо отримати ці права.

Перевіримо – додамо до інстансу роль з доступом до S3-бакетів:

Перевіряємо роль в метаданих:

[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]

Та отримаємо ключі та токен:

[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]

Додаємо їх собі на робочу машину в ~/.aws/credentials:

[testiam]
aws_access_key_id = ASI***3PJ
aws_secret_access_key = IfC***t2n
aws_session_token = IQo***o4a

Та створимо профіль у ~/.aws/config:

[profile testiam]
region = eu-central-1
output = json

І отримаємо доступ:

[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]

Жах! :scream:

Права доступу до IMDS

Щоб запобігти такому, можна просто відключити IMDS взагалі, або використовувати IMDS v2.

Відключення IMDS

Для ЕС2 робиться через 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]

Або через AWS Console – EC2 > Instance Settings > Modify instance metadata options:

І тепер при запиті до meta-data маємо помилку 403 – Forbidden:

[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]

Переключення на IMDS v2

Якщо доступ все ж треба, то можемо відключити IMDS v1 та використовувати тільки IMDS v2 додавши обов’язкове використання токена за допомогою параметру --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]

Тепер при запиті маємо 401 – Unauthorized:

[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]

Але якщо додамо токен – то все працюватиме.

Отримаємо токен:

[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]

Глянемо його:

[simterm]

root@ip-172-31-30-97:/home/ubuntu# echo $TOKEN
AQA***Vyg==

[/simterm]

І тепер знову curl з хедером 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]

При використані Terraform модулів для створення Node Groups, звертайте увагу на опції. Наприклад, у cloudposse/terraform-aws-eks-node-group по дефолту включена IMDSv2, див. Behavior changes.

IMDS v2 та Docker

У випадку, коли використовуються контейнери, з включеним IMDSv2 можуть бути проблеми при отримані токену, наприклад:

[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]

Щоб запобігти цьому, додаємо параметр http-put-response-hop-limit зі значенням більше 1, так як виклик з контейнеру додає ще один хоп при проходженні запросу від клієнта до IMDS: перший, це запит з самого хосту, а другий – із контейнера на цьому хості:

[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]

І пробуємо зараз:

[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]

Готово.