O que é OPA Gatekeeper?
OPA (Open Policy Agent) Gatekeeper é um validador de admissão (admission controller) nativo do Kubernetes que implementa o conceito de "Políticas como Código". Em vez de confiar em regras estáticas e difíceis de manter no cluster, o Gatekeeper permite que você defina políticas de segurança, conformidade e governança através de código declarativo usando a linguagem Rego. Essas políticas são aplicadas automaticamente no momento em que um recurso tenta ser criado ou modificado no cluster, bloqueando operações que não estejam em conformidade.
O Gatekeeper atua como intermediário entre a requisição do cliente (kubectl apply, por exemplo) e a persistência no etcd do Kubernetes. Quando você tenta fazer deploy de um Pod, Deployment ou qualquer outro recurso, o Gatekeeper analisa aquele recurso contra todas as políticas registradas. Se alguma política rejeitar o recurso, a operação é bloqueada antes mesmo de chegar ao cluster. Isso elimina a necessidade de revisar manualmente cada deploy e permite que você defina regras complexas de uma forma que é versionável, testável e auditável.
Instalação e Configuração Inicial
Instalando o Gatekeeper no seu cluster
A instalação do Gatekeeper é straightforward através do Helm ou dos manifestos YAML fornecidos pela comunidade OPA. Começaremos com a abordagem mais direta usando os manifestos oficiais:
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.14/deploy/gatekeeper.yaml
Esse comando cria um novo namespace chamado gatekeeper-system e implanta os componentes necessários: o webhook de validação, o webhook de mutação e o controlador OPA. Verifique se os pods estão rodando:
kubectl get pods -n gatekeeper-system
Você deve ver algo como gatekeeper-audit-* e gatekeeper-controller-* rodando com status Running. Se preferir usar Helm (recomendado para ambientes de produção), adicione o repositório Helm da Gatekeeper e faça a instalação:
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
helm install gatekeeper/gatekeeper --name-template=gatekeeper --namespace gatekeeper-system --create-namespace
Verificando a instalação
Para confirmar que o Gatekeeper está funcionando corretamente, verifique se os webhooks de validação estão registrados:
kubectl get validatingwebhookconfigurations | grep gatekeeper
Você deve ver a saída gatekeeper-validating-webhook-configuration. Isso significa que o Gatekeeper está pronto para interceder nas requisições de API do Kubernetes e avaliar as políticas.
Compreendendo Rego e a Linguagem de Políticas
Fundamentos da linguagem Rego
Rego é uma linguagem declarativa de consulta criada especificamente para políticas. Diferentemente de linguagens imperativas tradicionais, Rego funciona através de lógica: você descreve o que deveria ser verdadeiro, não como computar. Uma política em Rego geralmente responde a uma pergunta simples: "Esta operação deveria ser permitida?"
Um programa Rego básico consiste em regras. Uma regra define uma condição que, quando verdadeira, faz algo acontecer (permitir ou negar). Vamos ver um exemplo simples que nega qualquer Pod que não possua um label específico:
package kubernetes.admission
deny[msg] {
input.request.kind.kind == "Pod"
not input.request.object.metadata.labels.app
msg := "Pod deve ter o label 'app'"
}
Quebrando esse código: package kubernetes.admission define o escopo; deny[msg] é a regra que gera mensagens de negação; a seção entre chaves {} são as condições que devem ser atendidas para a negação ser acionada. input.request.kind.kind == "Pod" verifica se o recurso é um Pod. not input.request.object.metadata.labels.app verifica se o Pod não tem o label app. Se ambas as condições forem verdadeiras, o Pod é rejeitado com a mensagem especificada.
Estrutura de uma ConstraintTemplate
No Gatekeeper, você não escreve diretamente arquivos Rego avulsos. Em vez disso, você cria um ConstraintTemplate, que é um CRD (Custom Resource Definition) do Kubernetes que contém a lógica Rego e define os parâmetros que sua política aceita. Esse design permite que você crie restrições reutilizáveis e parametrizáveis.
Aqui está a estrutura típica de um ConstraintTemplate:
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
description: "Lista de labels obrigatórios"
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.securityContext.runAsNonRoot
msg := sprintf("Container '%v' deve rodar como usuário não-root", [container.name])
}
Neste exemplo, o ConstraintTemplate define um CRD chamado K8sRequiredLabels com um parâmetro labels que é uma lista de strings. A lógica Rego dentro da seção targets então usa esses parâmetros para validar recursos.
Implementando Políticas Práticas
Política 1: Garantir que imagens vêm de um registro confiável
Começaremos com uma política comum em ambientes de produção: garantir que todas as imagens de container usadas no cluster venham de registros confiáveis. Isso evita que imagens maliciosas ou não auditadas sejam implantadas.
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8sallowedregistries
spec:
crd:
spec:
names:
kind: K8sAllowedRegistries
validation:
openAPIV3Schema:
type: object
properties:
allowedRegistries:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedregistries
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
image := container.image
not image_from_allowed_registry(image)
msg := sprintf("Imagem '%v' não é de um registro permitido", [image])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
image := container.image
not image_from_allowed_registry(image)
msg := sprintf("Imagem init '%v' não é de um registro permitido", [image])
}
image_from_allowed_registry(image) {
registry := split(image, "/")[0]
registry == input.parameters.allowedRegistries[_]
}
image_from_allowed_registry(image) {
not contains(image, "/")
}
Agora você precisa criar uma Constraint (uma instância específica dessa política) para aplicá-la ao seu cluster:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRegistries
metadata:
name: require-allowed-registries
spec:
parameters:
allowedRegistries:
- "gcr.io"
- "docker.io"
- "registry.example.com"
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces: ["gatekeeper-system", "kube-system"]
Após aplicar ambos os YAMLs, qualquer tentativa de criar um Pod com uma imagem de um registro não permitido será bloqueada. Teste isso:
kubectl run test-pod --image=unauthorized-registry.com/malicious:latest
# Erro: admission webhook denied the request
Política 2: Exigir resource limits e requests
Clusters sem limites de recursos podem sofrer de "resource starvation", onde um único Pod consome todos os recursos disponíveis. Esta política garante que todo container defina requests e limits:
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8srequiredresources
spec:
crd:
spec:
names:
kind: K8sRequiredResources
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredresources
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.resources.requests.memory
msg := sprintf("Container '%v' deve definir requests.memory", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.resources.requests.cpu
msg := sprintf("Container '%v' deve definir requests.cpu", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.resources.limits.memory
msg := sprintf("Container '%v' deve definir limits.memory", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.resources.limits.cpu
msg := sprintf("Container '%v' deve definir limits.cpu", [container.name])
}
Aplique a constraint:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredResources
metadata:
name: require-resource-limits
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
- apiGroups: ["apps"]
kinds: ["Deployment", "StatefulSet", "DaemonSet"]
Política 3: Bloquear containers rodando como root
Containers rodando como root representam um risco de segurança significativo. Esta política garante que todos os containers rodem com um usuário não-root:
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8snoroot
spec:
crd:
spec:
names:
kind: K8sNoRoot
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8snoroot
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.securityContext.runAsNonRoot
msg := sprintf("Container '%v' deve ter runAsNonRoot: true", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.securityContext.runAsUser == 0
msg := sprintf("Container '%v' não pode rodar com UID 0 (root)", [container.name])
}
A constraint:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sNoRoot
metadata:
name: no-root-containers
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
- apiGroups: ["apps"]
kinds: ["Deployment", "StatefulSet"]
excludedNamespaces: ["gatekeeper-system"]
Testando, Monitorando e Troubleshooting
Testando suas políticas
Antes de aplicar políticas em produção, você deve testá-las para garantir que não bloqueiam recursos legítimos. O Gatekeeper fornece um modo "audit" que registra violações sem bloqueá-las:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRegistries
metadata:
name: test-allowed-registries
spec:
enforcementAction: "audit" # Em vez de "deny"
parameters:
allowedRegistries:
- "gcr.io"
Com enforcementAction: audit, violações são registradas no objeto Constraint mas não bloqueiam o deploy. Você pode então verificar as violações:
kubectl describe k8sallowedregistries test-allowed-registries
Procure pela seção Status.Total Violations. Quando estiver seguro, mude para enforcementAction: deny.
Verificando violações
Cada Constraint mantém um histórico de violações. Para ver quais recursos estão violando qual política:
kubectl get constraints
kubectl describe k8snoroot no-root-containers
Você também pode fazer query diretamente:
kubectl get k8snoroot -o json | jq '.items[].status.totalViolations'
Debugging de políticas
Se sua política não está funcionando como esperado, existem algumas técnicas úteis. Primeiro, verifique se o ConstraintTemplate foi criado corretamente:
kubectl get constrainttemplates
Depois, verifique se há erros de compilação Rego:
kubectl describe constrainttemplate k8snoroot
Se houver erros de sintaxe Rego, eles aparecerão em Status. Para testar a lógica Rego localmente antes de aplicar ao cluster, use o playground OPA em https://play.openpolicyagent.org ou instale o OPA localmente:
opa run -s
# Isso inicia um servidor interativo onde você pode testar suas policies
Você também pode verificar os logs do Gatekeeper:
kubectl logs -n gatekeeper-system -l gatekeeper.sh/system=yes -f
Conclusão
Aprendemos que o OPA Gatekeeper transforma a governança de clusters Kubernetes de um processo manual e propenso a erros em um sistema automatizado, auditável e versionável através de "Políticas como Código". Primeiro, entendemos que políticas são definidas em ConstraintTemplates usando a linguagem Rego, que permite expressões lógicas complexas de forma declarativa e legível. Segundo, implementamos três políticas práticas (validação de registros, resource limits e não-root) que cobrem casos de uso reais em produção. Finalmente, descobrimos que testar políticas através do modo audit antes de ativar enforcement é uma prática essencial que previne bloqueios inesperados.