Workload Identity no GKE: uma forma mais segura de rodar suas aplicações

Alvaro David
google-cloud-brasil
6 min readDec 26, 2022

A maioria dos aplicativos que são executados no GKE não são ilhas, eles podem interagir com uma ampla variedade de outros serviços do Google Cloud. Um bom exemplo prático foi mostrado na publicação Implante seus sites do WordPress no GKE em minutos, em que conseguimos criar uma conexão entre um workload do GKE e o Cloud SQL usando uma Conta de Serviço do IAM com o role cloudsql.client.

Porém, para simplificar a demo, fizemos download e armazenamos a chave da conta de serviço como um secret do kubernetes, esse processo além de ser trabalhoso, não segue as melhores práticas de segurança, pois as chaves da conta de serviço que você cria e faz o download pelo IAM não têm uma data de expiração, e não perdem a validade a menos que você as exclua ou atualize.

No caso de violação ou comprometimento, uma chave não contabilizada pode significar acesso prolongado para um invasor. Esse possível ponto de falha, além da sobrecarga de gerenciamento de inventário e rotação de chaves, torna o uso de chaves de conta de serviço como secrets do kubernetes um método menos que ideal para autenticar workloads do GKE.

Outra opção é associar uma service account ao node pool e usar ela para acessar os recursos, porém essa service account acaba sendo global e usada por todos os PODs, acumulando permissões desnecessárias entre várias aplicações a fim de satisfazer todos os casos de uso. O Workload Identity permite deixar isso bem mais granular, com permissões específicas a cada workload.

Com o Workload Identity, você tem um mecanismo de autenticação mais seguro e fácil de configurar, usar e gerenciar.

Para esta demonstração, vamos usar Firestore como banco de dados e uma API feita em Go baseado no Firestore quickstart sample.

Arquitetura inicial

No Firestore, temos uma Coleção chamada “users” e um Documento com o ID “mysuperuser”.

Panel de Firestore

A API obtém os dados desde o Firestore e os retorna em formato JSON.

package main

import (
"encoding/json"
"log"
"net/http"
"context"

"cloud.google.com/go/firestore"
)

// MyUser definition for response API
type MyUser struct {
Name string `json:"name"`
Nickname string `json:nickname`
}

func createClient(ctx context.Context) *firestore.Client {
client, err := firestore.NewClient(ctx, "[YOUR-PROJECT-ID]")
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
return client
}

