Autorización basada en políticas usando Kyverno
Delega la lógica de decisión de autorización de capa 7 usando el Authz Server de Kyverno, aprovechando las políticas basadas en CEL.
Istio admite la integración con muchos proyectos diferentes. El blog de Istio presentó recientemente una publicación sobre la funcionalidad de políticas L7 con OpenPolicyAgent. Kyverno es un proyecto similar, y hoy profundizaremos en cómo Istio y el Kyverno Authz Server pueden usarse juntos para hacer cumplir las políticas de capa 7 en tu plataforma.
Te mostraremos cómo empezar con un ejemplo simple. Verás cómo esta combinación es una opción sólida para entregar políticas de forma rápida y transparente al equipo de aplicaciones en todas partes del negocio, al mismo tiempo que proporciona los datos que los equipos de seguridad necesitan para la auditoría y el cumplimiento.
Pruébalo
Cuando se integra con Istio, el Kyverno Authz Server se puede utilizar para hacer cumplir políticas de control de acceso de grano fino para microservicios.
Esta guía muestra cómo hacer cumplir las políticas de control de acceso para una aplicación de microservicios simple.
Prerrequisitos
- Un cluster de Kubernetes con Istio instalado.
- La herramienta de línea de comandos
istioctl
instalada.
Instala Istio y configura tus opciones de malla para habilitar Kyverno:
$ istioctl install -y -f - <<EOF
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
meshConfig:
accessLogFile: /dev/stdout
accessLogFormat: |
[KYVERNO DEMO] my-new-dynamic-metadata: '%DYNAMIC_METADATA(envoy.filters.http.ext_authz)%'
extensionProviders:
- name: kyverno-authz-server
envoyExtAuthzGrpc:
service: kyverno-authz-server.kyverno.svc.cluster.local
port: '9081'
EOF
Observa que en la configuración, definimos una sección extensionProviders
que apunta a la instalación del Kyverno Authz Server:
[...]
extensionProviders:
- name: kyverno-authz-server
envoyExtAuthzGrpc:
service: kyverno-authz-server.kyverno.svc.cluster.local
port: '9081'
[...]
Desplegar el Kyverno Authz Server
El Kyverno Authz Server es un servidor GRPC capaz de procesar solicitudes de Autorización Externa de Envoy.
Es configurable usando recursos AuthorizationPolicy
de Kyverno, ya sea almacenados en el cluster o proporcionados externamente.
$ kubectl create ns kyverno
$ kubectl label namespace kyverno istio-injection=enabled
$ helm install kyverno-authz-server --namespace kyverno --wait --version 0.1.0 --repo https://kyverno.github.io/kyverno-envoy-plugin kyverno-authz-server
Desplegar la aplicación de ejemplo
httpbin es una aplicación conocida que se puede utilizar para probar solicitudes HTTP y ayuda a mostrar rápidamente cómo podemos jugar con los atributos de la solicitud y la respuesta.
$ kubectl create ns my-app
$ kubectl label namespace my-app istio-injection=enabled
$ kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.26/samples/httpbin/httpbin.yaml -n my-app
Desplegar una AuthorizationPolicy de Istio
Una AuthorizationPolicy
define los servicios que serán protegidos por el Kyverno Authz Server.
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: my-kyverno-authz
namespace: istio-system # Esto aplica la política en toda la malla, siendo istio-system el namespace raíz de la malla
spec:
selector:
matchLabels:
ext-authz: enabled
action: CUSTOM
provider:
name: kyverno-authz-server
rules: [{}] # Reglas vacías, se aplicará a los selectores con la etiqueta ext-authz: enabled
EOF
Observa que en este recurso, definimos el extensionProvider
del Kyverno Authz Server que estableciste en la configuración de Istio:
[...]
provider:
name: kyverno-authz-server
[...]
Etiquetar la aplicación para hacer cumplir la política
Etiquetemos la aplicación para hacer cumplir la política. La etiqueta es necesaria para que la AuthorizationPolicy
de Istio se aplique a los pods de la aplicación de ejemplo.
$ kubectl patch deploy httpbin -n my-app --type=merge -p='{
"spec": {
"template": {
"metadata": {
"labels": {
"ext-authz": "enabled"
}
}
}
}
}'
Desplegar una AuthorizationPolicy de Kyverno
Una AuthorizationPolicy
de Kyverno define las reglas utilizadas por el Kyverno Authz Server para tomar una decisión basada en una CheckRequest de Envoy dada.
Utiliza el lenguaje CEL para analizar una CheckRequest
entrante y se espera que produzca una CheckResponse a cambio.
La solicitud entrante está disponible bajo el campo object
, y la política puede definir variables
que estarán disponibles para todas las authorizations
.
$ kubectl apply -f - <<EOF
apiVersion: envoy.kyverno.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: demo-policy.example.com
spec:
failurePolicy: Fail
variables:
- name: force_authorized
expression: object.attributes.request.http.?headers["x-force-authorized"].orValue("")
- name: allowed
expression: variables.force_authorized in ["enabled", "true"]
authorizations:
- expression: >
variables.allowed
? envoy.Allowed().Response()
: envoy.Denied(403).Response()
EOF
Observa que puedes construir la CheckResponse
a mano o usar funciones de ayuda de CEL como envoy.Allowed()
y envoy.Denied(403)
para simplificar la creación del mensaje de respuesta:
[...]
- expression: >
variables.allowed
? envoy.Allowed().Response()
: envoy.Denied(403).Response()
[...]
Cómo funciona
Al aplicar la AuthorizationPolicy
, el control plane de Istio (istiod) envía las configuraciones requeridas al proxy sidecar (Envoy) de los servicios seleccionados en la política.
Envoy luego enviará la solicitud al Kyverno Authz Server para verificar si la solicitud está permitida o no.
El proxy Envoy funciona configurando filtros en una cadena. Uno de esos filtros es ext_authz
, que implementa un servicio de autorización externa con un mensaje específico. Cualquier servidor que implemente el protobuf correcto puede conectarse al proxy Envoy y proporcionar la decisión de autorización; el Kyverno Authz Server es uno de esos servidores.
Revisando la documentación del servicio de Autorización de Envoy, puedes ver que el mensaje tiene estos atributos:
Respuesta Ok
{ "status": {...}, "ok_response": { "headers": [], "headers_to_remove": [], "response_headers_to_add": [], "query_parameters_to_set": [], "query_parameters_to_remove": [] }, "dynamic_metadata": {...} }
Respuesta denegada
{ "status": {...}, "denied_response": { "status": {...}, "headers": [], "body": "..." }, "dynamic_metadata": {...} }
Esto significa que, basándose en la respuesta del servidor authz, Envoy puede agregar o eliminar encabezados, parámetros de consulta e incluso cambiar el cuerpo de la respuesta.
Podemos hacer esto también, como se documenta en la documentación del Kyverno Authz Server.
Pruebas
Probemos el uso simple (autorización) y luego creemos una política más avanzada para mostrar cómo podemos usar el Kyverno Authz Server para modificar la solicitud y la respuesta.
Despliega una aplicación para ejecutar comandos curl a la aplicación de ejemplo httpbin:
$ kubectl apply -n my-app -f https://raw.githubusercontent.com/istio/istio/release-1.26/samples/curl/curl.yaml
Aplica la política:
$ kubectl apply -f - <<EOF
apiVersion: envoy.kyverno.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: demo-policy.example.com
spec:
failurePolicy: Fail
variables:
- name: force_authorized
expression: object.attributes.request.http.?headers["x-force-authorized"].orValue("")
- name: allowed
expression: variables.force_authorized in ["enabled", "true"]
authorizations:
- expression: >
variables.allowed
? envoy.Allowed().Response()
: envoy.Denied(403).Response()
EOF
El escenario simple es permitir solicitudes si contienen el encabezado x-force-authorized
con el valor enabled
o true
.
Si el encabezado no está presente o tiene un valor diferente, la solicitud será denegada.
En este caso, combinamos el manejo de respuestas permitidas y denegadas en una sola expresión. Sin embargo, es posible usar múltiples expresiones, la primera que devuelva una respuesta no nula será utilizada por el Kyverno Authz Server, esto es útil cuando una regla no quiere tomar una decisión y delega a la siguiente regla:
[...]
authorizations:
# permitir la solicitud cuando el valor del encabezado coincide
- expression: >
variables.allowed
? envoy.Allowed().Response()
: null
# si no, denegar la solicitud
- expression: >
envoy.Denied(403).Response()
[...]
Regla simple
La siguiente solicitud devolverá 403
:
$ kubectl exec -n my-app deploy/curl -- curl -s -w "
http_code=%{http_code}" httpbin:8000/get
La siguiente solicitud devolverá 200
:
$ kubectl exec -n my-app deploy/curl -- curl -s -w "
http_code=%{http_code}" httpbin:8000/get -H "x-force-authorized: true"
Manipulaciones avanzadas
Ahora el caso de uso más avanzado, aplica la segunda política:
$ kubectl apply -f - <<EOF
apiVersion: envoy.kyverno.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: demo-policy.example.com
spec:
variables:
- name: force_authorized
expression: object.attributes.request.http.headers[?"x-force-authorized"].orValue("") in ["enabled", "true"]
- name: force_unauthenticated
expression: object.attributes.request.http.headers[?"x-force-unauthenticated"].orValue("") in ["enabled", "true"]
- name: metadata
expression: '{"my-new-metadata": "my-new-value"}'
authorizations:
# si force_unauthenticated -> 401
- expression: >
variables.force_unauthenticated
? envoy
.Denied(401)
.WithBody("Authentication Failed")
.Response()
: null
# si force_authorized -> 200
- expression: >
variables.force_authorized
? envoy
.Allowed()
.WithHeader("x-validated-by", "my-security-checkpoint")
.WithoutHeader("x-force-authorized")
.WithResponseHeader("x-add-custom-response-header", "added")
.Response()
.WithMetadata(variables.metadata)
: null
# si no -> 403
- expression: >
envoy
.Denied(403)
.WithBody("Unauthorized Request")
.Response()
EOF
En esa política, puedes ver:
- Si la solicitud tiene el encabezado
x-force-unauthenticated: true
(ox-force-unauthenticated: enabled
), devolveremos401
con el cuerpo “Authentication Failed” - Si no, si la solicitud tiene el encabezado
x-force-authorized: true
(ox-force-authorized: enabled
), devolveremos200
y manipularemos los encabezados de la solicitud, los encabezados de la respuesta e inyectaremos metadatos dinámicos - En todos los demás casos, devolveremos
403
con el cuerpo “Unauthorized Request”
La CheckResponse correspondiente se devolverá al proxy Envoy desde el Kyverno Authz Server. Envoy usará esos valores para modificar la solicitud y la respuesta en consecuencia.
Cambiar el cuerpo devuelto
Probemos las nuevas capacidades:
$ kubectl exec -n my-app deploy/curl -- curl -s -w "
http_code=%{http_code}" httpbin:8000/get
Ahora podemos cambiar el cuerpo de la respuesta.
Con 403
el cuerpo se cambiará a “Unauthorized Request”, ejecutando el comando anterior, deberías recibir:
Unauthorized Request
http_code=403
Cambiar el cuerpo y el código de estado devueltos
Ejecutando la solicitud con el encabezado x-force-unauthenticated: true
:
$ kubectl exec -n my-app deploy/curl -- curl -s -w "
http_code=%{http_code}" httpbin:8000/get -H "x-force-unauthenticated: true"
Esta vez deberías recibir el cuerpo “Authentication Failed” y el error 401
:
Authentication Failed
http_code=401
Agregar encabezados a la solicitud
Ejecutando una solicitud válida:
$ kubectl exec -n my-app deploy/curl -- curl -s -w "
http_code=%{http_code}" httpbin:8000/get -H "x-force-authorized: true"
Deberías recibir el cuerpo de eco con el nuevo encabezado x-validated-by: my-security-checkpoint
y el encabezado x-force-authorized
eliminado:
[...]
"X-Validated-By": [
"my-security-checkpoint"
]
[...]
http_code=200
Agregar encabezados a la respuesta
Ejecutando la misma solicitud pero mostrando solo el encabezado:
$ kubectl exec -n my-app deploy/curl -- curl -s -I -w "
http_code=%{http_code}" httpbin:8000/get -H "x-force-authorized: true"
Encontrarás el encabezado de respuesta agregado durante la verificación de Authz x-add-custom-response-header: added
:
HTTP/1.1 200 OK
[...]
x-add-custom-response-header: added
[...]
http_code=200
Compartir datos entre filtros
Finalmente, puedes pasar datos a los siguientes filtros de Envoy usando dynamic_metadata
.
Esto es útil cuando quieres pasar datos a otro filtro ext_authz
en la cadena o quieres imprimirlos en los registros de la aplicación.
Para hacerlo, revisa el formato del registro de acceso que estableciste anteriormente:
[...]
accessLogFormat: |
[KYVERNO DEMO] my-new-dynamic-metadata: "%DYNAMIC_METADATA(envoy.filters.http.ext_authz)%"
[...]
DYNAMIC_METADATA
es una palabra clave reservada para acceder al objeto de metadatos. El resto es el nombre del filtro al que quieres acceder.
En nuestro caso, el nombre envoy.filters.http.ext_authz
es creado automáticamente por Istio. Puedes verificar esto volcando la configuración de Envoy:
$ istioctl pc all deploy/httpbin -n my-app -oyaml | grep envoy.filters.http.ext_authz
Verás las configuraciones para el filtro.
Probemos los metadatos dinámicos. En la regla avanzada, estamos creando una nueva entrada de metadatos: {"my-new-metadata": "my-new-value"}
.
Ejecuta la solicitud y verifica los registros de la aplicación:
$ kubectl exec -n my-app deploy/curl -- curl -s -I httpbin:8000/get -H "x-force-authorized: true"
$ kubectl logs -n my-app deploy/httpbin -c istio-proxy --tail 1
Verás en la salida los nuevos atributos configurados por la política de Kyverno:
[...]
[KYVERNO DEMO] my-new-dynamic-metadata: '{"my-new-metadata":"my-new-value","ext_authz_duration":5}'
[...]
Conclusión
En esta guía, hemos mostrado cómo integrar Istio y el Kyverno Authz Server para hacer cumplir las políticas para una aplicación de microservicios simple. También mostramos cómo usar políticas para modificar los atributos de la solicitud y la respuesta.
Este es el ejemplo fundamental para construir un sistema de políticas para toda la plataforma que pueda ser utilizado por todos los equipos de aplicaciones.