Um auditor não te vai perguntar se o teu cluster Kubernetes é seguro. Vai pedir-te que mostres que um controlo existia, que funcionou durante todo o período auditado e que cada desvio foi aprovado. Um cluster bem endurecido mas sem registos chumba nesse teste.
Este guia é sobre produzir esses registos a partir do próprio cluster. A segurança das cargas de trabalho em produção está no nosso guia de DevSecOps para Kubernetes. Aqui encontras uma base do CIS Benchmark que podes correr hoje, regras de admissão que escrevem os seus próprios relatórios, exceções com responsável e data de validade, e um repositório de evidências mapeado para SOC 2 e ISO 27001.
O que um auditor realmente analisa por amostragem
Seja qual for o referencial, cada controlo do cluster tem de produzir três coisas:
- Uma definição. A regra, escrita e versionada: um ficheiro de política no Git, integrado depois de revisão.
- Prova de que funcionou. Registos datados que mostram que a regra foi aplicada durante todo o período, não apenas no dia da auditoria: resultados de análises, relatórios, entradas de log.
- Um registo de exceções. Cada sítio onde a regra não se aplica, com quem aprovou, porquê e até quando.
Num relatório SOC 2 Tipo II, o auditor escolhe datas de toda a janela de observação e pede a evidência de cada uma, por isso os registos têm de ser produzidos de forma agendada e guardados onde ninguém os possa editar sem deixar rasto. O nosso playbook de SOC 2 para startups cobre o âmbito e os controlos organizacionais; isto é a parte do cluster.
Passo 1: corre hoje uma análise do CIS Benchmark
O CIS Kubernetes Benchmark é uma base de hardening, não uma certificação: uma norma de configuração publicada que podes citar na tua política de segurança. O kube-bench, um scanner open source, verifica os teus nós contra ela.
O kube-bench lê ficheiros do host e inspeciona os seus processos, algo que o nível baseline do Pod Security proíbe. Dá-lhe um namespace próprio:
apiVersion: v1
kind: Namespace
metadata:
name: compliance-scans
labels:
# kube-bench reads host files and processes, which baseline forbids.
# Record this namespace as an exception, with the reason, in your register.
pod-security.kubernetes.io/enforce: privileged
pod-security.kubernetes.io/warn: baselineEssa etiqueta é a tua primeira exceção. Agora o Job:
apiVersion: batch/v1
kind: Job
metadata:
name: kube-bench-node
namespace: compliance-scans
spec:
backoffLimit: 0
template:
spec:
hostPID: true
restartPolicy: Never
containers:
- name: kube-bench
# Mirror the official kube-bench image into your registry and pin the release
image: registry.example.com/mirror/kube-bench:v0.16.0
command: ["kube-bench", "run", "--targets", "node"]
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 256Mi
volumeMounts:
- name: var-lib-kubelet
mountPath: /var/lib/kubelet
readOnly: true
- name: etc-systemd
mountPath: /etc/systemd
readOnly: true
- name: lib-systemd
mountPath: /lib/systemd
readOnly: true
- name: etc-kubernetes
mountPath: /etc/kubernetes
readOnly: true
- name: usr-bin
mountPath: /usr/local/mount-from-host/bin
readOnly: true
volumes:
- name: var-lib-kubelet
hostPath:
path: /var/lib/kubelet
- name: etc-systemd
hostPath:
path: /etc/systemd
- name: lib-systemd
hostPath:
path: /lib/systemd
- name: etc-kubernetes
hostPath:
path: /etc/kubernetes
- name: usr-bin
hostPath:
path: /usr/binCorre-o e guarda o resultado:
kubectl apply -f compliance-scans-namespace.yaml -f kube-bench-node.yaml
kubectl -n compliance-scans wait --for=condition=complete job/kube-bench-node --timeout=5m
kubectl -n compliance-scans logs job/kube-bench-node > "kube-bench-$(date -u +%F).txt"
grep -E '^\[(FAIL|WARN)\]' "kube-bench-$(date -u +%F).txt"Um Job só analisa o nó onde é agendado, por isso acrescenta um nodeSelector, corre um por node pool e apaga o Job terminado antes da execução seguinte. Num serviço gerido, parte da variante do Job para o teu fornecedor que o repositório do kube-bench inclui; monta os caminhos que esses nós usam. O kube-bench escolhe o benchmark a partir da tua versão de Kubernetes e, se não a conhecer, recorre ao mais recente que tiver; passa --benchmark para avaliar todas as execuções contra o mesmo documento, e --json quando o resultado for para o teu repositório de evidências.
Como ler os resultados
Cada verificação imprime uma linha, neste formato:
[PASS] 4.2.1 Ensure that the --anonymous-auth argument is set to false (Automated)
[FAIL] 4.2.6 Ensure that the --make-iptables-util-chains argument is set to true (Automated)
[WARN] 4.2.13 Ensure that a limit is set on pod PIDs (Manual)- FAIL significa que uma verificação automatizada correu e o nó não cumpre. Cada uma passa a ser um ticket ou uma aceitação de risco por escrito.
- WARN não significa que passou. O kube-bench reporta como WARN todas as verificações Manual e as não pontuadas que não conseguiu confirmar. Alguém tem de rever cada uma e registar a resposta.
- A secção de remediação dá a correção para cada FAIL e WARN. Aplica as correções do kubelet na imagem do nó ou na configuração do node pool; uma alteração feita num nó em funcionamento perde-se quando o nó é substituído.
Os identificadores das verificações mudam entre versões do benchmark, por isso guarda a versão junto de cada resultado.
O que não é contigo num control plane gerido
Num serviço gerido, o control plane corre em máquinas do fornecedor, fora do teu alcance. Os CIS Benchmarks para serviços geridos reduzem os seus capítulos a pouco mais do que ativar os logs de auditoria, e acrescentam um capítulo para as definições que controlas através da API do fornecedor.
- Responsabilidade do fornecedor: flags e permissões de ficheiros dos componentes do control plane, configuração e cifragem do etcd, atualizações de segurança do control plane. A tua evidência aqui é o próprio relatório de auditoria ou certificação do fornecedor, arquivado na gestão de fornecedores.
- Continua a ser teu: a configuração dos worker nodes (através da imagem do nó e das definições do node pool), se os logs de auditoria do control plane estão ativos e para onde vão, o acesso de rede ao endpoint da API, a cifragem dos Secrets com uma chave gerida por ti, as identidades cloud que os nós e as cargas de trabalho usam, e que registries de imagens estão aprovados.
Se essas definições estiverem em Terraform, o código e o seu histórico de revisão são a definição e o registo de alterações. Num cluster autogerido, corre também um Job num nó do control plane com os targets master, controlplane e etcd; o repositório também inclui essa variante.
Passo 2: Pod Security Admission, sem instalar nada
O Pod Security Admission vem integrado no Kubernetes e é estável desde a 1.25. Aplica os níveis dos Pod Security Standards (privileged, baseline, restricted) por namespace, através de etiquetas, em três modos: enforce rejeita, audit regista e warn avisa quem está a aplicar o manifesto.
Um ponto de partida sensato é aplicar baseline e auditar e avisar em restricted:
apiVersion: v1
kind: Namespace
metadata:
name: payments
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latestAntes de etiquetar namespaces de produção, um dry run no servidor avisa sobre os pods existentes que violariam o nível, sem alterar nada:
kubectl label --dry-run=server --overwrite ns --all \
pod-security.kubernetes.io/enforce=baselineHá dois detalhes que apanham muitas equipas:
- O enforce verifica pods, não cargas de trabalho. Um Deployment que viola o nível aplicado é aceite; depois o seu ReplicaSet não consegue criar pods e o único rasto é um evento FailedCreate. Os modos
warneauditverificam mesmo os recursos de carga de trabalho, e é por isso que os defines a par doenforce. - O modo audit escreve no log de auditoria do API server, como anotação do pedido (
pod-security.kubernetes.io/audit-violations). Se o log de auditoria estiver desligado, o modo audit não regista nada.
O que o Pod Security Admission não consegue fazer é verificar seja o que for fora dos Pod Security Standards, produzir um relatório exportável ou conceder uma exceção mais estreita do que um namespace. Para isso é preciso um motor de políticas.
Queres verificar isto nos teus próprios clusters? Numa avaliação gratuita de conformidade em Kubernetes corremos contigo a análise CIS, revemos as etiquetas dos teus namespaces e as tuas políticas de admissão, e separamos as constatações que são tuas das que pertencem ao teu fornecedor.
Passo 3: políticas como código com o Kyverno
O Kyverno é um motor de políticas open source que funciona como webhook de admissão. Além disso, volta a analisar em segundo plano os recursos existentes e escreve objetos PolicyReport: a prova de funcionamento que um auditor pede.
Uma nota de versão: desde o Kyverno 1.19, o tipo ClusterPolicy, usado há muito tempo, está descontinuado e aplicar um devolve um aviso. O seu substituto, ValidatingPolicy, escreve-se em CEL, a linguagem de expressões das políticas de admissão integradas no Kubernetes. Os exemplos abaixo foram testados com o CLI do Kyverno 1.19.2.
Atenção também ao âmbito: por omissão, o Helm chart faz o Kyverno ignorar kube-system, kube-public, kube-node-lease e o seu próprio namespace. Se não mudares isso, regista esses namespaces no teu mapeamento de controlos como excluídos, com o motivo: um auditor vai perguntar que namespaces cada controlo não cobre.
Proibir contentores privilegiados
O nível baseline do Pod Security já os bloqueia nos namespaces etiquetados. Repetir a regra no Kyverno cobre também os namespaces sem etiquetas ou etiquetados como privileged, gera uma entrada de relatório por carga de trabalho e permite-te isentar uma única imagem em vez de um namespace. Apanha ainda as sessões de kubectl debug, já que estas acrescentam contentores efémeros a pods em funcionamento.
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: disallow-privileged-containers
spec:
validationActions: [Audit, Warn]
evaluation:
background:
enabled: true
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments", "statefulsets", "daemonsets"]
- apiGroups: ["batch"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["jobs", "cronjobs"]
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["UPDATE"]
resources: ["pods/ephemeralcontainers"]
variables:
- name: podSpec
expression: >-
object.spec.?jobTemplate.?spec.?template.?spec.orValue(
object.spec.?template.?spec.orValue(object.spec))
- name: allContainers
expression: >-
variables.podSpec.containers +
variables.podSpec.?initContainers.orValue([]) +
variables.podSpec.?ephemeralContainers.orValue([])
validations:
- expression: >-
variables.allContainers.all(c,
(has(exceptions.allowedImages) && c.image in exceptions.allowedImages) ||
!c.?securityContext.?privileged.orValue(false))
message: "Privileged containers are not allowed. Remove securityContext.privileged or request an exception."Cada política cobre os recursos de carga de trabalho além dos pods: uma política só para pods em modo Deny aceita o Deployment e rejeita os seus pods em silêncio, a mesma armadilha do modo enforce do Pod Security.
Exigir requests e limits
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: require-requests-limits
spec:
validationActions: [Audit, Warn]
evaluation:
background:
enabled: true
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments", "statefulsets", "daemonsets"]
- apiGroups: ["batch"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["jobs", "cronjobs"]
variables:
- name: podSpec
expression: >-
object.spec.?jobTemplate.?spec.?template.?spec.orValue(
object.spec.?template.?spec.orValue(object.spec))
validations:
- expression: >-
variables.podSpec.containers.all(c,
c.?resources.?requests.?cpu.hasValue() &&
c.?resources.?requests.?memory.hasValue() &&
c.?resources.?limits.?memory.hasValue())
message: "Every container needs CPU and memory requests and a memory limit."Restringir as imagens a um registry aprovado
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: restrict-image-registries
spec:
validationActions: [Audit, Warn]
evaluation:
background:
enabled: true
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments", "statefulsets", "daemonsets"]
- apiGroups: ["batch"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["jobs", "cronjobs"]
variables:
- name: podSpec
expression: >-
object.spec.?jobTemplate.?spec.?template.?spec.orValue(
object.spec.?template.?spec.orValue(object.spec))
- name: allContainers
expression: >-
variables.podSpec.containers +
variables.podSpec.?initContainers.orValue([]) +
variables.podSpec.?ephemeralContainers.orValue([])
validations:
- expression: >-
variables.allContainers.all(c,
c.image.startsWith('registry.example.com/'))
message: "Images must come from registry.example.com. Mirror third-party images there first."Se as imagens de terceiros forem replicadas para o teu registry e analisadas lá, esta política torna essa convenção obrigatória. O Job do kube-bench acima cumpre as três políticas.
Primeiro auditar, depois bloquear
As três políticas começam com validationActions: [Audit, Warn]: as violações vão para os relatórios e quem aplica um manifesto não conforme vê um aviso, mas nada é bloqueado. Quando o relatório de uma política não mostrar falhas fora das exceções registadas, muda-a para [Deny] através de um merge request revisto, que serve também de registo da alteração. Muda uma política de cada vez.
Corre as mesmas políticas em CI para que os programadores saibam primeiro. Isto termina com um código diferente de zero quando um manifesto falha, e carrega os ficheiros de exceções (secção seguinte) para que as cargas de trabalho aprovadas não partam o build:
kyverno apply policies/ --resource manifests/ \
--exception "$(find exceptions -name '*.yaml' | paste -sd, -)"O nosso guia sobre um pipeline de CI/CD seguro com GitOps mostra onde encaixa essa barreira.
Exceções, registadas
Algumas cargas de trabalho precisam mesmo do que uma política proíbe: os drivers de armazenamento e de rede correm muitas vezes como privilegiados. As exceções vêm desativadas por omissão no Helm chart do Kyverno. Ativa-as para um único namespace, para que criar uma seja uma permissão que concedes de propósito:
# values.yaml for the Kyverno Helm chart
features:
policyExceptions:
enabled: true
namespace: policy-exceptionsCada exceção passa a ser um ficheiro revisto no Git:
apiVersion: policies.kyverno.io/v1
kind: PolicyException
metadata:
name: csi-node-driver-privileged
namespace: policy-exceptions
spec:
policyRefs:
- name: disallow-privileged-containers
kind: ValidatingPolicy
# Exact image reference: a new driver version needs a new approval
images:
- registry.example.com/mirror/csi-node-driver:2.1.0
expiresAt: "2027-04-30T00:00:00Z"
properties:
reason: "Storage driver mounts volumes on the host; needs privileged mode"
ticket: "SEC-142"
approved-by: "head-of-engineering"A lista images exige correspondência exata, por isso atualizar o driver obriga a uma nova aprovação. expiresAt e properties chegaram com o Kyverno 1.19: passada a data, a exceção deixa de se aplicar, e o motivo, o ticket e quem aprovou viajam com ela em cada exportação.
Passo 4: transforma relatórios e logs em evidências
Os relatórios de políticas mostram o estado atual: quando uma violação é corrigida, desaparece também o registo de que existiu. Os eventos do Kubernetes expiram, por omissão ao fim de uma hora. Nenhum dos dois é histórico, por isso faz snapshots de forma agendada.
O log de auditoria do API server
O log de auditoria regista quem alterou o quê, incluindo cada pedido que o controlo de admissão recusou. Num cluster autogerido, és tu que escreves a política de auditoria:
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- "RequestReceived"
rules:
# Secrets: record who touched them, never their content
- level: Metadata
resources:
- group: ""
resources: ["secrets"]
# RBAC and admission policy changes: the full request and response
- level: RequestResponse
resources:
- group: "rbac.authorization.k8s.io"
- group: "policies.kyverno.io"
# Interactive access to running containers
- level: Metadata
resources:
- group: ""
resources: ["pods/exec", "pods/attach", "pods/portforward"]
# Every other change, including admission denials
- level: Metadata
verbs: ["create", "update", "patch", "delete", "deletecollection"]Num serviço gerido, normalmente ativas os logs de auditoria do control plane e escolhes para onde vão. Fá-lo no primeiro dia: um período de auditoria não pode começar antes dos seus logs.
Uma exportação diária
Corre isto a partir de um job de CI agendado ou de um CronJob com acesso só de leitura:
#!/usr/bin/env bash
set -euo pipefail
OUT="evidence/$(date -u +%F)"
mkdir -p "$OUT"
# What is enforced, and every approved exception
kubectl get validatingpolicies.policies.kyverno.io -o yaml > "$OUT/policies.yaml"
kubectl get policyexceptions.policies.kyverno.io -A -o yaml > "$OUT/exceptions.yaml"
kubectl get namespaces -L pod-security.kubernetes.io/enforce,pod-security.kubernetes.io/audit,pod-security.kubernetes.io/warn > "$OUT/psa-labels.txt"
# Policy results as they stand today
kubectl get policyreports -A -o json > "$OUT/policyreports.json"
kubectl get clusterpolicyreports -o json > "$OUT/clusterpolicyreports.json"
# One line per namespace: failures and passes
jq -r '.items | group_by(.metadata.namespace)[]
| [.[0].metadata.namespace, (map(.summary.fail // 0) | add), (map(.summary.pass // 0) | add)]
| @tsv' "$OUT/policyreports.json" > "$OUT/summary-by-namespace.tsv"
# Fingerprint the snapshot before it leaves the cluster
(cd "$OUT" && sha256sum -- * > SHA256SUMS)Acrescenta o JSON do kube-bench de cada análise e a exportação dos bindings de RBAC descrita no playbook de SOC 2. Depois define a cadência:
- Relatórios de políticas, exceções e etiquetas de namespaces: diariamente.
- kube-bench: semanalmente, e depois de cada atualização do Kubernetes ou mudança da imagem dos nós.
- Log de auditoria: continuamente, enviado à medida que é escrito.
Guarda tudo em armazenamento de objetos com versionamento e bloqueio de retenção, numa conta separada da qual os administradores do cluster não possam apagar, escrito com credenciais que podem acrescentar objetos mas não removê-los. Mantém-no pelo menos durante o período auditado mais o tempo até à emissão do relatório, ou mais se o teu SGSI o exigir.
Mapear os controlos do cluster para SOC 2 e ISO 27001
Nenhum dos dois referenciais menciona o Kubernetes, mas cada exportação acima encaixa numa área de controlo que ambos exigem:
- Resultados da análise CIS e tickets de remediação. ISO 27001: gestão da configuração e gestão de vulnerabilidades técnicas. SOC 2: operações do sistema, que inclui detetar alterações de configuração que introduzem vulnerabilidades.
- Etiquetas do Pod Security e a política de contentores privilegiados. ISO 27001: direitos de acesso privilegiado e gestão da configuração. SOC 2: acesso lógico, restringir o acesso privilegiado à infraestrutura.
- A política de registry aprovado. ISO 27001: instalação de software em sistemas em produção. SOC 2: gestão de alterações, já que só chegam à produção imagens de um registry que controlas.
- Requests e limits. ISO 27001: gestão da capacidade. SOC 2: os critérios de disponibilidade, se a disponibilidade estiver no teu âmbito.
- Alterações de políticas através de merge requests. Gestão de alterações, em ambos os referenciais.
- Log de auditoria e snapshots dos relatórios de políticas. ISO 27001: registo de eventos e atividades de monitorização. SOC 2: operações do sistema.
- O registo de exceções. Ambos esperam que os desvios sejam aprovados, limitados no tempo e revistos; os auditores analisam o registo por amostragem como qualquer outra população.
Se a NIS2 ou o DORA se aplicarem a ti, as mesmas exportações suportam os seus requisitos de gestão do risco de TIC. Põe o mapeamento por escrito num documento curto que aponte cada controlo para o caminho da sua evidência: é o que entregas ao auditor no primeiro dia.
Uma implementação em seis semanas
- Semana 1, base. Corre o kube-bench em cada node pool e transforma cada FAIL num ticket ou numa aceitação de risco por escrito. Ativa o log de auditoria do API server. Entregáveis: a primeira análise no repositório de evidências, uma lista de constatações triada, logs de auditoria a fluir.
- Semana 2, Pod Security. Faz o dry run e depois etiqueta os namespaces das aplicações para aplicar baseline, com audit e warn em restricted. Regista cada namespace que não consiga cumprir baseline, com o motivo. Entregáveis: a exportação das etiquetas dos namespaces e a respetiva lista de exceções.
- Semana 3, Kyverno em auditoria. Instala-o com as exceções ativadas para um namespace, aplica as políticas com
[Audit, Warn]e acrescenta a verificação em CI. Entregáveis: políticas no Git, o primeiro snapshot de relatórios, a barreira de CI. - Semana 4, corrigir e excecionar. Trabalha as falhas com as equipas responsáveis; o que não puder ser corrigido recebe uma exceção com data de validade e aprovador. Entregáveis: uma contagem de falhas a descer e o registo de exceções.
- Semana 5, bloquear. Passa para
[Deny]cada política com o relatório limpo, um merge request por política. Entregáveis: os registos de alterações. - Semana 6, evidências. Agenda a exportação diária, bloqueia o armazenamento e escreve o mapeamento dos controlos. Depois ensaia: escolhe uma data passada ao acaso e apresenta a evidência de cada controlo. Entregáveis: o repositório de evidências, o mapeamento e as notas do ensaio.
A partir daí o trabalho é rotina: análises semanais, uma revisão mensal das exceções e uma verificação trimestral de que o mapeamento continua a corresponder ao cluster.
Pede uma avaliação gratuita de conformidade em Kubernetes
Se tens uma auditoria à porta e não sabes o que os teus clusters mostrariam hoje, a nossa equipa de DevSecOps trabalha isso contigo sem custos.
A avaliação gratuita inclui:
- Uma execução do CIS Benchmark nos teus node pools, com cada constatação classificada como tua, do teu fornecedor ou risco aceite
- Uma revisão dos teus controlos de admissão: etiquetas dos namespaces, políticas, e como as exceções são aprovadas e se expiram
- Uma lista de lacunas de evidências por área de controlo do SOC 2 ou da ISO 27001: o que os teus clusters registam hoje, o que falta e como recolhê-lo de forma automática
Recebes uma lista por escrito e um plano à medida da tua equipa, sem qualquer compromisso. Pede a tua avaliação gratuita de conformidade em Kubernetes, ou vê como a nossa equipa de consultoria Kubernetes gere clusters em produção.