func handler(w http.ResponseWriter, r *http.Request) {

// Get a Firestore client.
ctx := context.Background()
client := createClient(ctx)
defer client.Close()

// [START firestore_setup_dataset_read]
dsnap, err := client.Collection("users").Doc("mysuperuser").Get(ctx)
if err != nil {
log.Fatalf("Failed to iterate: %v", err)
}

var myUser MyUser
dsnap.DataTo(&myUser)
// [END firestore_setup_dataset_read]

responseJSON, err := json.Marshal(myUser)
if err != nil {
log.Fatalf("Failed to parse json: %v", err)
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(responseJSON)
}

func main() {
log.Print("starting server...")
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}

Para fazer build da imagem do container desta API usei Buildpacks para não ter que usar um Dockerfile, e para armazenar na nuvem usei o Artifact Registry, você pode encontrar um exemplo de passo a passo na publicação Implante seu site em segundos com Cloud Run.

Para começar a usar o Workload Identity, siga as seguintes etapas

Exporte as primeiras variáveis globais

export PROJECT_ID=$(gcloud config list --format 'value(core.project)')
export NS=myapi-ns

Crie o cluster no GKE

gcloud container clusters create my-cluster \
--project=$PROJECT_ID \
--zone=southamerica-east1-c \
--workload-pool=$PROJECT_ID.svc.id.goog

Obtenha as credenciais do cluster

gcloud container clusters get-credentials my-cluster --zone southamerica-east1-c --project $PROJECT_ID

Crie o namespace que será usado para a conta de serviço do Kubernetes.

kubectl create namespace $NS

Crie a conta de serviço do Kubernetes

export KSA=myapi-ksa
kubectl create serviceaccount $KSA --namespace $NS

Crie a conta de serviço do IAM

export GSA=myapi-gsa
gcloud iam service-accounts create $GSA --project=$PROJECT_ID

Estas duas contas de serviço (do kubernetes e do IAM) vão ser a ponte entre o Kubernetes e o IAM, vamos criar uma vinculação de política do IAM entre as duas contas de serviço. Essa vinculação permite que a conta de serviço do Kubernetes atue como a conta do serviço do IAM.

gcloud iam service-accounts add-iam-policy-binding $GSA@$PROJECT_ID.iam.gserviceaccount.com \
--member "serviceAccount:$PROJECT_ID.svc.id.goog[$NS/$KSA]" \
--role roles/iam.workloadIdentityUser

kubectl annotate serviceaccount $KSA \
--namespace $NS \
iam.gke.io/gcp-service-account=$GSA@$PROJECT_ID.iam.gserviceaccount.com
Arquitetura usando Workload Identity

Precisamos que nossos pods tenham autorização para consultar dados no Firestore, para isso vamos atribuir o role de datastore.user à conta de serviço do IAM.

gcloud projects add-iam-policy-binding $PROJECT_ID \
--member "serviceAccount:$GSA@$PROJECT_ID.iam.gserviceaccount.com" \
--role roles/datastore.user

O passo final é implantar a imagem do container no cluster, o jeito mais simples é dar um apply no arquivo de manifesto que tem de forma declarativa tudo o que precisamos para a API:

kubectl apply -f myapi.yaml

O conteudo de myapi.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: myapi
namespace: myapi-ns
spec:
selector:
matchLabels:
app: myapi
template:
metadata:
labels:
app: myapi
annotations:
sidecar.istio.io/rewriteAppHTTPProbers: "true"
spec:
serviceAccountName: myapi-ksa
containers:
- name: server
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- all
privileged: false
readOnlyRootFilesystem: true
image: southamerica-east1-docker.pkg.dev/[YOUR-PROJECT-ID]/go-firestore/myapi@sha256:5e616…ac7d
ports:
- containerPort: 8080
env:
- name: PORT
value: "8080"
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: myapi
namespace: myapi-ns
spec:
type: ClusterIP
selector:
app: myapi
ports:
- name: http
port: 80
targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: myapi-external
namespace: myapi-ns
spec:
type: LoadBalancer
selector:
app: myapi
ports:
- name: http
port: 80
targetPort: 8080

Pontos importantes deste arquivo:

Deployment > metadata > namespace: Indicamos o namespace onde criamos a conta de serviço do Kubernetes.

Deployment > spec> template > spec >serviceAccountName: Indicamos a conta de serviço do Kubernetes.

Service > spec > type: [LoadBalancer] Expõe o serviço externamente usando o balanceador de carga do Google Cloud.

Espere o IP externo que está sendo gerado pelo Load Balancer do Google Cloud, com esse IP podemos consumir o serviço exposto:

kubectl get services -n $NS

# Resposta esperada
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
myapi ClusterIP 10.52.1.152 <none> 80/TCP 20h
myapi-external LoadBalancer 10.52.10.78 [YOUR-IP] 80:30196/TCP 20h

Teste a API:

curl [YOUR-IP]

# Resposta esperada
{"name":"Alvaro","Nickname":"AlvarDev"}

Noções básicas do GKE metadata server

Cada nó em um GKE com o Workload Identity ativado armazena os metadados no GKE metadata server, que é um conjunto dos endpoints do Compute Engine metadata server necessários para os workloads do kubernetes.

O GKE metadata server é executado como um DaemonSet, com um pod em cada nó do Linux ou um serviço de Windows Nativo em cada nó do Windows no cluster. O metadata server intercepta solicitações HTTP para http://metadata.google.internal (169.254.169.254:80). Por exemplo, a solicitação GET /computeMetadata/v1/instance/service-accounts/default/token recupera um token para a conta de serviço do IAM que o pod está configurado para personificar. O tráfego para o metadata server nunca sai da instância de VM que hospeda o pod.

Conclusão

O Workload Identity é uma maneira de ajudar a reduzir o potencial “raio de explosão” de uma violação ou comprometimento e sobrecarga de gerenciamento, ajudando você a aplicar o princípio do menor privilégio em seu ambiente. Ele faz isso automatizando as práticas recomendadas para autenticação de workloads, eliminando a necessidade de soluções alternativas e facilitando o cumprimento das melhores recomendações de segurança.

As chaves da conta de serviço podem se tornar um risco de segurança se não forem gerenciadas com cuidado, se você já as utiliza junto com GKE, teste o Workload Identity e conte-nos o que achou!. Para saber mais sobre como aumentar a proteção dos seus clusters temos a documentação do Google Cloud em https://cloud.google.com/kubernetes-engine/docs/how-to/hardening-your-cluster.

--

--