Photo by William Bout on Unsplash
Como usar o Workload Identity no GKE: Um guia prático
Um guia prático de como configurar e usar o Workload Identity em seu cluster GKE
Introdução
O Workload Identity é um recurso do GKE que concede permissões para que suas cargas de trabalho (workloads) nos clusters do GKE atuem como contas de serviço do IAM para acessarem outros serviços do Google Cloud de forma gerenciada e segura.
Talvez suas aplicações em execução no GKE precisem de acesso às APIs do Google Cloud, como o Cloud Storage ou BigQuery, e o Workload Identity facilita isso! Os pods que usam a conta de serviço configurada do Kubernetes serão autenticados automaticamente como a conta de serviço do IAM ao acessar as APIs do Google Cloud.
Ao falarmos de contas de serviço, precisamos esclarecer as diferenças entre:
Contas de serviço do IAM - São recursos do Google Cloud que permitem que os aplicativos façam requisições autorizadas para as APIs do Google Cloud.
Contas de serviço do Kubernetes - São recursos do Kubernetes que fornecem uma identidade para processos em execução nos pods do GKE.
A importância da utilização do Workload Identity é citado no CIS Benchmark do GKE com a recomendação de referência 6.2.2 que descreve:
- Prefira usar as contas de serviço dedicadas e o recurso Workload Identity do Google Cloud.
Principais benefícios do Workload Identity
Temos muitos benefícios ao utilizar o Workload Identity como:
✅ Simplificar o gerenciamento de acesso: ao utilizarmos funções do IAM para controlar e auditar o acesso aos serviços do Google Cloud, conseguimos facilitar o gerenciamento do acesso para diversas cargas de trabalho.
✅ Aumentar da flexibilidade: ao invés de todos os Pods utilizarem a mesma conta de serviço, muitas das vezes com privilégios excessivos, conseguimos segregar os níveis de acesso aos serviços do Google Cloud conforme a necessidade de cada Pod, e também é possível vincular dinamicamente a conta de serviço aos pods.
✅ Maior segurança: ao utilizar identidades gerenciadas pelo Google Cloud para autenticar as cargas de trabalho, eliminamos a necessidade de utilização de chaves de conta de serviço (service account keys), dessa forma reduzimos o risco dessas chaves serem comprometidas e utilizadas para fins maliciosos.
Arquitetura
Para que fique mais claro o potencial de utilização do Workload Identity no GKE, assim como seus benefícios, iremos implantar de forma prática a arquitetura abaixo.
Componentes da arquitetura:
Cluster GKE em modo Standard.
Workload Identity ativado no cluster GKE.
Namespace no Kubernetes.
Conta de serviço do Kubernetes com um annotation tendo como valor a conta de serviço do IAM.
Pod que utilizará a conta de serviço do Kubernetes.
Bucket do Cloud Storage.
Conta de serviço do IAM com duas roles:
Role storage.objectViewer para acesso ao bucket;
Role iam.workloadIdentityUser para vincular a conta de serviço do Kubernetes que usará o Workload Identity com a conta de serviço do IAM.
1️⃣ Ative o Workload Identity
Abaixo mapeamos os pré-requisitos e as etapas para ativar o Workload Identity em seu cluster Kubernetes do GKE em modo Standard.
Os clusters do Autopilot ativam o Workload Identity por padrão.
1.1. Pré-requisitos
Ativar a API Google Kubernetes Engine e API Service Account Credentials do IAM. Execute o comando usando o gcloud CLI:
gcloud services enable container.googleapis.com iamcredentials.googleapis.com
Verificar se você tem as roles do IAM:
Kubernetes Engine Admin -
roles/container.admin
Service Account Admin -
roles/iam.serviceAccountAdmin
Exporte as variáveis de ambiente em seu shell que utilizaremos na criação dos nossos recursos:
export ID_PROJETO=ID do seu projeto export NOME_CLUSTER=Nome desejado para o cluster GKE export REGIAO_COMPUTE=Regiao desejada para criação do cluster GKE export NAMESPACE_K8S=Nome desjeado para o namespace no Kubernetes export CONTASVC_K8S=Nome desejeado para conta de serviço do Kubernetes export CONTASVC_IAM=Nome desejado para conta de serviço do IAM export MEU_BUCKET=gs://bucket-$ID_PROJETO
Para você ter algo como referência, o meu ficou assim:
export ID_PROJETO=gke-workloadid-demo export NOME_CLUSTER=wli-gke export REGIAO_COMPUTE=us-south1 export NAMESPACE_K8S=wli-ns export CONTASVC_K8S=sa-k8s export CONTASVC_IAM=sa-iam export MEU_BUCKET=gs://bucket-$ID_PROJETO
1.2. Criar um novo cluster com o Workload Identity ativado
Caso já possua uma cluster GKE pule para a próxima etapa 1.3.
Para habilitar o Workload Identity em um novo cluster, execute o seguinte comando:
gcloud container clusters create $NOME_CLUSTER --region $REGIAO_COMPUTE --workload-pool=$ID_PROJETO.svc.id.goog --disk-size "30" --num-nodes "1"
Criaremos um cluster com apenas 3 worker nodes e 30GB de armazenamento em cada. A criação do cluster leva cerca de 15 minutos.
O seu output após finalizar a criação deve ser parecido com:
kubeconfig entry generated for wli-gke.
NAME: wli-gke
LOCATION: us-south1
MASTER_VERSION: 1.27.3-gke.100
MASTER_IP: 34.174.244.77
MACHINE_TYPE: e2-medium
NODE_VERSION: 1.27.3-gke.100
NUM_NODES: 3
STATUS: RUNNING
1.3. Ativar o Workload Identity em um cluster existente
Para habilitar o Workload Identity em um cluster existente, execute o seguinte comando:
gcloud container clusters update $NOME_CLUSTER --region=$REGIAO_COMPUTE --workload-pool=$ID_PROJETO.svc.id.goog
Aguarde a ativação do recurso no cluster, que geralmente leva entre 15-20 minutos.
1.4. Verificar se o Workload Identity está ativado
É possível verificar se o Workload Identity está habilitado usando o gcloud CLI ou via console do Google Cloud.
Para verificar usando o gcloud CLI execute o comando abaixo:
gcloud container clusters describe $NOME_CLUSTER --region=$REGIAO_COMPUTE | grep workloadPool
O seu output deve ser parecido com:
workloadPool: ID_PROJETO.svc.id.goog
Para verificar via console do Google CUSloud vá em Kubernetes Engine > Clusters > clique sobre o nome do seu cluster e na sessão Security verifique se a Identidade da carga de trabalho (Workload Identity) está como "Ativado", e abaixo terá o nome do seu Namespace.
2️⃣ Configurações para utilizar o Workload Identity
Vamos realizar as configurações listadas abaixo para utilizar o Workload Identity em uma aplicação e acessar um bucket do Cloud Storage.
2.1. Criar uma conta de serviço do Kubernetes
Crie um namespace para sua conta de serviço Kubernetes. Você também pode usar o namespace default ou qualquer outro namespace que desejar. Para isso usaremos o
kubectl
:kubectl create namespace $NAMESPACE_K8S
Crie uma conta de serviço do Kubernetes para sua aplicação utilizar.
kubectl create serviceaccount $CONTASVC_K8S --namespace $NAMESPACE_K8S
2.2. Criar um bucket no Cloud Storage
Vamos criar um bucket no Cloud Storage para posteriormente acessarmos a partir do nosso Pod, para isso execute o comando abaixo:
gcloud storage buckets create $MEU_BUCKET
Se preferir é possível usar o gsutil para criar o bucket, por exemplo:
gsutil mb -p $ID_PROJETO $MEU_BUCKET
Crie um arquivo conforme abaixo:
echo "Arquivo que está no bucket do Cloud Storage!" > arquivobucket.txt
Copie o arquivo para seu bucket:
gcloud storage cp file://arquivobucket.txt $MEU_BUCKET
Habilite os logs de leitura de dados em IAM e administrador > Registros de auditoria:
Filtre por Google Cloud Storage.
Selecione a caixa ao lado do nome de serviço.
Selecione a caixa de Leitura de dados.
Clique em Salvar.
2.3. Criar uma conta de serviço do IAM
Crie uma conta de serviço do IAM para sua aplicação usar. Execute o comando abaixo com o gcloud CLI.
gcloud iam service-accounts create $CONTASVC_IAM --project=$ID_PROJETO
Conceda acesso a conta de serviço do IAM para acessar o bucket do Cloud Storage criado anteriormente.
gcloud projects add-iam-policy-binding $ID_PROJETO --member "serviceAccount:$CONTASVC_IAM@$ID_PROJETO.iam.gserviceaccount.com" --role "roles/storage.objectViewer"
Nesse caso daremos permissão de Storage Object Viewer:
roles/storage.objectViewer
Como exemplo, os meus comandos e output foram:
$ gcloud iam service-accounts create $CONTASVC_IAM --project=$ID_PROJETO Created service account [sa-iam]. $ gcloud projects add-iam-policy-binding $ID_PROJETO --member "serviceAccount:$CONTASVC_IAM@$ID_PROJETO.iam.gserviceaccount.com" --role "roles/storage.objectViewer" Updated IAM policy for project [gke-workloadid-demo]. auditConfigs: - auditLogConfigs: - logType: DATA_READ service: storage.googleapis.com bindings: ... - members: - serviceAccount:sa-iam@gke-workloadid-demo.iam.gserviceaccount.com role: roles/storage.objectViewer etag: BwYEF_6GRIs= version: 1
Permita que a conta de serviço do Kubernetes atue como (impersonate) a conta de serviço do IAM, adicionando uma vinculação de política do IAM (IAM policy binding) entre as duas contas de serviço. Essa vinculação, ou binding, permite que a conta de serviço do Kubernetes atue como a conta de serviço do Google.
gcloud iam service-accounts add-iam-policy-binding $CONTASVC_IAM@$ID_PROJETO.iam.gserviceaccount.com --role roles/iam.workloadIdentityUser --member "serviceAccount:$ID_PROJETO.svc.id.goog[$NAMESPACE_K8S/$CONTASVC_K8S]"
Como exemplo, o meu comando e output foi:
$ gcloud iam service-accounts add-iam-policy-binding $CONTASVC_IAM@$ID_PROJETO.iam.gserviceaccount.com --role roles/iam.workloadIdentityUser --member "serviceAccount:$ID_PROJETO.svc.id.goog[$NAMESPACE_K8S/$CONTASVC_K8S]" Updated IAM policy for serviceAccount [sa-iam@gke-workloadid-demo.iam.gserviceaccount.com]. bindings: - members: - serviceAccount:gke-workloadid-demo.svc.id.goog[wli-ns/sa-k8s] role: roles/iam.workloadIdentityUser etag: BwYEGAbAN7o= version: 1
Crie um Annotate na conta de serviço do Kubernetes com o endereço de e-mail da conta de serviço do IAM. Execute o comando abaixo com o
kubectl
:kubectl annotate serviceaccount $CONTASVC_K8S --namespace $NAMESPACE_K8S iam.gke.io/gcp-service-account=$CONTASVC_IAM@$ID_PROJETO.iam.gserviceaccount.com
Output:
serviceaccount/sa-k8s annotate
Para entender a alteração que realizamos em nossa conta de serviço do Kubernetes:
Antes, não temos nada no valor da chave "Annotations":
Name: sa-k8s Namespace: wli-ns Labels: <none> Annotations: <none> Image pull secrets: <none> Mountable secrets: <none> Tokens: <none> Events: <none>
Depois, o e-mail da conta de serviço do IAM está como valor da chave "Annotations":
Name: sa-k8s Namespace: wli-ns Labels: <none> Annotations: iam.gke.io/gcp-service-account: sa-iam@gke-workloadid-demo.iam.gserviceaccount.com Image pull secrets: <none> Mountable secrets: <none> Tokens: <none> Events: <none>
3️⃣ Criar um Pod e usar o Workload Identity
Uma vez realizada todas as etapas anteriores, vamos criar um Pod com a conta de serviço do Kubernetes para validar as configurações do Workload Identity e acessar o bucket do Cloud Storage que criamos.
Copie e execute o conteúdo abaixo em seu terminal para criar o manifesto teste-pod-wli.yaml:
cat <<EOF > teste-pod-wli.yaml apiVersion: v1 kind: Pod metadata: name: teste-pod namespace: $NAMESPACE_K8S spec: containers: - image: google/cloud-sdk:slim name: teste-pod command: ["sleep","infinity"] serviceAccountName: $CONTASVC_K8S EOF
Crie o Pod usando o manifesto teste-pod-wli.yaml com o comando:
kubectl apply -f teste-pod-wli.yaml
Para verificar o status do Pod execute:
kubectl get pod teste-pod -n $NAMESPACE_K8S
Aguarde até que o status do Pod esteja como RUNNING.
Abra uma sessão interativa no Pod executando o comando:
kubectl exec -it teste-pod -n $NAMESPACE_K8S -- bash
Uma vez no shell dentro do pod, para verificar se a conta de serviço do IAM está devidamente configurada execute
gcloud auth list
.Como exemplo, o meu comando foi executado com sucesso e tive o output:
root@teste-pod:/# gcloud auth list Credentialed Accounts ACTIVE ACCOUNT * sa-iam@gke-workloadid-demo.iam.gserviceaccount.com To set the active account, run: $ gcloud config set account `ACCOUNT` root@teste-pod:/#
Essa execução vai basicamente fazer uma requisição para o Metadata da Instância de VM.
Execute a consulta abaixo no Cloud Logging:
resource.type="k8s_container" "service-accounts"
Exemplo de log que encontraremos:
{ "insertId": "ys6jzb9gmrismu9t", "jsonPayload": { "message": "[conn-id:621f015e5e71fdde ip:10.88.2.6 pod:wli-ns/teste-pod rpc-id:e999aad0f9a37933] \"/computeMetadata/v1/instance/service-accounts/\" HTTP/200, started at 2023-08-29 23:32:37.120077655 +0000 UTC m=+5156.012258412", "pid": "2855" }, "resource": { "type": "k8s_container", "labels": { "cluster_name": "wli-gke", "container_name": "gke-metadata-server", "namespace_name": "kube-system", "project_id": "gke-workloadid-demo", "pod_name": "gke-metadata-server-dv9lv", "location": "us-south1" } }, "timestamp": "2023-08-29T23:32:37.120311425Z", "severity": "INFO", "labels": { "k8s-pod/k8s-app": "gke-metadata-server", "k8s-pod/pod-template-generation": "1", "k8s-pod/addonmanager_kubernetes_io/mode": "Reconcile", "k8s-pod/controller-revision-hash": "fddb6849b", "compute.googleapis.com/resource_name": "gke-wli-gke-default-pool-c86ef146-dmm9" }, "logName": "projects/gke-workloadid-demo/logs/stderr", "sourceLocation": { "file": "metadata.go", "line": "168" }, "receiveTimestamp": "2023-08-29T23:32:40.151494713Z" }
Dentro do shell do Pod, copie o arquivo que está no bucket do Cloud Storage para o seu Pod.
gcloud storage cp gs://bucket-ID_PROJETO/arquivobucket.txt .
Como as variáveis de ambiente criadas no início não serem vistas dentro do pod, substitua o
ID_PROJETO
pelo o ID do seu projeto.Como exemplo, copiei o arquivo com sucesso e consegui ler o conteúdo dele:
root@teste-pod:/# gcloud storage cp gs://bucket-gke-workloadid-demo/arquivobucket.txt . Copying gs://bucket-gke-workloadid-demo/arquivobucket.txt to file://./arquivobucket.txt Completed files 1/1 | 46.0B/46.0B root@teste-pod:/# cat arquivobucket.txt Arquivo que está no bucket do Cloud Storage!
Verifique as logs de leitura do Bucket no Cloud Logging realizando a consulta:
resource.type="gcs_bucket" protoPayload.methodName="storage.objects.get"
Abaixo temos um exemplo de log que encontraremos, sendo que o
principalEmail
utilizado na autenticação da requisição é a nossa conta de serviço do IAM. Isso constata que nossa conta de serviço do Kubernetes está assumindo a conta de serviço do IAM para realizar requisições na API do Cloud Storage.{ "protoPayload": { "@type": "type.googleapis.com/google.cloud.audit.AuditLog", "status": {}, "authenticationInfo": { "principalEmail": "sa-iam@gke-workloadid-demo.iam.gserviceaccount.com", "serviceAccountDelegationInfo": [ { "principalSubject": "serviceAccount:gke-workloadid-demo.svc.id.goog[wli-ns/sa-k8s]" } ] }, "requestMetadata": { "callerIp": "34.174.118.174", "callerSuppliedUserAgent": "google-cloud-sdk gcloud/444.0.0 command/gcloud.storage.cp invocation-id/65b65347aafe4183b3a3248679cb009a environment/github_docker_image environment-version/None client-os/LINUX client-os-ver/5.15.109 client-pltf-arch/x86_64 interactive/True from-script/False python/3.9.16 term/xterm (Linux 5.15.109+),gzip(gfe)", "callerNetwork": "//compute.googleapis.com/projects/gke-workloadid-demo/global/networks/__unknown__", "requestAttributes": { "time": "2023-08-29T23:40:05.060620370Z", "auth": {} }, "destinationAttributes": {} }, "serviceName": "storage.googleapis.com", "methodName": "storage.objects.get", "authorizationInfo": [ { "resource": "projects/_/buckets/bucket-gke-workloadid-demo/objects/arquivobucket.txt", "permission": "storage.objects.get", "granted": true, "resourceAttributes": {} } ], "resourceName": "projects/_/buckets/bucket-gke-workloadid-demo/objects/arquivobucket.txt", "resourceLocation": { "currentLocations": [ "us" ] } }, "insertId": "19s9jbwe3ef3d", "resource": { "type": "gcs_bucket", "labels": { "location": "us", "bucket_name": "bucket-gke-workloadid-demo", "project_id": "gke-workloadid-demo" } }, "timestamp": "2023-08-29T23:40:05.049883390Z", "severity": "INFO", "logName": "projects/gke-workloadid-demo/logs/cloudaudit.googleapis.com%2Fdata_access", "receiveTimestamp": "2023-08-29T23:40:05.728599032Z" }
Conclusão
Após essa jornada prática de como configurar e utilizar o Workload Identity em seu cluster GKE para acessar recursos do Google Cloud de forma segura, podemos entender que a principal ideia é reduzirmos as chances de um atacante ter sucesso em explorar nossos recursos do Google Cloud, uma vez que ele está no pod que utiliza uma conta de serviços do Kubernetes que está seguindo os princípios de menor privilégio e com logs das ações no Cloud Logging, que permitem a customização de alerta para detectar comportamentos anômalos.
Não sendo mais necessário a utilização das chaves de conta de serviço, muita das vezes configuradas dentro dos containers ou como secrets do Kubernetes, reduzimos significativamente os incidentes por conta de comprometimento dessas chaves.
O Workload Identity é um recurso poderoso para utilizarmos em um cluster GKE, que permitirá o refinamento das permissões dadas aos workloads em execução em um cluster, nos tira a preocupação com o gerenciamento manual de chaves de conta de serviço e os riscos de comprometimento das mesmas.
Referências
(Google Cloud - Workload Identity, 2023), https://cloud.google.com/kubernetes-engine/docs/concepts/workload-identity
(Google Cloud - Use Workload Identity, 2023), https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity
(Google Cloud - CIS Benchmarks, 2023), https://cloud.google.com/kubernetes-engine/docs/concepts/cis-benchmarks
(Google Cloud Tech - Secure access to GKE workloads with Workload Identity, 2023), https://www.youtube.com/watch?v=4OzbPaJCUr8