Capítulo 4. Política como código y Kubernetes
Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com
En 2018, me hice cargo de dirigir un equipo que estaba creando herramientas para el aprovisionamiento de Kubernetes, similar a Kubernetes por las malas. Estábamos creando herramientas, principalmente mediante secuencias de comandos de shell Bash, para aprovisionar y gestionar clústeres. Yo llevaba casi dos años trabajando con Kubernetes a tiempo parcial. Habíamos considerado herramientas como kOps para ayudar a construir y actualizar clusters, pero en aquel momento kOps no satisfacía nuestras necesidades de computación en nube.
Consejo
Incluso con los servicios automatizados, las plataformas, las API y las herramientas disponibles hoy en día para ayudar a la gente a construir y gestionar Kubernetes, te recomiendo que investigues y pruebes a ejecutar Kubernetes por las malas. Esa experiencia tuvo un valor incalculable para mi curva de aprendizaje.
Una de mis primeras tareas fue implantar controles dentro de Kubernetes para evitar cambios no deseados en los clústeres. Celebramos un hackathon interno para ayudar a generar ideas sobre los controles de Kubernetes. Fue entonces cuando utilicé por primera vez PaC para Kubernetes y descubrí cómo podía integrar políticas en la API de Kubernetes.
En este capítulo, exploraremos cómo se utiliza PaC con Kubernetes para crear controles que impidan cambios no deseados, para erigir barandillas que guíen las buenas prácticas y para realizar autorizaciones. Presentaré conceptos sobre PaC y la integración con Kubernetes, utilizando ejemplos cuando sea necesario. En capítulos posteriores, profundizaremos en cada solución PaC.
Empezaré repasando brevemente la CNCF y presentando la organización de la comunidad Kubernetes; luego te pediré que te pongas manos a la obra conmigo mientras hacemos un rápido recorrido por los casos de uso y las soluciones que PaC puede ofrecer a Kubernetes. Prepararemos el terreno para profundizar en cada solución PaC más adelante en este libro.
CNCF y Gestión de Políticas
En el Capítulo 1, presenté la Fundación de Computación Nativa en la Nube (CNCF) y su estructura de proyectos. Aquí quiero explicar brevemente cómo encaja Kubernetes. Kubernetes fue aceptado en la CNCF en 2016 y ahora es un proyecto graduado. Sin embargo, la CNCF sigue muy implicada en la dirección de Kubernetes y en la comunidad del proyecto Kubernetes.
Dentro de la comunidad del proyecto Kubernetes, la gestión de las actividades se organiza en grupos de interés especial (SIG). Estos SIG abarcan un amplio espectro de esfuerzos destinados a mejorar Kubernetes. Cada SIG es una comunidad de práctica centrada en esfuerzos específicos y relacionados del proyecto Kubernetes. En particular, el Grupo de Interés Especial Auth, también conocido como sig-auth, se centra en los esfuerzos en curso relacionados con la autenticación de Kubernetes (AuthN), la autorización (AuthZ) y la política de seguridad.
Cuando se necesita un enfoque más estrecho para un esfuerzo temporal, se forman grupos de trabajo (GT). Los SIG patrocinan los GT y también son partes interesadas en los esfuerzos de los GT.
El Grupo de Trabajo sobre Políticas se centra en la arquitectura de gestión de políticas y en propuestas de políticas para Kubernetes. Sig-auth patrocina el GT de Políticas y figura en la lista de partes interesadas. En el Capítulo 3, se te presentó el Libro Blanco de la Gestión de Políticas de Kubernetes, que está almacenado en el proyecto sig-security.
Nota
Para más información sobre el ciclo de vida de los SIGs, WGs y grupos de usuarios (UGs) de Kubernetes, consulta esta documentación de la comunidad Kubernetes.
Con esta sinopsis de la organización de Kubernetes SIG y WG a nuestras espaldas, veamos cómo podemos mejorar y controlar las operaciones de Kubernetes con PaC.
Implantar controles de seguridad y controlar comportamientos
El rico conjunto de funciones que ofrece Kubernetes reduce o directamente elimina las tareas comunes y el trabajo pesado que supone ejecutar contenedores a escala. Entre esas funciones están las configuraciones de seguridad, junto con los ajustes que pueden hacerse para seguir las buenas prácticas. Al igual que la seguridad no es simplemente algo que se añade al final de un proyecto de software, la planificación de un clúster de Kubernetes -o una colección de ellos- requiere diseños y decisiones de seguridad al principio -el día 0, si quieres- de tu viaje por Kubernetes.
Incluso con todas las palancas de las que puedes tirar para asegurar tus aplicaciones Kubernetes y reforzar la solidez de su ejecución, Kubernetes no te obliga a seguir las buenas prácticas ni las configuraciones de seguridad recomendadas. Ese vacío puede llenarse con PaC. Y para entender cómo PaC mejora Kubernetes y ayuda a controlar el comportamiento dentro de los clústeres, primero debemos examinar cómo se realizan los cambios dentro de un clúster Kubernetes en ejecución a través de peticiones al servidor API.
Solicitudes al servidor API
Los componentes de Kubernetes se dividen en los que se ejecutan en el plano de control (CP) y los que se ejecutan en los nodos del plano de datos (DP). Los componentes del CP gestionan el clúster. Sin profundizar demasiado en la arquitectura de Kubernetes, la Figura 4-1 muestra la relación entre los componentes del CP y los del DP (nodos).
El componente clave que une las cosas es el servidor API de Kubernetes. El servidor API gestiona las solicitudes de clientes externos, como kubectl, y de clientes internos, como los agentes y controladores de nodos kubelet.
Nota
Kubectl es la CLI utilizada para gestionar e interactuar con los clusters de Kubernetes.
Como puedes ver en la Figura 4-1, la comunicación entre los agentes kubelet del nodo y el CP pasa por el servidor API. Ningún agente kubelet habla directamente con los componentes del CP. Además, ningún componente -salvo el servidor API- habla directamente con etcd, el almacén de valores clave de Kubernetes que aloja las configuraciones del clúster. La idea que subyace a esta arquitectura es que los cambios que llegan a través del servidor API se guardan en etcd. Luego, esos cambios se envían a los componentes del CP para que realicen cambios en el clúster.
Nota
Consulta la documentación general de los componentes de K ubernetes para obtener una visión más detallada de la arquitectura de Kubernetes.
Las solicitudes del servidor API de Kubernetes pueden mutarse o validarse mediante controladores de admisión que se compilan en el código del servidor API o se validan mediante servicios AuthZ. A continuación, exploraremos los controladores de admisión, cómo se habilitan y sus tipos.
Controladores de admisión
En Kubernetes, un controlador de admisión es un código que se ejecuta después de que las solicitudes al servidor API se autentiquen y autoricen, pero antes de que la solicitud provoque un cambio en etcd. Los controladores de admisión se compilan en Kubernetes y están destinados a interceptar las solicitudes entrantes. Estos controladores pueden ser de tipo mutante, validador, o incluso ambos, y añaden controles integrados para la seguridad y el comportamiento del clúster. Como se muestra en las siguientes entradas del registro del servidor API de minikube, en minikube 1.27.1 se cargan por defecto 12 controladores de admisión mutantes y 11 controladores de admisión validadores:
# Mutating Admission Controllers Loaded 12 mutating admission controller(s) successfully in the following order: NamespaceLifecycle,LimitRanger,ServiceAccount,NodeRestriction, TaintNodesByCondition,Priority,DefaultTolerationSeconds, DefaultStorageClass,StorageObjectInUseProtection,RuntimeClass, DefaultIngressClass,MutatingAdmissionWebhook.
# Validating Admission Controllers Loaded 11 validating admission controller(s) successfully in the following order: LimitRanger,ServiceAccount,PodSecurity,Priority, PersistentVolumeClaimResize,RuntimeClass,CertificateApproval, CertificateSigning,CertificateSubjectRestriction, ValidatingAdmissionWebhook,ResourceQuota.
Nota
Los controladores de admisión no responden a las operaciones de lectura de Kubernetes, como get
, watch
y list
. Para evitar estas operaciones, tienes que utilizar AuthZ como RBAC.
Como puedes ver en las listas de controladores de admisión anteriores, LimitRanger y ServiceAccount son controladores de admisión mutantes y validadores. También puedes observar que aparecen en la lista dos controladores de admisión "webhook" :
-
MutatingAdmissionWebhook
-
ValidatingAdmissionWebhook
Estos dos controladores de admisión "webhook" se utilizan en realidad para llamar a los controladores de admisión dinámicos configurados, como los servicios del motor de políticas antes mencionados. En la siguiente sección, profundizaremos en los controladores de admisión dinámicos y en cómo su utilidad en tiempo de ejecución mejora Kubernetes.
Controladores dinámicos de admisión
Prácticamente todos los cambios introducidos en un clúster Kubernetes entran a través del servidor API. Esto significa que los cambios progresan a través del mismo flujo de solicitud del servidor API -representado en la Figura 4-2-en su camino hacia la persistencia en etcd. Los controladores de admisión dinámicos permiten a los usuarios del clúster añadir controladores personalizados a este flujo de solicitudes del servidor API para cambiar cómo se permite que procedan las solicitudes sin tener que personalizar el servidor API. De este modo, los controladores dinámicos de admisión son una extensión del servidor API. Si el contenido de una solicitud no se persiste en etcd, ese cambio no se realiza en el clúster. De hecho, uno de los propósitos más importantes de los componentes de Kubernetes es mantener el estado del clúster basándose en lo que se almacena correctamente en etcd.
En el flujo de solicitudes que se muestra en la Figura 4-2, hay dos pasos en los que se puede utilizar el PaC para afectar a la solicitud entrante. El webhook de admisión de mutaciones llama a un servicio configurado de motor de políticas que se ejecuta en los nodos del clúster DP. La carga útil de la solicitud se envía a este servicio, y si una política mutante coincide con la solicitud, entonces el servicio muta la carga útil antes de continuar con la validación del esquema del objeto.
Tras la validación del esquema del objeto, hay un segundo paso en el flujo en el que un servicio DP puede afectar a la solicitud entrante. El webhook de admisión de validación llama a un servicio configurado para que valide la carga útil actual. En el caso de que el servicio DP esté conectado a un motor de políticas, la validación la realiza cualquier política que coincida. Si la validación devuelve verdadero, el cambio se persiste en etcd, y los procesos descendentes modifican el clúster. Sin embargo, si la validación resulta falsa (inválida), la solicitud se detiene y el servidor API devuelve inmediatamente el estado al cliente que la ha realizado. Un ejemplo de mensaje de fallo devuelto al utilizar la etiqueta de imagen de contenedor latest
puede verse en la siguiente salida de consola:
Error from server ("DEPLOYMENT_INVALID": "GOOD_REGISTRY/read-only-container: latest" container image "latest" tag/version is not allowed. Resource ID (ns/name/kind): "opa-test/test/Deployment"): error when creating "test.yaml": admission webhook <WEBHOOK_NAME>" denied the request: "DEPLOYMENT_INVALID": "GOOD_REGISTRY/read-only-container:latest" container image "latest" tag/version is not allowed. Resource ID (ns/name/kind): "opa-test/test/Deployment"
Ahora que tenemos una comprensión de alto nivel de por qué existen los controladores dinámicos de admisión, vamos a profundizar en su funcionamiento examinando cómo se comunican con el servidor API.
Carga útil de la solicitud al servidor API
El servidor API de Kubernetes envía un objeto API AdmissionReview a los servicios webhook configurados. Este objeto contiene la carga útil de la solicitud enviada al servidor API por el cliente que solicita el cambio en el clúster. A los servicios de webhook de modificación y validación se les envían las peticiones del servidor API mediante peticiones POST como Content-Type: "application/json"
. Estas peticiones POST envían el objeto API AdmissionReview para que sea emparejado y gestionado por los servicios webhook. Dependiendo de la solución PaC que se utilice, estos objetos AdmissionReview pueden capturarse de los registros del motor de políticas.
Una de las formas más sencillas de ver el aspecto de este objeto AdmissionReview es crear uno a partir de un archivo YAML fuente de recursos de Kubernetes. El proyecto kube-review de GitHub es una utilidad que se utiliza para modelar un objeto AdmissionReview a partir de un archivo YAML fuente. Esto también es muy útil cuando quieres crear prototipos o probar políticas de admisión.
El siguiente archivo Pod YAML se utilizará para crear un objeto AdmissionReview fuera de banda a partir de una solicitud del servidor API utilizando el comando kube-review create
:
# test-pod.yaml
apiVersion
:
v1
kind
:
Pod
metadata
:
name
:
test-pod
namespace
:
policy-test
spec
:
containers
:
-
name
:
test-pause
image
:
<IMAGE_URL>
imagePullPolicy
:
Always
securityContext
:
allowPrivilegeEscalation
:
false
runAsUser
:
1000
readOnlyRootFilesystem
:
true
Tras instalar la utilidad kube-review, se puede utilizar el siguiente comando para crear un objeto AdmissionReview que refleje lo que el servidor API de Kubernetes enviaría a los servicios webhook de admisión:
# Use kube-review to create AdmissionReview object $ kube-review create test-pod.yaml > kube-review.json
{
"kind"
:
"AdmissionReview"
,
"apiVersion"
:
"admission.k8s.io/v1"
,
"request"
:
{
"uid"
:
"d44c6009-1a75-428d-80ac-ba2ad13b985e"
,
"kind"
:
{
"group"
:
""
,
"version"
:
"v1"
,
"kind"
:
"Pod"
},
"resource"
:
{
"group"
:
""
,
"version"
:
"v1"
,
"resource"
:
"pods"
},
"requestKind"
:
{
"group"
:
""
,
"version"
:
"v1"
,
"kind"
:
"Pod"
},
"requestResource"
:
{
"group"
:
""
,
"version"
:
"v1"
,
"resource"
:
"pods"
},
"name"
:
"test-pod"
,
"namespace"
:
"test"
,
"operation"
:
"CREATE"
,
"userInfo"
:
{
"username"
:
"kube-review"
,
"uid"
:
"66befdaa-1097-4249-9279-4fe5ed2fa4f3"
},
"object"
:
{
"kind"
:
"Pod"
,
"apiVersion"
:
"v1"
,
"metadata"
:
{
"name"
:
"test-pod"
,
"namespace"
:
"test"
,
"creationTimestamp"
:
null
},
"spec"
:
{
"containers"
:
[
{
"name"
:
"test-pause"
,
"image"
:
"<IMAGE_URL>"
,
"resources"
:
{},
"imagePullPolicy"
:
"Always"
,
"securityContext"
:
{
"runAsUser"
:
1000
,
"readOnlyRootFilesystem"
:
true
,
"allowPrivilegeEscalation"
:
false
}
}
]
},
"status"
:
{}
},
"oldObject"
:
null
,
"dryRun"
:
true
,
"options"
:
{
"kind"
:
"CreateOptions"
,
"apiVersion"
:
"meta.k8s.io/v1"
}
}
}
El objeto JSON AdmissionReview precedente sería interpretado y quizás registrado por los servicios integrados en el servidor API y que se ejecutan en los nodos de la AD. A menos que sea detenida por el control de admisión, la solicitud dará lugar en última instancia a una actualización de etcd y a la creación de un Pod (test-pod) en el espacio de nombres policy-test
.
Respuesta de admisión
Los servicios webhook responden a la solicitud del servidor API con objetos AdmissionReview que contienen elementos de respuesta. Se trata de un contrato que deben cumplir. Las siguientes respuestas van de mínimas a avanzadas, con códigos de estado, mensajes, parches (mutantes) y advertencias:
// minimal response - allowed and uid
{
"apiVersion"
:
"admission.k8s.io/v1"
,
"kind"
:
"AdmissionReview"
,
"response"
:
{
"uid"
:
"<value from request.uid>"
,
"allowed"
:
true
}
}
// Response with error code and message
{
"apiVersion"
:
"admission.k8s.io/v1"
,
"kind"
:
"AdmissionReview"
,
"response"
:
{
"uid"
:
"<value from request.uid>"
,
"allowed"
:
false
,
"status"
:
{
"code"
:
403
,
"message"
:
"something wasn’t allowed"
}
}
}
// Mutating webhook response with base64 encoded patch
{
"apiVersion"
:
"admission.k8s.io/v1"
,
"kind"
:
"AdmissionReview"
,
"response"
:
{
"uid"
:
"<value from request.uid>"
,
"allowed"
:
true
,
"patchType"
:
"JSONPatch"
,
"patch"
:
"base64 encoded Patch"
}
}
// Response with warnings
{
"apiVersion"
:
"admission.k8s.io/v1"
,
"kind"
:
"AdmissionReview"
,
"response"
:
{
"uid"
:
"<value from request.uid>"
,
"allowed"
:
true
,
"warnings"
:
[
"<WARNING_1>"
,
"<WARNING_2>"
]
}
}
Cada respuesta debe devolver el elemento allowed
y el elemento uid
. El elemento uid
debe coincidir con el uid
de la solicitud POST original del servidor API.
Los motores PaC utilizarán las propiedades del objeto AdmissionReview para hacer coincidir las políticas con las solicitudes entrantes y mutar o validar las solicitudes. El método principal para integrar PaC en el servidor API de Kubernetes es a través de los controladores de admisión. A continuación exploraremos estos componentes.
Configurar controladores dinámicos de admisión
Los controladores de admisión dinámicos de Kubernetes son posibles cargando los controladores de admisión compilados MutatingAdmissionWebhook y ValidatingAdmissionWebhook cuando se inicia el servidor API. Con estos dos controladores de admisión en ejecución, podemos configurar extensiones del flujo de solicitudes del servidor de API en tiempo de ejecución utilizando servicios que se ejecutan en nodos DP. Esto significa que, una vez que el servidor API está en marcha y el clúster en funcionamiento, podemos añadir servicios del motor de políticas a la AD en tiempo de ejecución y configurarlos para que sean llamados por los webhooks del servidor API. Este enfoque reduce la necesidad de personalizar la configuración del servidor API de un clúster a otro.
Mutar la configuración del webhook
Los webhooks de admisión dinámica de se clasifican en mutantes y validadores, igual que los controladores de admisión compilados. Las configuraciones de los webhooks se utilizan para configurar cómo pueden utilizarse los servicios DP -instalados después de que se inicie el clúster y se configure el servidor API- para comunicarse con el servidor API y ampliarlo. Empecemos con un ejemplo de configuración de webhook mutante. Utilizando el siguiente comando kubectl
, podemos explorar un recurso de ejemplo mutatingwebhookconfiguration
:
# Get mutating webhook configuration $ kubectl get mutatingwebhookconfiguration <WEBHOOK_CONFIGURATION_NAME> \ -o yaml
apiVersion
:
admissionregistration.k8s.io/v1
kind
:
MutatingWebhookConfiguration
metadata
:
name
:
<WEBHOOK_CONFIGURATION_NAME>
webhooks
:
-
admissionReviewVersions
:
-
v1
clientConfig
:
caBundle
:
<x509_CERT>
url
:
https://127.0.0.1:23443/mutate
failurePolicy
:
Ignore
matchPolicy
:
Equivalent
namespaceSelector
:
{}
objectSelector
:
{}
reinvocationPolicy
:
IfNeeded
rules
:
-
apiGroups
:
-
""
apiVersions
:
-
v1
operations
:
-
CREATE
resources
:
-
pods
scope
:
'*'
sideEffects
:
None
timeoutSeconds
:
10
Veamos con más detalle algunos de los ajustes:
admissionReviewVersions
-
Conjunto de versiones admitidas para la integración del webhook. El servidor de la API enviará una solicitud con la primera versión de la lista y recorrerá la lista hasta encontrar una versión adecuada (si es que hay alguna).
clientConfig.caBundle
-
Un certificado x509 utilizado para autenticar el servidor de la API de Kubernetes (cliente) en el servicio webhook a través de TLS para una comunicación segura.
clientConfig.url
failurePolicy
-
La política de fallo del webhook determina qué ocurre cuando una llamada del servidor API al webhook no devuelve en el tiempo de espera configurado.
rules
-
Reglas sobre qué solicitudes de recursos de Kubernetes debe enviar el servidor API al servicio webhook.
Dada la configuración anterior, este webhook de mutación recibirá peticiones autenticadas del servidor API cuando se creen Pods. El servidor API esperará un máximo de 10 segundos a recibir una respuesta del servicio de mutación. El servidor API ignorará los fallos y procederá al paso en el flujo de solicitud, independientemente del éxito o fracaso.
Validar la configuración del webhook
Los webhooks de validación se configuran de forma similar a los webhooks de mutación. Exploremos un ejemplo con el siguiente comando kubectl
:
# Get validating admission webhook configuration $ kubectl get validatingwebhookconfiguration <WEBHOOK_CONFIGURATION_NAME> -oyaml
apiVersion
:
admissionregistration.k8s.io/v1
kind
:
ValidatingWebhookConfiguration
metadata
:
name
:
<WEBHOOK_CONFIGURATION_NAME>
webhooks
:
-
admissionReviewVersions
:
-
v1
clientConfig
:
caBundle
:
<x509_CERT>
Service
:
name
:
<WEBHOOK_SERVICE_NAME>
namespace
:
<WEBHOOK_SERVICE_NAMESPACE>
port
:
443
failurePolicy
:
Fail
matchPolicy
:
Equivalent
name
:
<WEBHOOK_NAME>
namespaceSelector
:
matchExpressions
:
-
key
:
<LABEL_NAME>
operator
:
NotIn
values
:
-
ignore
objectSelector
:
{
}
rules
:
-
apiGroups
:
-
'
*
'
apiVersions
:
-
'
*
'
operations
:
-
CREATE
-
UPDATE
resources
:
-
'
*
'
scope
:
'
*
'
sideEffects
:
None
timeoutSeconds
:
10
-
Dirección interna del servicio Kubernetes del servicio webhook de validación llamado por el servidor API
-
Regla de selección de espacios de nombres para incluir/excluir espacios de nombres del procesamiento con este webhook de validación
Dada la configuración anterior, este webhook de validación recibirá solicitudes autenticadas del servidor de la API cuando se cree o actualice cualquier recurso de Kubernetes con cualquier grupo o versión de la API. La excepción a esta regla es que no se enviará al servicio webhook validador ninguna solicitud de recursos que se creen o actualicen en Namespaces con la siguiente etiqueta:
# Label to ignore a namespace
metadata.label.<LABEL_NAME>=ignore
Consejo
Suele considerarse una buena práctica excluir los Espacios de nombres del sistema y los Espacios de nombres en los que se ejecutan los servicios de webhook. Esto se hace para que las operaciones del clúster no se vean comprometidas por una configuración de políticas demasiado restrictiva o incorrecta. Los controles compensatorios serían utilizar RBAC para restringir el acceso a estos Espacios de Nombres. Si la mutación dinámica o la validación de los controladores de admisión están causando problemas en el clúster, se pueden desactivar las configuraciones correspondientes para recuperar el control del clúster.
El servidor de la API esperará un máximo de 10 segundos a recibir una respuesta del servicio de validación. Si el servidor de la API no recibe una respuesta del servicio en el tiempo de espera configurado, la solicitud, válida o no, fallará.
Nota
Los controladores de admisión dinámicos de Kubernetes utilizan la integración webhook para llamar a los servicios de validación y mutación. Estas llamadas tienen ajustes de tiempo de espera (máximo 30 segundos, por defecto 10 segundos) para saber cuánto tiempo esperará la llamada antes de que se produzca un tiempo de espera. Las configuraciones del webhook también incluyen un ajuste failurePolicy
para configurar cómo debe responder el servidor de la API cuando la llamada del webhook no regresa dentro del periodo de tiempo de espera configurado. El webhook puede fallar en abierto (cuando se permite que la petición al servidor API continúe) o en cerrado (cuando se bloquea la petición al servidor API). Por defecto, falla en cerrado.
Hay compensaciones para cada escenario de fallo. Mientras que un escenario de fallo abierto podría verse como un problema potencial de seguridad, un escenario de fallo cerrado podría causar problemas operativos al clúster. Puedes encontrar más información sobre las políticas de fallos en la documentación de Kubernetes.
Datos más allá de AdmisiónRevisión
Con el objeto AdmissionReview, las validaciones de políticas son conscientes del contexto, estando éste dentro de los límites de la solicitud del servidor API que se está evaluando. Esto significa que la fuente de datos principal de las validaciones de políticas es el objeto AdmissionReview. Sin embargo, hay situaciones en las que los datos externos adicionales son útiles, si no necesarios. Algunos de esos casos de uso son
- Verificación de la firma de la imagen del contenedor
-
Los datos externos proporcionan la firma de la imagen del contenedor y la clave pública necesaria para verificar la imagen.
- Validación basada en clústeres
-
Cuando la validación de una petición del servidor API necesita tener en cuenta los recursos existentes en el clúster para tomar una decisión.
- Datos no de Kubernetes necesarios para la validación
-
En ocasiones, hay dependencias que deben modelarse para que se produzca una validación correcta.
Los datos externos pueden introducirse en las soluciones PaC de múltiples formas. Éstas son sólo algunas de las que trataremos más adelante en este libro:
-
Los datos externos se obtienen en el momento de la evaluación, según sea necesario.
-
Los datos externos del clúster los recoge un contenedor sidecar que escucha los cambios del clúster y los actualiza en el motor de políticas con una cadencia regular.
-
Los datos externos procedentes de fuera del clúster se introducen en el motor de políticas al iniciarse y cuando cambian los datos.
Veamos ahora la primera operación que puede realizarse en el flujo de peticiones del servidor API de Kubernetes: la mutación.
Recursos mutantes
Los controladores de admisión mutantes de Kubernetes pueden mutar las solicitudes entrantes del servidor -mediante parches in situ- antes de que se validen las solicitudes de y se utilicen para cambiar el clúster. Se trata de un patrón bien conocido en el desarrollo de aplicaciones, en el que los datos del lado del cliente se traducen, coaccionan o cambian mediante acciones del lado del servidor antes de que los datos se validen y guarden en el almacenamiento de datos del lado del servidor. Para ver cómo funciona esto, puedes probarlo utilizando kubectl. En primer lugar, aplicaremos los siguientes recursos Namespace y Pod:
apiVersion
:
v1
kind
:
Namespace
metadata
:
name
:
test
---
apiVersion
:
v1
kind
:
Pod
metadata
:
name
:
test-pod
namespace
:
test
spec
:
containers
:
-
name
:
test-pause
image
:
<IMAGE_URL>
imagePullPolicy
:
Always
securityContext
:
allowPrivilegeEscalation
:
false
runAsUser
:
1000
readOnlyRootFilesystem
:
true
A continuación, podemos añadir una etiqueta al Pod con el siguiente comando kubectl patch
:
# Add a label to a Pod via the kubectl patch command $ kubectl -n test patch pod test-pod --patch-file patch.yaml -o yaml
# patch.yaml
metadata
:
labels
:
owner
:
jimmy
El siguiente YAML contiene la nueva configuración Pod:
# Patched Pod
apiVersion
:
v1
kind
:
Pod
metadata
:
name
:
test-pod
namespace
:
test
labels
:
owner
:
jimmy
…
La mutación de peticiones del servidor de la API de Kubernetes se utiliza todo el tiempo, casi siempre de forma inadvertida. Algunos de los casos de uso que he utilizado y visto incluyen:
-
Inyecta contenedores sidecar en Implementaciones para instrumentación, como proxies de malla de servicios, observabilidad y soluciones PaC.
-
Añade etiquetas o anotaciones a los recursos
-
Cambiar la configuración de seguridad en Pods y contenedores
-
Añade tolerancias y configuraciones de afinidad de nodos a los Pods para soluciones de multitenencia
-
Cambiar los registros de imágenes de contenedores
También puedes revisar algunos de los controladores de admisión mutantes en la lista anterior.
Consejo
Después de la mutación, sigue siendo una buena práctica de confianza cero y DiD validar las solicitudes entrantes del servidor API de Kubernetes, asegurándose de que la configuración deseada está presente en la carga útil de la solicitud, incluso después de la mutación.
Existe una diferencia de opinión entre la comunidad de Kubernetes cuando se trata de utilizar la mutación de las peticiones entrantes al servidor API. Algunos creen que ayuda a reducir el número de errores de validación y facilita la vida a los usuarios de Kubernetes. Otros creen que introduce desviaciones en la solicitud del servidor API y que no debe utilizarse. Puedo ver ambos lados de este argumento como válidos, aunque tiendo a utilizar la mutación con poca frecuencia.
A continuación, vamos a explorar la validación de las peticiones entrantes al servidor API.
Validación de recursos
Validar las solicitudes del servidor API de Kubernetes evita cambios no deseados en tus clústeres. Como ya se ha mencionado, se trata de un enfoque válido para aplicar seguridad adicional y buenas prácticas.
Los casos de uso de la validación abarcan los controles de seguridad y las buenas prácticas. Algunos de los casos de uso de validación que he visto -y para los que he escrito políticas de PaC- son:
-
Aplicar las configuraciones de seguridad de Pods y contenedores
-
Aplicar la configuración de los recursos del contenedor
-
Restringir el origen de las imágenes de los contenedores
-
Evitar el uso de la etiqueta
latest
o la ausencia de etiquetas de versión en las imágenes contenedoras -
Aplica la configuración de multitenencia (afinidad de nodos, manchas, tolerancias, etc.)
-
Validar la entrada y proporcionar protección contra colisiones
-
Aplicar cuotas de espacio de nombres y limitar rangos
-
Hacer cumplir las clases de prioridad Pod
-
Hacer cumplir los presupuestos de interrupción de Pod
-
Evitar que se utilicen direcciones IP externas en los servicios ClusterIP
-
Impedir la creación o modificación de recursos en determinados espacios de nombres (también es un caso de uso de AuthZ)
Veamos en un ejemplo de política de validación utilizada en la solución jsPolicy PaC:
# Example jsPolicy validating policy
apiVersion
:
policy.jspolicy.com/v1beta1
kind
:
JsPolicy
metadata
:
name
:
"no-default-ns.jimmyray.io"
spec
:
operations
:
[
"CREATE"
,
"UPDATE"
]
resources
:
[
"*"
]
scope
:
Namespaced
javascript
:
|
if (request.namespace === "default") {
deny("Create and Update in the default namespace is not allowed!");
}
Como puedes ver, la política anterior se aplica a las operaciones CREATE
y UPDATE
en cualquier recurso del Espacio de Nombres predeterminado. Las propiedades del objeto AdmissionReview -Namespace
y operation
- son utilizadas por esta política para evaluar la solicitud entrante del servidor API de Kubernetes y evitar estos cambios no deseados, tal y como se definen en la política.
Consejo
Es una buena práctica no utilizar el Espacio de Nombres por defecto en un clúster de Kubernetes. Dado que el comportamiento por defecto de la CLI kubectl es seleccionar el Espacio de Nombres por defecto cuando no se especifica ningún Espacio de Nombres en el comando o en el archivo kubeconfig, el Espacio de Nombres por defecto se contamina fácilmente y puede verse comprometido. Utilizar una política de admisión validadora para impedir el uso del Espacio de Nombres por defecto reduce sustancialmente las posibilidades de uso erróneo de este Espacio de Nombres y de comportamiento no determinista.
En la siguiente sección, exploraremos brevemente la latencia de las solicitudes del servidor API.
Latencia de las solicitudes del servidor API y orden de los Webhooks
En el capítulo 6 de Site Reliability Engineering (O'Reilly), los autores Betsy Beyer, Chris Jones, Niall Richard Murphy y Jennifer Petoff introdujeron el concepto de monitoreo de las "Cuatro Señales Doradas" de : latencia, tráfico, errores y saturación. En esta sección, vamos a examinar brevemente la latencia y cómo afecta a las solicitudes del servidor API en el contexto del control de admisión.
En el caso del servidor API de Kubernetes, la latencia es el tiempo que tarda el servidor API en responder o servir una solicitud. Esa latencia se ve directamente afectada por el número de controladores de admisión y webhooks de controladores de admisión dinámicos que realizan el procesamiento de las solicitudes entrantes del servidor API. Para evitar problemas en el servidor API debidos a la latencia, debes tener en cuenta el número de controladores de admisión y webhooks de admisión, así como la configuración del tiempo de espera agregado de todos ellos.
El orden en que se ejecutan los controladores de admisión también es importante para controlar la latencia del servidor API. Por ejemplo, en el caso de los controladores de admisión dinámicos, el servidor API llama de forma diferente a los webhooks de mutación y de validación. Los webhooks mutantes se llaman en serie -en orden aleatorio- para evitar colisiones. Los webhooks de validación se llaman concurrentemente, en paralelo. Por tanto, en el caso de los webhooks mutantes, los ajustes de tiempo de espera configurados individualmente pueden aumentar el tiempo de espera agregado de la fase de mutación y contribuir a la latencia general de las solicitudes del servidor API.
La configuración predeterminada del tiempo de espera de para las solicitudes al servidor API de Kubernetes es de 60 segundos. Cambiar esta configuración puede dar lugar a que no haya tiempo suficiente para que las solicitudes del servidor API se ejecuten con éxito o a que se permita demasiado tiempo, lo que puede dar lugar a ataques de denegación de servicio. Dado que el tiempo máximo de espera para los controladores de admisión es de 30 segundos, el procesamiento de las solicitudes del servidor API por parte de varios controladores de admisión puede hacer que se agote el tiempo de espera de las solicitudes del servidor API, dando lugar a solicitudes fallidas.
Es una buena práctica ajustar la configuración del tiempo de espera de tu servidor API teniendo en cuenta los webhooks PaC que potencialmente se ejecutarán en tus clusters. Este ajuste puede cambiar a medida que tus clusters estén más ocupados. Parte del ajuste incluye fijar los tiempos de espera respectivos en la duración más baja posible sin introducir errores de tiempo de espera.
Hemos visto cómo se pueden mutar y validar las solicitudes del servidor API de Kubernetes y cómo afectan los controladores de admisión a la latencia del servidor API; sin embargo, ¿qué hacemos con los recursos que existían antes de que se añadieran nuevas políticas o cuando los motores PaC no detectan infracciones? Echemos un vistazo a la auditoría PaC y al escaneo en segundo plano.
Auditoría y exploración en segundo plano de los recursos existentes
Como ya se ha mencionado, los controladores de admisión dinámicos mutantes y validadores pueden configurarse para que fallen abiertos o cerrados. En los ejemplos anteriores de mutación y validación, la configuración del webhook de mutación se configuró como fail open, mientras que la configuración del webhook de validación se configuró como fail closed. Establecer la configuración del webhook en fallo abierto-failurePolicy: Ignore
-erra por precaución. Si por alguna razón el webhook no responde, los cambios seguirán adelante independientemente de que falte la mutación o la validación. Sin embargo, esta configuración también ofrece la mayor posibilidad de permitir que cambios no deseados actualicen el clúster. Se considera menos segura.
Si decides configurar los webhooks para que fallen -failurePolicy: Fail
- no se permitirá que progresen los cambios si los webhooks no responden con éxito. Aunque esto es más seguro, también puedes comprometer el funcionamiento de tu clúster impidiendo cualquier cambio. Debes recordar que muchos cambios en un clúster se originan dentro del mismo, en contraposición a las peticiones de clientes externos. Comprometer el funcionamiento de tu clúster de esta forma también se conoce como "brickear" tu clúster.
Para evitar lagunas en tus controles de seguridad y buenas prácticas, algunas soluciones PaC para Kubernetes de admiten auditoría (por ejemplo, Gatekeeper) o escaneado en segundo plano (por ejemplo, Kyverno). Con la auditoría o el escaneo en segundo plano, las soluciones PaC aumentan la naturaleza basada en eventos de las solicitudes al servidor API y registran o informan de las infracciones no detectadas. Esto ayuda a evitar cambios no deseados que se colaron cuando los servicios de webhook no respondieron en el periodo de tiempo asignado y la solicitud siguió adelante. Otro gran caso de uso para la auditoría y el escaneo en segundo plano es realizar pruebas y análisis de impacto al lanzar nuevas políticas a los clústeres, sin afectar negativamente a los recursos de clúster existentes con políticas de aplicación potencialmente perturbadoras. A medida que profundicemos en las soluciones PaC individuales de Kubernetes, exploraremos estas funciones y sus respectivas configuraciones y operaciones.
A continuación, vamos a ver cómo podemos reducir el trabajo pesado de Kubernetes y la gestión de políticas utilizando la automatización PaC para generar recursos y políticas.
Generar recursos y políticas
Lo mejor de la integración de PaC en las peticiones al servidor API de Kubernetes es que los controles que implementamos son principalmente preventivos. Podemos modificar e incluso evitar que se produzcan cambios no deseados en nuestros clústeres con PaC antes de que ocurran. Sin embargo, existen casos de uso que requieren que reaccionemos ante eventos en el clúster. Por ejemplo, el Kubernetes Cluster Autoscaler responde a eventos Pod no programables aprovisionando nodos de computación de clúster adicionales.
Las soluciones PaC también pueden utilizarse para responder a eventos del clúster. Por ejemplo, algunas soluciones PaC de Kubernetes ofrecen la posibilidad de generar recursos y políticas de Kubernetes sobre la marcha, en respuesta a la aplicación de otros recursos de Kubernetes. Con una solución PaC compatible, puedes reducir la sobrecarga de la gestión de políticas utilizando la generación de recursos y políticas. La generación de recursos y políticas aumenta la expresividad de las políticas y reduce el esfuerzo de mantener políticas para todos los recursos.
Por ejemplo, con Kyverno puedes escribir políticas para Pods y utilizar la función Auto-Gen de Kyverno para generar políticas para los correspondientes recursos de controlador que crean Pods, como los recursos de Implementación. Con la política Auto-Gen, puedes reducir la necesidad de gestionar políticas para múltiples tipos de recursos. Gatekeeper tiene una función similar mediante la expansión de políticas. Cubro Gatekeeper y Kyverno en los Capítulos 7 y 8, respectivamente.
Consejo
Kyverno Auto-Gen también funciona con la CLI de Kyverno. Con la CLI, puedes aplicar políticas a YAML -fuera del clúster de Kubernetes- antes de que se realicen las solicitudes al servidor API. Que conste que Kyverno Auto-Gen también funciona en la CLI.
Otra función de Kyverno, generar recursos, utiliza tipos de políticas generadas para crear políticas en respuesta a los recursos de Kubernetes aplicados. En un caso de uso multiarrendamiento en el que las aplicaciones se aíslan en sus respectivos Espacios de Nombres, las políticas de generación pueden utilizarse para preparar el Espacio de Nombres para su uso tras su creación y antes de que se implementen las aplicaciones.
Por ejemplo, utilizando una política de generación, puedes generar un recurso de política de red denegar todo para cada Namespace aprovisionado como parte del proceso de aprovisionamiento. Esto bloqueará el tráfico de red de entrada y salida para todos los Pods del Namespace. Cuando la aplicación se despliega finalmente en el espacio de nombres, se pueden utilizar políticas de red con las reglas de salida y entrada adecuadas para proporcionar el acceso a la red con menos privilegios necesario para la aplicación correspondiente. A continuación, se aplicarían las políticas de mutación o validación adecuadas para garantizar que en las políticas de red aplicadas no se configura un acceso a la red no autorizado.
Nota
jsPolicy tiene la función de política de controlador -similar a generar recursos- que reacciona a los eventos del clúster. En el Capítulo 9 hablo de jsPolicy.
Las soluciones PaC no siempre proceden del ecosistema Kubernetes. A continuación, examinaremos las soluciones PaC "nativas" de Kubernetes.
Características de las políticas nativas de Kubernetes
Hasta ahora, nos hemos centrado en las soluciones PaC que se añaden a los clústeres para decorarlos con funcionalidades adicionales para aplicar buenas prácticas y controles de seguridad. Sin embargo, Kubernetes incluye herramientas nativas que también pueden utilizarse para aplicar controles similares. Ahora exploraremos estas herramientas nativas.
Seguridad de la vaina
Los Pods son la unidad atómica de computación en Kubernetes; contienen contenedores. Al crear Pods, la seguridad se configura principalmente utilizando los elementos securityContext a nivel de Pod y de contenedor. El securityContext a nivel de Pod, que se encuentra en la especificación Pod, es menos granular que su homólogo a nivel de contenedor y es anulado por el securityContext a nivel de contenedor cuando los ajustes se solapan. Por lo demás, la configuración del securityContext a nivel de Pod la utilizan todos los contenedores del Pod y, a menudo, se combina con la configuración a nivel de contenedor.
En el siguiente fragmento de YAML se muestra un ejemplo de un securityContext a nivel de contenedor con ajustes que se consideran buenas prácticas de seguridad en contenedores:
# Container-level securityContext element - best practice settings
securityContext
:
allowPrivilegeEscalation
:
false
runAsUser
:
1000
readOnlyRootFilesystem
:
true
runAsNonRoot
:
true
capabilities
:
drop
:
[
"ALL"
]
seccompProfile
:
type
:
"RuntimeDefault"
Las configuraciones securityContext a nivel de contenedor anteriores están diseñadas para ejecutar un contenedor con el acceso con menos privilegios, al tiempo que evitan que el contenedor escale privilegios y realice llamadas no deseadas al núcleo del sistema operativo del nodo.
Nota
En el mundo Linux, los contenedores contienen aplicaciones -código, bibliotecas e intérpretes- que se ejecutan en el espacio de usuario. El espacio de usuario se refiere al área fuera del núcleo del sistema operativo. El espacio dentro del núcleo se denomina espacio del núcleo. Para que las aplicaciones funcionen correctamente en el espacio de usuario, necesitan recursos -PU, memoria, disco, red, etc.- obtenidos del espacio del núcleo. Los contenedores contienen espacio de usuario y hacen llamadas al sistema desde el espacio del núcleo. La seguridad de los contenedores se utiliza para limitar lo que un contenedor puede hacer en el espacio de usuario a sólo lo que necesita para funcionar. También sirve para limitar las llamadas al sistema que se pueden hacer desde un contenedor al núcleo.
En los primeros tiempos de Kubernetes, los recursos PodSecurityPolicy (PSP) se utilizaban para asegurar los Pods y evitar comportamientos no deseados de los contenedores que contenían. Las PSP aplicaban los ajustes de seguridad de los contenedores que se encontraban en los elementos securityContext de los Pods y contenedores. Sin embargo, las PSP eran notoriamente difíciles de configurar y utilizar, lo que daba lugar a muchas configuraciones erróneas que disminuían la seguridad deseada o incluso obstaculizaban las operaciones del clúster.
Muchos administradores de Kubernetes añadieron soluciones PaC a sus clústeres para mejorar la seguridad, imponer comportamientos deseados y evitar comportamientos no deseados. Esto significaba a menudo que las PSP predeterminadas se dejaban abiertas de par en par, en modo privilegiado. Los recursos PSP quedaron obsoletos en Kubernetes 1.21 y se eliminaron en Kubernetes 1.25.
Admisión de Seguridad Pod
Los PSP eran una función in-tree de Kubernetes, por lo que era muy conveniente sustituirlos por otra solución in-tree. El controlador de Admisión de Seguridad de Pods (PSA) es el sustituto de PSP en el árbol de Kubernetes . Este controlador de admisión pasó a ser beta en Kubernetes v1.23 y estable en v1.25. El PSA implementa las Normas de Seguridad de los Pods de Kubernetes (PSS), que definen 17 controles de seguridad para las configuraciones de los Pods, organizados en tres niveles:
-
Privilegiado (no seguro)
-
Línea de base (segura)
-
Restringido (muy seguro)
Nota
El término in-tree, cuando se utiliza con Kubernetes, se refiere a dónde reside la funcionalidad de Kubernetes. Cuando una función se incluye en una versión de Kubernetes y está disponible para su uso sin necesidad de instalar software adicional, ese componente se conoce como in-tree. Como referencia, los PaC eran una función in-tree. Las soluciones PaC están disponibles en el ecosistema Kubernetes y se instalan o integran con Kubernetes; las soluciones PaC se consideran fuera del árbol.
Para que quede claro, el apodo "en el árbol" no es necesariamente un indicador de lo fácil de configurar o de usar que es una función. De hecho, las PSP se eliminaron de Kubernetes en gran parte debido a su complejidad y difícil UX.
La PSA implementa el PSS utilizando tres modos de funcionamiento. Los modos, junto con los niveles de PSS, crean políticas de seguridad lógicas que se utilizan para controlar la seguridad de los Pods dentro de un clúster Kubernetes, aplicando la configuración de los elementos securityContext de los Pods y contenedores. Los modos de PSS son:
- Haz cumplir
-
Las violaciones de la política impedirán el aprovisionamiento de Pods.
- Warn
-
Las infracciones de la política harán que el servidor de la API de Kubernetes responda con advertencias.
- Auditoría
-
Las infracciones de la política provocarán anotaciones de auditoría en los eventos registrados en los registros de auditoría del servidor de la API de Kubernetes.
PSA es un controlador de admisión que se carga cuando se inicia el servidor API de Kubernetes. Como se ve en k8s-psa-pss-testing, unproyecto OSS que creé cuando formaba parte del equipo de AWS Kubernetes y del que soy mantenedor, el PSA puede personalizarse al iniciarse el servidor API. Sin dicha personalización, la configuración de seguridad de Pod en todo el clúster, utilizando PSA y PSS, se establece en el nivel privilegiado de PSS por defecto para los tres modos de PSA. Esta configuración por defecto da lugar a una configuración de seguridad de Pod insegura en el respectivo clúster.
Para aplicar configuraciones PSA y PSS más seguras, los espacios de nombres de Kubernetes deben optar por una mayor seguridad utilizando las etiquetas de los espacios de nombres de Kubernetes. Como se muestra en el siguiente fragmento de código del proyecto k8s-psa-pss-testing, las etiquetas se utilizan para que el Espacio de Nombres opte por modos PSA y niveles PSS específicos:
# Namespace labels for PSA and PSS settings
apiVersion
:
v1
kind
:
Namespace
metadata
:
name
:
policy-test
labels
:
# pod-security.kubernetes.io/enforce: privileged
# pod-security.kubernetes.io/audit: privileged
# pod-security.kubernetes.io/warn: privileged
pod-security.kubernetes.io/enforce
:
baseline
pod-security.kubernetes.io/audit
:
baseline
pod-security.kubernetes.io/warn
:
baseline
# pod-security.kubernetes.io/enforce: restricted
# pod-security.kubernetes.io/audit: restricted
# pod-security.kubernetes.io/warn: restricted
Además, se pueden utilizar múltiples combinaciones de modos PSA y niveles PSS. En el siguiente ejemplo, el modo de aplicación de PSA se establece para el nivel básico de PSS, mientras que la auditoría y la advertencia de PSA se establecen en PSS restringido. Esto satisface el caso de uso en el que querrías hacer cumplir una línea base de seguridad Pod, pero también comprender el impacto potencial de pasar a PSS restringido:
# Mixed PSA modes and PSS levels
apiVersion
:
v1
kind
:
Namespace
metadata
:
name
:
policy-test
labels
:
pod-security.kubernetes.io/enforce
:
baseline
pod-security.kubernetes.io/audit
:
restricted
pod-security.kubernetes.io/warn
:
restricted
PSA enforce es el único modo de PSA que impide que se produzcan cambios físicos en un clúster de Kubernetes. En el momento de redactar este documento, PSA enforce sólo reacciona ante los cambios en los Pods; no impide que se creen o actualicen otros recursos de Kubernetes que crean o actualizan Pods, ni ningún otro recurso. Por ejemplo, si se aplica un Recurso de implementación de Kubernetes a un clúster, y las especificaciones del Pod infringen la configuración actual de PSA/PSS para el Espacio de nombres en el que residen los Pods, éstos se rechazarían y se impediría su aprovisionamiento. Sin embargo, la Implementación se crearía sin ninguna indicación externa de que algo fuera mal. Los Pods simplemente no arrancarían porque los cambios para aprovisionar los Pods no se validarían ni se permitiría que llegaran a etcd.
Puesto que el modo de aplicación PSA no detendría la Implementación de Kubernetes, tendrías que examinar el estado del Recurso de implementación para determinar por qué no se iniciaron los Pods:
# Examine Deployment status to determine Pod start issues $ kubectl -n policy-test get deploy test -oyaml ... status: conditions: ... - lastTransitionTime: "2022-07-12T23:56:10Z" lastUpdateTime: "2022-07-12T23:56:10Z" message: > 'pods "test-59955f994-wl8hf" is forbidden: violates PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "test" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "test" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "test" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "test" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")' reason: FailedCreate status: "True" type: ReplicaFailure ...
Esta es una experiencia de usuario difícil y otra razón por la que querrías utilizar todos los modos PSA, no sólo el de forzar. Los modos advertir y auditar de PSA sí reaccionan ante los controladores Pod, como los recursos Kubernetes Implementación y DaemonSet. Así, aunque no se impidieran las Implementaciones, al menos el cliente del servidor API de Kubernetes y los registros de auditoría del servidor API recibirían indicaciones de que existe una posible violación de la seguridad. Un ejemplo de advertencia, enviada a los clientes del servidor API de Kubernetes, puede verse en la siguiente salida de consola:
Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "test" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "test" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "test" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "test" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost") deployment.apps/test created
Para la gente que sustituye el PSP o implanta la seguridad Pod, el PSA es una migración relativamente sencilla, siempre que los niveles de PSS satisfagan sus necesidades de seguridad. Sin embargo, cuando los usuarios de Kubernetes necesitan más granularidad y flexibilidad para sus esquemas de seguridad Pod o incluso quieren imponer otros comportamientos de recursos Kubernetes adicionales, las soluciones PaC son una mejor opción.
También es importante entender que PaC puede ejecutarse en el mismo clúster que PSA y PSS; no son mutuamente excluyentes. De hecho, hay casos de uso en los que PaC mejora la seguridad y la experiencia de usuario de PSA y PSS. Por ejemplo, PaC puede mejorar el uso de PSA y PSS aplicando el modelo de admisión de PSA/PSS. Utilizando los controladores de admisión dinámicos de Kubernetes, que se trataron anteriormente en este capítulo, PaC puede hacer cumplir las etiquetas PSA/PSS en los Espacios de Nombres, mediante políticas, y en las configuraciones de webhooks mutantes y validadores.
Nota
Para obtener más información y ejemplos sobre el uso de soluciones PaC con Kubernetes PSA/PSS, consulta esta entrada del blog de la que soy coautor con Jim Bugwadia, de Nirmata, "Managing Pod Security on Amazon EKS with Kyverno" (Gestión de la seguridad de los pods en Amazon EKS con Kyverno).
No todas las soluciones PaC proceden de fuera de Kubernetes; en otras palabras, no todas las PaC están fuera del árbol y proceden del ecosistema Kubernetes. En esta sección, hemos revisado PSA, una función dentro del árbol, que cuando se combina con PSS, proporciona la capacidad de aplicar políticas para la seguridad Pod. Sin embargo, sólo hemos explorado ligeramente la seguridad de los Pods en Kubernetes; profundizaremos en los capítulos siguientes, cuando examinemos soluciones PaC específicas y su uso con Kubernetes.
Ahora vamos a descubrir una nueva función de Kubernetes in-tree que está internalizando PaC como parte de una oferta de Kubernetes in-tree.
Validar la política de admisión
A partir de Kubernetes v1.28, una nueva solución PaC en el árbol, la Validación de la Política de Admisión (VAP), está en estado beta. VAP es una solución PaC nativa (en el árbol) integrada en Kubernetes que es "altamente configurable". Con VAP, los administradores y operadores de clústeres pueden crear políticas para aplicar buenas prácticas y controles de seguridad.
A diferencia del PSA, el VAP funciona tanto con recursos Pod como no Pod. La funcionalidad de VAP se asemeja a las soluciones PaC que trataré en los próximos capítulos. Sin embargo, a diferencia de esas soluciones PaC, VAP está integrada en Kubernetes y no requiere ningún software adicional instalado.
Para utilizar esta función, debes aplicar las configuraciones correctas para habilitarla en tu clúster:
-
Activa la puerta de función ValidatingAdmissionPolicy.
-
Activa la API admissionregistration.k8s.io/v1alpha1 .
Los recursos de Kubernetes necesarios para el VAP son:
- ValidatingAdmissionPolicy
-
Contiene la lógica de la regla para la política, incluyendo a qué recursos y operaciones se aplica la política, así como las expresiones del Lenguaje de Expresión Común (CEL)
- ValidatingAdmissionPolicyBinding
-
Vincula la política a un ámbito específico, como un espacio de nombres, y vincula recursos de parámetros a la política
- RecursoParámetro
-
Permite que la configuración de la política esté separada de la definición de la política
VAP va más allá de la seguridad Pod y ofrece una solución PaC integrada en Kubernetes, basada en CEL de Google. La sintaxis de CEL se asemeja a la de C o Java y se incrusta en los recursos YAML, como se muestra en los siguientes ejemplos de recursos VAP:
# ValidatingAdmissionPolicy resource
apiVersion
:
admissionregistration.k8s.io/v1alpha1
kind
:
ValidatingAdmissionPolicy
metadata
:
name
:
"deploy-history-policy.jimmyray.io"
spec
:
failurePolicy
:
Fail
paramKind
:
apiVersion
:
rules.jimmyray.io/v1
kind
:
HistoryLimit
matchConstraints
:
resourceRules
:
-
apiGroups
:
[
"apps"
]
apiVersions
:
[
"v1"
]
operations
:
[
"CREATE"
,
"UPDATE"
]
resources
:
[
"deployments"
]
validations
:
-
expression
:
"object.spec.revisionHistoryLimit
<=
params.historyLimit"
reason
:
Invalid
El recurso ValidatingAdmissionPolicy anterior se aplica a Implementaciones creadas o actualizadas. La sintaxis CEL busca el ajuste revisionHistoryLimit en la especificación de Implementaciones. El campo failurePolicy
puede establecerse en Fallo o Ignorar. Fallo significa que si la expresión CEL es falsa, la operación de la API falla y no cambia el clúster.
El campo paramKind
establece el recurso de parámetros que la ValidatingAdmissionPolicy utilizará para obtener el parámetro o parámetros que necesita para evaluar la solicitud del servidor API. Ese recurso de parámetros se muestra en el siguiente YAML:
# Resource parameter
apiVersion
:
rules.jimmyray.io/v1
kind
:
HistoryLimit
metadata
:
name
:
"deploy-history-limit.jimmyray.io"
historyLimit
:
3
Para conectarlo todo, necesitamos el enlace ValidatingAdmissionPolicyBinding, que se muestra en el siguiente YAML:
# Binding
apiVersion
:
admissionregistration.k8s.io/v1alpha1
kind
:
ValidatingAdmissionPolicyBinding
metadata
:
name
:
"deploy-history-binding.jimmyray.io"
spec
:
policy
:
"deploy-history-policy.jimmyray.io"
paramsRef
:
name
:
"deploy-history-limit.jimmyray.io"
matchResources
:
namespaceSelectors
:
-
key
:
environment,
operator
:
In,
values
:
[
"policy-test"
]
El ValidatingAdmissionPolicyBinding vincula los recursos de la política y de los parámetros y, a continuación, delimita la aplicación de la política al espacio de nombres policy-test
.
Para probar esta política, utilizaremos dos Implementaciones:
-
Especificación buena conocida con revisionHistoryLimit <= 3
-
Especificación mala conocida con revisionHistoryLimit > 3 (incluido un campo revisionHistoryLimit que falta y que por defecto es 10)
El mensaje de error, provocado por el fallo de validación, se muestra en la siguiente salida de consola:
# VAP error message ValidatingAdmissionPolicy 'deploy-history-policy.jimmyray.io' with binding 'deploy-history-binding.jimmyray.io' denied request: failed expression: object.spec.revisionHistoryLimit <= 3
Tras años trabajando con PaC, soy cautelosamente optimista sobre el potencial de la función ValidatingAdmissionPolicy respaldada por CEL. ValidatingAdmissionPolicy tiene el potencial de reducir la carga de gestionar la infraestructura de las soluciones PaC que tienen que instalarse en Kubernetes después de aprovisionar los clústeres. También me entusiasma poder profundizar por fin en CEL, con casos de uso que facilitan la aplicación de mi experiencia en PaC.
Nota
Además de la función ValidatingAdmissionPolicy , el proyecto Kubernetes está trabajando en una nueva Propuesta de Mejora de Kubernetes (KEP) para una función MutatingAdmissionPolicy. KEP-3962 cubre esta nueva función MutatingAdmissionPolicy. En el momento de escribir esto, la función está prevista para su lanzamiento inicial en la versión v1.31 de Kubernetes.
Ahora, dejemos brevemente atrás el control de admisión y pasemos al control de acceso para ver cómo puede utilizarse el PaC para casos de uso de AuthZ en Kubernetes.
Modo Webhook AuthZ
Llegados a este punto, quiero dejar de centrarme en el control de admisión y examinar otra posibilidad de integrar PaC en Kubernetes para AuthZ. AuthZ en Kubernetes está diseñado para controlar el acceso al servidor API de Kubernetes desde clientes externos e internos. Kubernetes admite varios modos de AuthZ, incluido el acceso a nivel de nodo, AuthZ de cliente a través de la integración de webhooks, y RBAC y ABAC, que ya habíamos explorado en el Capítulo 3.
A diferencia del PaC para el control de admisión -que se integra principalmente en los controladores de admisión dinámicos-, la integración del webhook AuthZ se configura en el servidor API durante el arranque del clúster. Este tipo de AuthZ no se utiliza tanto como el RBAC, pero puede combinarse con éste para obtener una seguridad más granular. El modo webhook puede ser muy eficaz para integrarse con proveedores externos de AuthN. El webhook AuthZ no se utiliza tanto como el RBAC, principalmente por la necesidad de configurar el servidor API de Kubernetes para integrarse en clústeres o servicios remotos para gestionar las decisiones AuthZ. Además, las decisiones de diseño de Kubernetes multitenancy, como el uso de Namespaces granulares con RBAC y el aumento de AuthZ con servicios de admisión mutantes y validadores -integrados mediante controladores de admisión dinámicos- han sido tradicionalmente más fáciles de configurar y soportar.
Al igual que con los controles de admisión, el servidor API de Kubernetes envía cargas útiles JSON a los servicios webhook de AuthZ y recibe decisiones de permitido o denegado. El siguiente ejemplo de carga útil se envía desde el servidor de la API:
// SubjectAccessReview
{
"apiVersion"
:
"authorization.k8s.io/v1beta1"
,
"kind"
:
"SubjectAccessReview"
,
"spec"
:
{
"resourceAttributes"
:
{
"namespace"
:
"kube-system"
,
"verb"
:
"get"
,
"resource"
:
"pods"
,
"version"
:
"v1"
},
"user"
:
"jimmy"
,
"groups"
:
[
"system:authenticated"
,
"devops"
]
}
}
Lo primero que hay que observar es que la carga útil de ejemplo de SubjectAccessReview es más pequeña que la carga útil de ejemplo de AdmissionReview de antes en este capítulo. Esto se debe principalmente al hecho de que la AdmissionReview contiene en su interior los cambios reales de recursos del clúster que se desean, y la SubjectAccessReview contiene principalmente acciones deseadas e información del principio de AuthN, como la pertenencia a un grupo.
Ahora que entendemos la idea del modo webhook AuthZ, veamos más de cerca cómo se toman las decisiones AuthZ con este enfoque.
Decisiones AuthZ
El YAML de configuración del Webhook AuthZ reutiliza el formato kubeconfig, en el que el nodo clusters se utiliza para configurar los servicios AuthZ -también conocidos como autorizadores- llamadospor el servidor API. El nodo de usuarios se utiliza para configurar el servidor de la API para que se comunique de forma segura con el servicio webhook. Esto significa que se pueden utilizar varios autorizadores encadenados para las decisiones de AuthZ por parte del servidor API.
Las decisiones devueltas por los autorizadores son permitidas o denegadas, y estas decisiones no siempre son mutuamente excluyentes. Dado que se pueden configurar varios autorizadores para que devuelvan decisiones AuthZ, es posible que no todos los autorizadores puedan "permitir" o autorizar a un usuario a realizar una acción. Y si un autorizador no puede permitir el acceso, eso no significa necesariamente que ese mismo autorizador pueda denegarlo. Podría ser que el autorizador que no puede permitir el acceso simplemente no tenga reglas deterministas para denegar el acceso.
En el caso de varios autorizadores, un autorizador que no pueda permitir un acceso, por la razón que sea, puede pasar la decisión a los autorizadores siguientes devolviendo una condición allowed=false
sin devolver una condición denied=true
. Un autorizador con las reglas apropiadas para actuar sobre la información transmitida a través de SubjectAccessReview -con lógica determinista- también puede denegar la solicitud. Con múltiples autorizadores, las decisiones de acceso se vuelven muy granulares. La Tabla 4-1 explica cómo pueden combinarse las decisiones de permitir y denegar.
permitido = verdadero | El autorizador puede permitir. | |
permitido = false | El autorizador no puede permitir, pero tampoco puede denegar. | |
permitido = false | denegado = verdadero | El autorizador puede denegar. |
Los autorizadores devuelven un objeto JSON SubjectAccessReview con el campo status
que contiene la decisión y los mensajes aplicables:
// Decision to allow access, message not necessary
{
"apiVersion"
:
"authorization.k8s.io/v1"
,
"kind"
:
"SubjectAccessReview"
,
"status"
:
{
"allowed"
:
true
}
}
// Decision to disallow access, but not deny, additional authorizers can deny
{
"apiVersion"
:
"authorization.k8s.io/v1"
,
"kind"
:
"SubjectAccessReview"
,
"status"
:
{
"allowed"
:
false
,
"reason"
:
"Non-admin users cannot access admin namespaces."
}
}
// Decision to disallow and deny
{
"apiVersion"
:
"authorization.k8s.io/v1"
,
"kind"
:
"SubjectAccessReview"
,
"status"
:
{
"allowed"
:
false
,
"denied"
:
true
,
"reason"
:
"Non-admin users cannot access admin namespaces."
}
}
Cuando un autorizador envía una respuesta a denied=true
, se evita la necesidad de contactar con otros autorizadores configurados para que tomen una decisión.
Ahora que entendemos lo que hacen los autorizadores, veamos cómo lo hacen, con la integración del PaC.
AuthZ Webhook y PaC
Dado que se intercambia JSON entre el servidor API y los servicios AuthZ, es fácil imaginar cómo se pueden utilizar las herramientas PaC para tomar las decisiones AuthZ para el servidor API. El truco está en iniciar el servidor API y configurar el webhook de AuthZ sin impedir que se realicen los cambios necesarios antes de configurar el webhook de AuthZ.
La documentación de Kubernetes parece indicar que se utiliza un servicio externo para crear la integración del webhook AuthZ. Sin embargo, una buena práctica de PaC es localizar los motores de decisión lo más cerca posible de los puntos de decisión. Añadir saltos de red adicionales llamando a servicios externos al clúster puede hacer que la respuesta sea más lenta y la solución más frágil. Ejecutar motores PaC dentro del clúster, con múltiples Pods, también crea tolerancia a la partición.
Puedes encontrar un ejemplo de esta configuración que utiliza kind, OPA y kubeadm para demostrar esta solución como contribución de OPA. Esta solución ejecuta OPA en los nodos del plano de control de Kubernetes utilizando recursos DaemonSet. La configuración(Figura 4-3) establece una IP estática en el recurso del Servicio Kubernetes que dirige los múltiples Pods OPA creados por el DaemonSet.
Utilizando las tentaciones y tolerancias de Kubernetes, los Pods mostrados en la Figura 4-3 están aislados en los nodos CP de Kubernetes. De hecho, cualquier servicio que sea sensible a las operacionesdel clúster -comoel servicio AuthZ del servidor API- debe aislarse en los nodos CP.
Ejemplo de política
Para crear las decisiones del autorizador anteriores, dado el objeto SubjectAccessReview, el siguiente ejemplo de política OPA denegará a los usuarios que no sean administradores el acceso a los Namespaces de los administradores:
# OPA policy to restrict admin Namespace access
packagek8s.authz
import
future.keywords.in
# Admin namespaces
admin_nss:
=
[
"kube-system"
,"admin"
,"opa"
]
# Non-admin users cannot access admin namespaces.
deny[
reason]
{
input.spec.resourceAttributes.namespace
in
admin_nss
not
"admin"
in
input.spec.groups
reason
:
=
"Non-admin users cannot access admin namespaces."
}
decision
=
{
"apiVersion"
:
"v1"
,
"kind"
:
"SubjectAccessReview"
,
"status"
:
{
"allowed"
:count
(
deny)
==
0
,
"deny"
:count
(
deny)
>
0
,
"reason"
:concat
(
" | "
,deny
)
,
}
,
}
El objetivo del webhook AuthZ es permitir o denegar el acceso a las acciones del servidor de la API de Kubernetes basándose en la información sobre el principio autenticado que intenta realizar la acción. Para que quede claro, esta información AuthN no se ve en el objeto AdmissionReview utilizado durante la mutación y validación de la admisión mediante controladores de admisión dinámicos, a menos que se añadan datos similares a la solicitud entrante de forma no autorizada. Por tanto, aunque el PaC puede utilizarse fácilmente para validar las solicitudes entrantes del servidor API a través de controladores de admisión dinámicos, la información necesaria para autorizar las solicitudes -como hacen los autorizadores de webhooks AuthZ- no está disponible. En este momento, los controladores de admisión dinámicos de Kubernetes no pueden utilizarse para este tipo de casos de uso de AuthZ.
Nota
El equipo de AWS Kubernetes (EKS) presentó en diciembre de 2023 lo que denominaron "controles de gestión de acceso simplificados de Amazon EKS". Este enfoque se basa en la funcionalidad del autorizador de Kubernetes. Mientras estuve en AWS en el equipo de Kubernetes, ayudé a probar la función y contribuí a esta entrada de blog que la presenta. Mi nombre no aparece en el post como autor, ya que dejé AWS antes de que se lanzara la función.
Antes de terminar este capítulo, debemos explorar cómo se pueden implantar y utilizar los informes PaC.
Informes políticos
Las distintas soluciones de PaC ofrecen distintos medios de elaboración de informes y registro. Aunque todas las soluciones de PaC que he utilizado tienen distintos niveles de registro, registrar no es informar, del mismo modo que recopilar métricas no es registrar. Los informes PaC se utilizan para verificar que tu solución PaC está proporcionando el resultado deseado; además, los informes PaC pueden crear artefactos informativos y auditables para satisfacer los requisitos normativos internos y externos.
En lo que respecta a los informes de PaC dentro de Kubernetes, la implementación -en la que yo basaría un estándar- es el formato abierto definido por el GT de Políticas de Kubernetes. Este PolicyReport se basa en una definición de recurso personalizada (CRD) de Kubernetes:
# policyreports.wgpolicyk8s.io CRD $ kubectl get crd policyreports.wgpolicyk8s.io -oyaml
apiVersion
:
apiextensions.k8s.io/v1
kind
:
CustomResourceDefinition
metadata
:
…
name
:
policyreports.wgpolicyk8s.io
spec
:
conversion
:
strategy
:
None
group
:
wgpolicyk8s.io
names
:
kind
:
PolicyReport
listKind
:
PolicyReportList
plural
:
policyreports
shortNames
:
-
polr
singular
:
policyreport
scope
:
Namespaced
versions
:
…
name
:
v1alpha2
Como se ve en la salida abreviada -los CRD son notoriamente largos y enrevesados-, la versión actual del CRD de PolicyReport es v1alpha2. Dado el patrón de adopción de Kuberrnetes, la adopción de nuevas funciones no suele aumentar hasta que la función es promovida al menos al estado beta. Dicho esto, he utilizado PolicyReports con el motor de políticas Kyverno.
Consejo
Los CRDde Kubernetes se utilizan para ampliar la API de Kubernetes. Puedes utilizar el siguiente comando kubectl api-resources
para ver los recursos API existentes en tu clúster:
$ kubectl api-resources | grep pod NAME SHORTNAMES APIVERSION NAMESPACED KIND pods po v1 true Pod …
Puedes utilizar el siguiente comando kubectl explain
para explorar las API:
$ kubectl explain pod.spec.containers.name KIND: Pod VERSION: v1 FIELD: name <string> DESCRIPTION: Name of the container specified as a DNS_LABEL. Each container in a Pod must have a unique name (DNS_LABEL). Cannot be updated.
Los Informes de Política pueden tener un ámbito de espacio de nombres y un ámbito de clúster. El siguiente ejemplo incluye un PolicyReport del espacio de nombres kyverno
:
# List PolicyReports in the kyverno namespace $ kubectl -n kyverno get policyreports.wgpolicyk8s.io NAME PASS FAIL WARN ERROR SKIP AGE cpol-example 2 0 0 0 0 1h10m
# Review PolicyReport $ kubectl -n kyverno get policyreports.wgpolicyk8s.io cpol-example \ -o yaml
apiVersion
:
wgpolicyk8s.io/v1alpha2
kind
:
PolicyReport
metadata
:
…
labels
:
app.kubernetes.io/managed-by
:
kyverno
name
:
cpol-example
namespace
:
kyverno
results
:
-
message
:
Validation rule 'autogen-example' passed.
policy
:
example
resources
:
-
apiVersion
:
apps/v1
kind
:
Deployment
name
:
kyverno
namespace
:
kyverno
result
:
pass
rule
:
autogen-example
scored
:
true
source
:
kyverno
…
summary
:
error
:
0
fail
:
0
pass
:
2
skip
:
0
warn
:
0
El PolicyReport anterior corresponde a la ClusterPolicy cpol-example Kyverno que se autogeneró para los recursos de Implementación a partir de una política Pod definida. El informe también indica que la política ha superado dos validaciones. El PolicyReport es un artefacto útil para el consumo humano, y como es YAML, también es legible por la máquina, como las propias políticas.
No todas las soluciones de PaC incluyen informes, y exploraremos esa característica y otras a medida que profundicemos en soluciones específicas de PaC en capítulos posteriores.
Resumen
Si hay algo que he aprendido trabajando con Kubernetes durante los últimos siete años, es que Kubernetes evoluciona constantemente, espoleado por su comunidad de desarrolladores y partes interesadas. A medida que Kubernetes sigue cambiando, es inevitable que PaC también lo haga. Ahora somos testigos de esa evolución, con nuevas funciones de PaC procedentes del ecosistema Kubernetes, así como nuevas soluciones dentro del árbol.
En este capítulo, he expuesto varios casos de uso de Kubernetes que pueden satisfacerse utilizando PaC. Ahora deberías tener conocimientos básicos sobre los controladores de admisión y su finalidad en tus clústeres. Cubrí cómo puedes mutar y validar las solicitudes entrantes del servidor API con controladores de admisión dinámicos, así como utilizar políticas para responder a eventos en tu clúster, como modificaciones de recursos (CREATE y UPDATE).
Exploramos nuevas soluciones dentro del árbol, como la Admisión de Seguridad de Pods y la Validación de la Política de Admisión. Dimos un pequeño rodeo para explorar cómo puede utilizarse PaC para AuthZ utilizando el modo webhook de Kubernetes. Por último, revisamos las normas emergentes de información de políticas del GT de Políticas.
Con toda esta información que te he pedido que digieras en este capítulo, quiero recordarte mi recomendación original del Capítulo 1 sobre cómo elegir la solución PaC adecuada a tus necesidades. Hay varios casos de uso en este capítulo que deberías tener en cuenta para tu cuadro de mando de selección de soluciones de PaC si diriges y proteges clústeres Kubernetes. A medida que analicemos cada solución a lo largo de los próximos capítulos, deberás considerar hasta qué punto la solución se ajusta al caso de uso descrito, así como a tus necesidades y capacidades. Te guiaré por el camino, discutiendo la idoneidad, exponiendo problemas y retos, y haciendo recomendaciones.
Comenzaremos nuestro estudio de soluciones PaC específicas en el Capítulo 5, cuando exploremos cómo se instala y utiliza OPA con Kubernetes para mejorar la seguridad, el control y la experiencia del usuario.
Get La Política como Código now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.