Kubernetes Auditing
O kube-apiserver recebe todas as request para qualquer interação com o cluster.
É possível salvar todos esses request, dados e metadados em um datastore chamado audit logs. Podemos utilizar esse histórico para consulta e aprimoramento da segurança do cluster, análises e debug.
Salvar o audit log em um cluster Kubernetes é fundamental para garantir a segurança, conformidade e observabilidade do ambiente. O audit log é uma trilha de auditoria que registra todas as ações realizadas no cluster, desde chamadas de API até mudanças de configuração.
Importância do Audit Log no Kubernetes
-
Segurança e Detecção de Ameaças
: Identificar atividades suspeitas ou mal-intencionadas no cluster, como tentativas de acesso não autorizado, mudanças não planejadas, ou exploração de vulnerabilidades. -
Conformidade e Auditoria
: Para muitas organizações, especialmente aquelas em setores regulamentados (financeiro, saúde, etc.), é necessário manter uma trilha de auditoria das ações realizadas no ambiente. Os logs de auditoria são frequentemente exigidos por padrões de conformidade como GDPR, HIPAA, PCI-DSS, entre outros. -
Investigação de Incidentes
: Em caso de falhas ou comportamento inesperado do cluster, o audit log fornece informações valiosas para investigar a causa raiz. Ele pode mostrar mudanças específicas que levaram ao problema, como alterações de configuração, escalonamentos de privilégio ou modificações em recursos críticos. -
Monitoramento e Análise
: Fonte importante de dados para monitoramento contínuo e análise de segurança. Outras ferramentas podem consumir logs de auditoria do Kubernetes para gerar alertas em tempo real e relatórios detalhados de segurança. -
Gerenciamento de Políticas
: Permite validar se as políticas de segurança e operacionais estão sendo seguidas. Por exemplo, ele pode ser usado para monitorar se apenas usuários autorizados estão realizando ações específicas no cluster.
Como Configurar e Utilizar o Audit Log no Kubernetes
Podemos decidir qual o nível de log que gostaríamos de salvar, assim como em várias ferramentas.
Para salvar o audit log no Kubernetes, é necessário habilitar o recurso de auditoria na API Server. Isso envolve a configuração de um policy file (arquivo de política) que define quais eventos devem ser registrados e o nível de detalhamento.
Stages VS Levels
Os stages no audit log representam as diferentes fases do ciclo de vida de uma solicitação feita para o kube-apiserver. Eles ajudam a rastrear como cada solicitação é processada, desde o momento em que é recebida até o momento em que é concluída ou descartada.
Descrevem em que ponto do processamento uma solicitação está.
Os stages possíveis são:
-
RequestReceived
: Este é o primeiro estágio, indicando que o servidor da API recebeu a solicitação, mas ainda não a processou. Nesse ponto, o servidor apenas registrou o recebimento da solicitação. -
ResponseStarted
: Indica o estágio em que o servidor da API começou a enviar uma resposta ao cliente, mas ainda não completou o envio da resposta. Ele é útil para monitorar solicitações longas que podem demorar para serem concluídas. -
ResponseComplete
: Estágio quando o servidor da API completou o processamento da solicitação e enviou a resposta completa de volta ao cliente. Este é o estágio mais comum para verificar se a solicitação foi bem-sucedida ou não. -
Panic
: Este estágio indica que ocorreu um erro crítico ou um panic interno no servidor da API durante o processamento da solicitação. É um sinal de que algo falhou de maneira inesperada, e é crucial para fins de depuração e auditoria.
os Levels determinam a quantidade de informações registradas no log de auditoria em um stage. É a profundidade ou a granularidade das informações auditadas, ou seja a verbosidade.
-
None
: Não registra informações. Usado para ignorar completamente a auditoria de uma determinada solicitação. -
Metadata
: Registra apenas informações básicas sobre a solicitação, como o método HTTP, caminho da API, tempo de solicitação, usuário, e informações de autenticação. É útil para uma auditoria de alto nível. -
Request
: Inclui o metadata e o conteúdo do corpo da solicitação (payload). Usado quando é necessário um rastreamento completo da solicitação, incluindo os dados enviados e recebidos, mas irá consumir mais armazenamento. -
RequestResponse
: Inclui o Request e a resposta, mas é somente aplicado a request de recursos.
Aspecto | Stages | Levels |
---|---|---|
Definição | Representam as fases do ciclo de vida de uma solicitação na API do Kubernetes. | Determinam a profundidade das informações a serem registradas nos logs de auditoria. |
Objetivo | Ajudam a entender em que ponto do processamento uma solicitação se encontra. | Controlam a quantidade e o tipo de informações registradas para cada solicitação. |
Tipos | RequestReceived, ResponseStarted, ResponseComplete, Panic. | None, Metadata, Request, RequestResponse. |
Uso | Para identificar como e quando uma solicitação é processada e completada. | Para definir o nível de detalhes necessários para auditoria. |
Granularidade | Relacionados à cronologia da solicitação (quando ela é recebida, respondida, ou falha). | Relacionados ao conteúdo e detalhamento da auditoria (apenas cabeçalho, cabeçalho e corpo, etc.). |
Obviamente que temos muitas api requests teremos muito mais dados para salvar o que pode poluir demais o log ou ser mais díficil para procurar. Os request não são gerados somente pos nós quando executamos comando kubectl, mas são gerados por vários componentes do kubernetes que se comunicam com ele como controller manager, scheduller, kubelet, controladores de terceiros, etc.
Audit logs pod ser rastreado de diferentes modos sendo que no CKS será usado JSON para não utilizar ferramenta de terceiros. É possível definir rotação, tamanho máximo, etc.
Em um cluster produtivo, dependendo do quanto de auditoria é necessário, é interessante enviar esses logs para ferramentas que conseguem trabalhar com grande quantidades de dados.
Sabendo que podemos utilizar os stages e levels, em uma policy podemos aplicar da uma desses conceitos em difierentes objetos, grupos e até verbos no kubernetes.
Ativando Audito Logs
Precisamos passar o parâmetro para ativar a auditoria no kube-apiserver, o parâmetro apontando onde e como ele deve guardar os logs que serão gerados e onde estão as políticas que ele deve respeitar. Vamos criar uma policy simples que irá guardar tudo em level metadata, ou seja, sem guardar nenhum corpo da mensagem, somente os cabeçalhos.
root@cks-master:~# cd /etc/kubernetes/
root@cks-master:/etc/kubernetes# mkdir audit
root@cks-master:/etc/kubernetes# cd audit/
root@cks-master:/etc/kubernetes# vim policy.yaml
root@cks-master:/etc/kubernetes# cat policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
# Ver abaixo o que deve ser editado
root@cks-master:/etc/kubernetes# vim /etc/kubernetes/manifests/kube-apiserver.yaml
Para ativar no kube-apiserver vamos adicionar os seguinte campos.
--audit-log-path
: especifica o caminho do arquivo de log que o backend de log usa para gravar eventos de auditoria. Não especificar este sinalizador desabilita o backend de log.- `--audit-log-maxage: Número máximo de dias para manter arquivos de log de auditoria antigos.
--audit-log-maxbackup
: Número máximo de arquivos de log de auditoria a serem retidos.--audit-log-maxsize
: Tamanho máximo em megabytes do arquivo de log de auditoria antes de ser rotacionado.
spec:
containers:
- command:
- kube-apiserver
...
- --audit-policy-file=/etc/kubernetes/audit/policy.yaml # add
- --audit-log-path=/etc/kubernetes/audit/logs/audit.log # add
- --audit-log-maxsize=500 # add
- --audit-log-maxbackup=5 # add
...
# Lembre de montar os volumes
volumeMounts:
...
- mountPath: /etc/kubernetes/audit # add
name: audit # add
...
...
volumes:
- hostPath: # add
path: /etc/kubernetes/audit # add
type: DirectoryOrCreate # add
name: audit # add
Agora vamos conferir
root@cks-master:/etc/kubernetes/audit# k get pods
# Claro vai gerar muito log, então vamos remover e pegar só o que interessa que é a última linha se for rápido.
root@cks-master:/etc/kubernetes/audit# cat logs/audit.log
...
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"Metadata","auditID":"b09a0a12-a858-4d62-9851-8ff972a4d90c","stage":"RequestReceived","requestURI":"/readyz","verb":"get","user":{"username":"system:anonymous","groups":["system:unauthenticated"]},"sourceIPs":["10.128.0.6"],"userAgent":"kube-probe/1.30","requestReceivedTimestamp":"2024-09-11T14:16:32.059780Z","stageTimestamp":"2024-09-11T14:16:32.059780Z"}
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"Metadata","auditID":"b09a0a12-a858-4d62-9851-8ff972a4d90c","stage":"ResponseComplete","requestURI":"/readyz","verb":"get","user":{"username":"system:anonymous","groups":["system:unauthenticated"]},"sourceIPs":["10.128.0.6"],"userAgent":"kube-probe/1.30","responseStatus":{"metadata":{},"code":200},"requestReceivedTimestamp":"2024-09-11T14:16:32.059780Z","stageTimestamp":"2024-09-11T14:16:32.062798Z","annotations":{"authorization.k8s.io/decision":"allow","authorization.k8s.io/reason":"RBAC: allowed by ClusterRoleBinding \"system:public-info-viewer\" of ClusterRole \"system:public-info-viewer\" to Group \"system:unauthenticated\""}}
...
# Vamos criar uma secret e conferir o que temos
root@cks-master:/etc/kubernetes/audit# k create secret generic verysecure --from-literal=user=admin
secret/verysecure created
root@cks-master:/etc/kubernetes/audit# cat /etc/kubernetes/audit/logs/audit.log | grep verysecure
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"Metadata","auditID":"3fca4b1a-4adc-4719-9246-07204a61f6e6","stage":"ResponseComplete","requestURI":"/api/v1/namespaces/default/secrets?fieldManager=kubectl-create\u0026fieldValidation=Strict","verb":"create","user":{"username":"kubernetes-admin","groups":["kubeadm:cluster-admins","system:authenticated"]},"sourceIPs":["10.128.0.6"],"userAgent":"kubectl/v1.30.3 (linux/amd64) kubernetes/6fc0a69","objectRef":{"resource":"secrets","namespace":"default","name":"verysecure","apiVersion":"v1"},"responseStatus":{"metadata":{},"code":201},"requestReceivedTimestamp":"2024-09-11T14:22:39.543130Z","stageTimestamp":"2024-09-11T14:22:39.557212Z","annotations":{"authorization.k8s.io/decision":"allow","authorization.k8s.io/reason":"RBAC: allowed by ClusterRoleBinding \"kubeadm:cluster-admins\" of ClusterRole \"cluster-admin\" to Group \"kubeadm:cluster-admins\""}}
{
"kind":"Event", //tipo
"apiVersion":"audit.k8s.io/v1",
"level":"Metadata", //Level
"auditID":"3fca4b1a-4adc-4719-9246-07204a61f6e6",
"stage":"ResponseComplete",
"requestURI":"/api/v1/namespaces/default/secrets?fieldManager=kubectl-create\u0026fieldValidation=Strict",
"verb":"create", // O verbo foi create
"user":{
"username":"kubernetes-admin", //Qual user que fez
"groups":[
"kubeadm:cluster-admins",
"system:authenticated"
]
},
"sourceIPs":[
"10.128.0.6"
],
"userAgent":"kubectl/v1.30.3 (linux/amd64) kubernetes/6fc0a69",
"objectRef":{
"resource":"secrets", // Qual o objeto se refere
"namespace":"default", // Namespace do recurso
"name":"verysecure", // Nome
"apiVersion":"v1"
},
"responseStatus":{
"metadata":{
},
"code":201
},
"requestReceivedTimestamp":"2024-09-11T14:22:39.543130Z", // Horário que recebeu
"stageTimestamp":"2024-09-11T14:22:39.557212Z",
"annotations":{
"authorization.k8s.io/decision":"allow",
"authorization.k8s.io/reason":"RBAC: allowed by ClusterRoleBinding \"kubeadm:cluster-admins\" of ClusterRole \"cluster-admin\" to Group \"kubeadm:cluster-admins\""
}
}
Editando essa secret e adicionando o campo password temos.
root@cks-master:/etc/kubernetes/audit# k edit secrets verysecure
secret/verysecure edited
root@cks-master:/etc/kubernetes/audit# cat /etc/kubernetes/audit/logs/audit.log | grep verysecure
# Esse foi do primeiro, na criação
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"Metadata","auditID":"3fca4b1a-4adc-4719-9246-07204a61f6e6","stage":"ResponseComplete","requestURI":"/api/v1/namespaces/default/secrets?fieldManager=kubectl-create\u0026fieldValidation=Strict","verb":"create","user":{"username":"kubernetes-admin","groups":["kubeadm:cluster-admins","system:authenticated"]},"sourceIPs":["10.128.0.6"],"userAgent":"kubectl/v1.30.3 (linux/amd64) kubernetes/6fc0a69","objectRef":{"resource":"secrets","namespace":"default","name":"verysecure","apiVersion":"v1"},"responseStatus":{"metadata":{},"code":201},"requestReceivedTimestamp":"2024-09-11T14:22:39.543130Z","stageTimestamp":"2024-09-11T14:22:39.557212Z","annotations":{"authorization.k8s.io/decision":"allow","authorization.k8s.io/reason":"RBAC: allowed by ClusterRoleBinding \"kubeadm:cluster-admins\" of ClusterRole \"cluster-admin\" to Group \"kubeadm:cluster-admins\""}}
# Na hora de editar gerou o evento requestReceived com o get entregando o yaml. Observe que temos o resquet e depois o response
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"Metadata","auditID":"a3c1a228-f287-4742-9c56-d316b9f0bf3b","stage":"RequestReceived","requestURI":"/api/v1/namespaces/default/secrets/verysecure","verb":"get","user":{"username":"kubernetes-admin","groups":["kubeadm:cluster-admins","system:authenticated"]},"sourceIPs":["10.128.0.6"],"userAgent":"kubectl/v1.30.3 (linux/amd64) kubernetes/6fc0a69","objectRef":{"resource":"secrets","namespace":"default","name":"verysecure","apiVersion":"v1"},"requestReceivedTimestamp":"2024-09-11T15:38:00.424748Z","stageTimestamp":"2024-09-11T15:38:00.424748Z"}
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"Metadata","auditID":"a3c1a228-f287-4742-9c56-d316b9f0bf3b","stage":"ResponseComplete","requestURI":"/api/v1/namespaces/default/secrets/verysecure","verb":"get","user":{"username":"kubernetes-admin","groups":["kubeadm:cluster-admins","system:authenticated"]},"sourceIPs":["10.128.0.6"],"userAgent":"kubectl/v1.30.3 (linux/amd64) kubernetes/6fc0a69","objectRef":{"resource":"secrets","namespace":"default","name":"verysecure","apiVersion":"v1"},"responseStatus":{"metadata":{},"code":200},"requestReceivedTimestamp":"2024-09-11T15:38:00.424748Z","stageTimestamp":"2024-09-11T15:38:00.427022Z","annotations":{"authorization.k8s.io/decision":"allow","authorization.k8s.io/reason":"RBAC: allowed by ClusterRoleBinding \"kubeadm:cluster-admins\" of ClusterRole \"cluster-admin\" to Group \"kubeadm:cluster-admins\""}}
# Request do Edit
# Veja o verbo utilizado patch que é utilizado para aplicar alterações.
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"Metadata","auditID":"dc88feab-4cbf-4643-b1d7-4dc90fe2b59f","stage":"RequestReceived","requestURI":"/api/v1/namespaces/default/secrets/verysecure?fieldManager=kubectl-edit\u0026fieldValidation=Strict","verb":"patch","user":{"username":"kubernetes-admin","groups":["kubeadm:cluster-admins","system:authenticated"]},"sourceIPs":["10.128.0.6"],"userAgent":"kubectl/v1.30.3 (linux/amd64) kubernetes/6fc0a69","objectRef":{"resource":"secrets","namespace":"default","name":"verysecure","apiVersion":"v1"},"requestReceivedTimestamp":"2024-09-11T15:38:23.658311Z","stageTimestamp":"2024-09-11T15:38:23.658311Z"}
# Response do #Edit
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"Metadata","auditID":"dc88feab-4cbf-4643-b1d7-4dc90fe2b59f","stage":"ResponseComplete","requestURI":"/api/v1/namespaces/default/secrets/verysecure?fieldManager=kubectl-edit\u0026fieldValidation=Strict","verb":"patch","user":{"username":"kubernetes-admin","groups":["kubeadm:cluster-admins","system:authenticated"]},"sourceIPs":["10.128.0.6"],"userAgent":"kubectl/v1.30.3 (linux/amd64) kubernetes/6fc0a69","objectRef":{"resource":"secrets","namespace":"default","name":"verysecure","apiVersion":"v1"},"responseStatus":{"metadata":{},"code":200},"requestReceivedTimestamp":"2024-09-11T15:38:23.658311Z","stageTimestamp":"2024-09-11T15:38:23.663667Z","annotations":{"authorization.k8s.io/decision":"allow","authorization.k8s.io/reason":"RBAC: allowed by ClusterRoleBinding \"kubeadm:cluster-admins\" of ClusterRole \"cluster-admin\" to Group \"kubeadm:cluster-admins\""}}
Criando um Audit Policy
Para evitar a poluição que temos acima, podemos melhorar a nossa policy.
Por exemplo, vamos editar o nosso policy.yaml simples para o seguinte cenário.
- Não vamos logar nada de RequestReceived para nenhum recurso.
- Não vamos logar nada de get, watch e list para nenhum recurso pois isso é somente consulta.
- Secrets podemos manter somente o metadata para não pegar o corpo da mensagem.
- Todo o resto resto vamos logar somente o Response que será de fato com foi terminado a tarefa.
Editar o arquivo não gera mudança no arquivo de manifesto do kube-apiserver, ou seja, não surtirá efeito pois não foi alterado seu arquivo de manifesto. É necessário reiniciar o kube-apiserver forçadamente.
- Desativar e reativar o audit log é uma opção.
- Matar o container com o crictl.
- Remover o kube-apiserve do /etc/kubernetes/manifests e depois colocar de volta (Gosto dessa opção).
A ORDEM IMPORTA.
apiVersion: audit.k8s.io/v1
kind: Policy
# Isso poderia ser aplicado a uma única regra. Nesse caso estamos aplicando em todas.
omitStages:
- "RequestReceived"
rules:
# como não especificamos o stage, será qualquer stage menos o RequestReceived que declaramos acima.
- level: None
verbs: ["watch","list","get"]
# Como a ordem importa, watch list e get acima serão aplicado aqui
- level: Metadata
resources:
- group: ""
resources: ["secrets"]
# Todo o resto. Inclusive será aplicado no stage RequestComplete e RequestStarted
- level: RequestResponse
# Removendo o slogs antigos para renovar nosso arquivo de saída.
root@cks-master:/etc/kubernetes/audit# rm -rf logs/
root@cks-master:/etc/kubernetes/audit# vim policy.yaml
root@cks-master:/etc/kubernetes/audit# cat policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- "RequestReceived"
rules:
- level: None
verbs: ["watch", "list", "get"]
- level: Metadata
resources:
- group: ""
resources: ["secrets"]
- level: RequestResponse
root@cks-master:/etc/kubernetes/audit# cd ../manifests/
root@cks-master:/etc/kubernetes/manifests# mv kube-apiserver.yaml ..
root@cks-master:/etc/kubernetes/manifests# mv ../kube-apiserver.yaml .
root@cks-master:/etc/kubernetes/manifests#
#Espera subir o kube-apiserver
root@cks-master:/etc/kubernetes/manifests# k get pods
NAME READY STATUS RESTARTS AGE
pod 1/1 Running 0 43h
root@cks-master:/etc/kubernetes/manifests# cat /etc/kubernetes/audit/logs/audit.log | grep RequestReceived
# Nada na saída
root@cks-master:/etc/kubernetes/manifests# k delete secrets verysecure
secret "verysecure" deleted
# Só o ResponseComplete
root@cks-master:/etc/kubernetes/manifests# cat /etc/kubernetes/audit/logs/audit.log | grep verysecure
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"Metadata","auditID":"79e90895-fe8d-4536-9124-0f0ef08ab69b","stage":"ResponseComplete","requestURI":"/api/v1/namespaces/default/secrets/verysecure","verb":"delete","user":{"username":"kubernetes-admin","groups":["kubeadm:cluster-admins","system:authenticated"]},"sourceIPs":["10.128.0.6"],"userAgent":"kubectl/v1.30.3 (linux/amd64) kubernetes/6fc0a69","objectRef":{"resource":"secrets","namespace":"default","name":"verysecure","apiVersion":"v1"},"responseStatus":{"metadata":{},"status":"Success","details":{"name":"verysecure","kind":"secrets","uid":"0848605a-b6b0-4e8d-aced-35a41e26144c"},"code":200},"requestReceivedTimestamp":"2024-09-11T17:21:22.845033Z","stageTimestamp":"2024-09-11T17:21:22.851421Z","annotations":{"authorization.k8s.io/decision":"allow","authorization.k8s.io/reason":"RBAC: allowed by ClusterRoleBinding \"kubeadm:cluster-admins\" of ClusterRole \"cluster-admin\" to Group \"kubeadm:cluster-admins\""}}
Mais modelos para estudo.
apiVersion: audit.k8s.io/v1 # This is required.
kind: Policy
# Don't generate audit events for all requests in RequestReceived stage.
omitStages:
- "RequestReceived"
rules:
# Log pod changes at RequestResponse level
- level: RequestResponse
resources:
- group: ""
# Resource "pods" doesn't match requests to any subresource of pods,
# which is consistent with the RBAC policy.
resources: ["pods"]
# Log "pods/log", "pods/status" at Metadata level
- level: Metadata
resources:
- group: ""
resources: ["pods/log", "pods/status"]
# Don't log requests to a configmap called "controller-leader"
- level: None
resources:
- group: ""
resources: ["configmaps"]
resourceNames: ["controller-leader"]
# Don't log watch requests by the "system:kube-proxy" on endpoints or services
- level: None
users: ["system:kube-proxy"]
verbs: ["watch"]
resources:
- group: "" # core API group
resources: ["endpoints", "services"]
# Don't log authenticated requests to certain non-resource URL paths.
- level: None
userGroups: ["system:authenticated"]
nonResourceURLs:
- "/api*" # Wildcard matching.
- "/version"
# Log the request body of configmap changes in kube-system.
- level: Request
resources:
- group: "" # core API group
resources: ["configmaps"]
# This rule only applies to resources in the "kube-system" namespace.
# The empty string "" can be used to select non-namespaced resources.
namespaces: ["kube-system"]
# Log configmap and secret changes in all other namespaces at the Metadata level.
- level: Metadata
resources:
- group: "" # core API group
resources: ["secrets", "configmaps"]
# Log all other resources in core and extensions at the Request level.
- level: Request
resources:
- group: "" # core API group
- group: "extensions" # Version of group should NOT be included.
# A catch-all rule to log all other requests at the Metadata level.
- level: Metadata
# Long-running requests like watches that fall under this rule will not
# generate an audit event in RequestReceived.
omitStages:
- "RequestReceived"