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.

Arquitetura Workload Identity GKE por Douglas Luna

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

  1. 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
    
  2. Verificar se você tem as roles do IAM:

    • Kubernetes Engine Admin - roles/container.admin

    • Service Account Admin - roles/iam.serviceAccountAdmin

  3. 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.

  1. 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

  2. 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.

    Verificação do Workload Identity no console do Google Cloud


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

  1. 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
    
  2. 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

  1. 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

  2. Crie um arquivo conforme abaixo:

     echo "Arquivo que está no bucket do Cloud Storage!" > arquivobucket.txt
    
  3. Copie o arquivo para seu bucket:

     gcloud storage cp file://arquivobucket.txt $MEU_BUCKET
    
  4. Habilite os logs de leitura de dados em IAM e administrador > Registros de auditoria:

    1. Filtre por Google Cloud Storage.

    2. Selecione a caixa ao lado do nome de serviço.

    3. Selecione a caixa de Leitura de dados.

    4. Clique em Salvar.

Habilitando os logs de leitura de dados do Cloud Storage

2.3. Criar uma conta de serviço do IAM

  1. 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
    
  2. 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
    
  3. 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
    
  4. 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.

  1. 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
    
  2. 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.

  3. Abra uma sessão interativa no Pod executando o comando:

     kubectl exec -it teste-pod -n $NAMESPACE_K8S -- bash
    
  4. 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"
     }
    
  5. 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!
    
  6. 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

Did you find this article valuable?

Support Douglas Luna - Blog by becoming a sponsor. Any amount is appreciated!