Pregunta 18 | Investigar Intrusión vía Audit Log
Usar contexto: kubectl config use-context infra-prod
El Namespace security contiene cinco Secrets de tipo Opaque que pueden considerarse altamente confidenciales. La última Investigación-Prevención-Incidentes reveló que la ServiceAccount p.auster tuvo acceso demasiado amplio al clúster durante algún tiempo. Esta SA nunca debió haber tenido acceso a ningún Secret en ese Namespace.
Descubre a qué Secrets en el Namespace security esta SA accedió mirando los Registros de Auditoría en /opt/course/18/audit.log.
Cambia la contraseña a cualquier nueva cadena de solo aquellos Secrets a los que se accedió por esta SA.
NOTA: Puedes usar jq para renderizar json más legible. cat data.json | jq
Respuesta:
Primero miramos los Secrets de los que se trata:
➜ k -n security get secret | grep Opaque
kubeadmin-token Opaque 1 37m
mysql-admin Opaque 1 37m
postgres001 Opaque 1 37m
postgres002 Opaque 1 37m
vault-token Opaque 1 37m
# A continuación investigamos el archivo de Registro de Auditoría:
➜ cd /opt/course/18
➜ :/opt/course/18$ ls -lh
total 7.1M
-rw-r--r-- 1 k8s k8s 7.5M Sep 24 21:31 audit.log
➜ :/opt/course/18$ cat audit.log | wc -l
4451
Los Registros de Auditoría pueden ser enormes y es común limitar la cantidad creando una Política de Auditoría y transferir los datos a sistemas como Elasticsearch. En este caso tenemos una exportación JSON simple, pero ya contiene 4451 líneas.
Deberíamos intentar filtrar el archivo a información relevante:
➜ :/opt/course/18$ cat audit.log | grep "p.auster" | wc -l
28
# No está mal, solo 28 registros para la ServiceAccount p.auster.
➜ :/opt/course/18$ cat audit.log | grep "p.auster" | grep Secret | wc -l
2
# Y solo 2 registros relacionados con Secrets...
➜ :/opt/course/18$ cat audit.log | grep "p.auster" | grep Secret | grep list | wc -l
0
➜ :/opt/course/18$ cat audit.log | grep "p.auster" | grep Secret | grep get | wc -l
2
# No hay acciones list, lo cual es bueno, pero 2 acciones get, así que las verificamos:
cat audit.log | grep "p.auster" | grep Secret | grep get | jq
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"level": "RequestResponse",
"auditID": "74fd9e03-abea-4df1-b3d0-9cfeff9ad97a",
"stage": "ResponseComplete",
"requestURI": "/api/v1/namespaces/security/secrets/vault-token",
"verb": "get",
"user": {
"username": "system:serviceaccount:security:p.auster",
"uid": "29ecb107-c0e8-4f2d-816a-b16f4391999c",
"groups": [
"system:serviceaccounts",
"system:serviceaccounts:security",
"system:authenticated"
]
},
...
"userAgent": "curl/7.64.0",
"objectRef": {
"resource": "secrets",
"namespace": "security",
"name": "vault-token",
"apiVersion": "v1"
},
...
}
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"level": "RequestResponse",
"auditID": "aed6caf9-5af0-4872-8f09-ad55974bb5e0",
"stage": "ResponseComplete",
"requestURI": "/api/v1/namespaces/security/secrets/mysql-admin",
"verb": "get",
"user": {
"username": "system:serviceaccount:security:p.auster",
"uid": "29ecb107-c0e8-4f2d-816a-b16f4391999c",
"groups": [
"system:serviceaccounts",
"system:serviceaccounts:security",
"system:authenticated"
]
},
...
"userAgent": "curl/7.64.0",
"objectRef": {
"resource": "secrets",
"namespace": "security",
"name": "mysql-admin",
"apiVersion": "v1"
},
...
}
Allí vemos que los Secrets vault-token y mysql-admin fueron accedidos por p.auster. Por lo tanto cambiamos las contraseñas de esos.
➜ echo new-vault-pass | base64
bmV3LXZhdWx0LXBhc3MK
➜ k -n security edit secret vault-token
➜ echo new-mysql-pass | base64
bmV3LW15c3FsLXBhc3MK
➜ k -n security edit secret mysql-admin
Audit Logs ftw.
Ejecutando cat audit.log | grep "p.auster" | grep Secret | grep password podemos ver que las contraseñas están almacenadas en los Registros de Auditoría, porque almacenan el contenido completo de los Secrets. Nunca es una buena idea revelar contraseñas en registros. En este caso probablemente sería suficiente almacenar solo información de nivel Metadata de los Secrets lo cual puede controlarse mediante una Política de Auditoría.