Política de Autenticación

Esta tarea cubre las actividades principales que podría necesitar realizar al habilitar, configurar y usar las políticas de autenticación de Istio. Obtenga más información sobre los conceptos subyacentes en la descripción general de la autenticación.

Antes de empezar

$ istioctl install --set profile=default

Configuración

Nuestros ejemplos utilizan dos namespaces foo y bar, con dos services, httpbin y curl, ambos ejecutándose con un proxy Envoy. También utilizamos segundas instancias de httpbin y curl ejecutándose sin el sidecar en el namespace legacy. Si desea utilizar los mismos ejemplos al probar las tareas, ejecute lo siguiente:

ZipZipZipZipZipZip
$ kubectl create ns foo
$ kubectl apply -f <(istioctl kube-inject -f @samples/httpbin/httpbin.yaml@) -n foo
$ kubectl apply -f <(istioctl kube-inject -f @samples/curl/curl.yaml@) -n foo
$ kubectl create ns bar
$ kubectl apply -f <(istioctl kube-inject -f @samples/httpbin/httpbin.yaml@) -n bar
$ kubectl apply -f <(istioctl kube-inject -f @samples/curl/curl.yaml@) -n bar
$ kubectl create ns legacy
$ kubectl apply -f @samples/httpbin/httpbin.yaml@ -n legacy
$ kubectl apply -f @samples/curl/curl.yaml@ -n legacy

Puede verificar la configuración enviando una solicitud HTTP con curl desde cualquier pod curl en el namespace foo, bar o legacy a httpbin.foo, httpbin.bar o httpbin.legacy. Todas las solicitudes deberían tener éxito con el código HTTP 200.

Por ejemplo, aquí hay un comando para verificar la accesibilidad de curl.bar a httpbin.foo:

$ kubectl exec "$(kubectl get pod -l app=curl -n bar -o jsonpath={.items..metadata.name})" -c curl -n bar -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "%{\n"http_code}
"
200

Este comando de una sola línea itera convenientemente a través de todas las combinaciones de accesibilidad:

$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec "$(kubectl get pod -l app=curl -n ${from} -o jsonpath={.items..metadata.name})" -c curl -n ${from} -- curl -s "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "curl.${from} to httpbin.${to}: %{\n"http_code}
""; done; done
curl.foo to httpbin.foo: 200
curl.foo to httpbin.bar: 200
curl.foo to httpbin.legacy: 200
curl.bar to httpbin.foo: 200
curl.bar to httpbin.bar: 200
curl.bar to httpbin.legacy: 200
curl.legacy to httpbin.foo: 000
command terminated with exit code 56
curl.legacy to httpbin.bar: 000
command terminated with exit code 56
curl.legacy to httpbin.legacy: 200

Verifique que no haya ninguna política de autenticación de pares en el sistema con el siguiente comando:

$ kubectl get peerauthentication --all-namespaces
No resources found

Por último, pero no menos importante, verifique que no haya reglas de destino que se apliquen a los services de ejemplo. Puede hacerlo comprobando el valor host: de las reglas de destino existentes y asegurándose de que no coincidan. Por ejemplo:

$ kubectl get destinationrules.networking.istio.io --all-namespaces -o yaml | grep "host:"

mTLS automático

Por defecto, Istio rastrea los workloads del servidor migrados a los proxies de Istio, y configura los proxies del cliente para enviar tráfico mTLS a esos workloads automáticamente, y para enviar tráfico de texto plano a los workloads sin sidecars.

Así, todo el tráfico entre workloads con proxies utiliza mTLS, sin que usted haga nada. Por ejemplo, tome la respuesta de una solicitud a httpbin/header. Cuando se utiliza mTLS, el proxy inyecta la cabecera X-Forwarded-Client-Cert a la solicitud ascendente al backend. La presencia de esa cabecera es una prueba de que se utiliza mTLS. Por ejemplo:

$ kubectl exec "$(kubectl get pod -l app=curl -n foo -o jsonpath={.items..metadata.name})" -c curl -n foo -- curl -s http://httpbin.foo:8000/headers -s | jq '.headers["X-Forwarded-Client-Cert"][0]' | sed 's/Hash=[a-z0-9]*;/Hash=<redacted>;/'
  "By=spiffe://cluster.local/ns/foo/sa/httpbin;Hash=<redacted>;Subject=\"\";URI=spiffe://cluster.local/ns/foo/sa/curl"

Cuando el servidor no tiene sidecar, la cabecera X-Forwarded-Client-Cert no está presente, lo que implica que las solicitudes están en texto plano.

$ kubectl exec "$(kubectl get pod -l app=curl -n foo -o jsonpath={.items..metadata.name})" -c curl -n foo -- curl http://httpbin.legacy:8000/headers -s | grep X-Forwarded-Client-Cert

Habilitar globalmente mTLS de Istio en modo STRICT

Aunque Istio actualiza automáticamente todo el tráfico entre los proxies y los workloads a mTLS, los workloads aún pueden recibir tráfico de texto plano. Para evitar el tráfico no mTLS para toda la malla, establezca una política de autenticación de pares a nivel de malla con el modo mTLS establecido en STRICT. La política de autenticación de pares a nivel de malla no debe tener un selector y debe aplicarse en el namespace raíz, por ejemplo:

$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: "default"
  namespace: "istio-system"
spec:
  mtls:
    mode: STRICT
EOF

Esta política de autenticación de pares configura los workloads para que solo acepten solicitudes cifradas con TLS. Dado que no especifica un valor para el campo selector, la política se aplica a todos los workloads de la malla.

Ejecute el comando de prueba de nuevo:

$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec "$(kubectl get pod -l app=curl -n ${from} -o jsonpath={.items..metadata.name})" -c curl -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "curl.${from} to httpbin.${to}: %{\n"http_code}
""; done; done
curl.foo to httpbin.foo: 200
curl.foo to httpbin.bar: 200
curl.foo to httpbin.legacy: 200
curl.bar to httpbin.foo: 200
curl.bar to httpbin.bar: 200
curl.bar to httpbin.legacy: 200
curl.legacy to httpbin.foo: 000
command terminated with exit code 56
curl.legacy to httpbin.bar: 000
command terminated with exit code 56
curl.legacy to httpbin.legacy: 200

Verá que las solicitudes siguen teniendo éxito, excepto las del cliente que no tiene proxy, curl.legacy, al servidor con un proxy, httpbin.foo o httpbin.bar. Esto es de esperar porque ahora se requiere estrictamente mTLS, pero el workload sin sidecar no puede cumplir.

Limpieza parte 1

Elimine la política de autenticación global agregada en la sesión:

$ kubectl delete peerauthentication -n istio-system default

Habilitar mTLS por namespace o workload

Política a nivel de namespace

Para cambiar mTLS para todos los workloads dentro de un namespace particular, use una política a nivel de namespace. La especificación de la política es la misma que para una política a nivel de malla, pero especifica el namespace al que se aplica en metadata. Por ejemplo, la siguiente política de autenticación de pares habilita mTLS estricto para el namespace foo:

$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: "default"
  namespace: "foo"
spec:
  mtls:
    mode: STRICT
EOF

Como esta política se aplica solo a los workloads en el namespace foo, solo debería ver que las solicitudes del cliente sin sidecar (curl.legacy) a httpbin.foo comienzan a fallar.

$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec "$(kubectl get pod -l app=curl -n ${from} -o jsonpath={.items..metadata.name})" -c curl -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "curl.${from} to httpbin.${to}: %{\n"http_code}
""; done; done
curl.foo to httpbin.foo: 200
curl.foo to httpbin.bar: 200
curl.foo to httpbin.legacy: 200
curl.bar to httpbin.foo: 200
curl.bar to httpbin.bar: 200
curl.bar to httpbin.legacy: 200
curl.legacy to httpbin.foo: 000
command terminated with exit code 56
curl.legacy to httpbin.bar: 200
curl.legacy to httpbin.legacy: 200

Habilitar mTLS por workload

Para establecer una política de autenticación de pares para un workload específico, debe configurar la sección selector y especificar las etiquetas que coincidan con el workload deseado. Por ejemplo, la siguiente política de autenticación de pares habilita mTLS estricto para el workload httpbin.bar:

$ cat <<EOF | kubectl apply -n bar -f -
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: "httpbin"
  namespace: "bar"
spec:
  selector:
    matchLabels:
      app: httpbin
  mtls:
    mode: STRICT
EOF

De nuevo, ejecute el comando de sondeo. Como era de esperar, la solicitud de curl.legacy a httpbin.bar comienza a fallar por las mismas razones.

$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec "$(kubectl get pod -l app=curl -n ${from} -o jsonpath={.items..metadata.name})" -c curl -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "curl.${from} to httpbin.${to}: %{\n"http_code}
""; done; done
curl.foo to httpbin.foo: 200
curl.foo to httpbin.bar: 200
curl.foo to httpbin.legacy: 200
curl.bar to httpbin.foo: 200
curl.bar to httpbin.bar: 200
curl.bar to httpbin.legacy: 200
curl.legacy to httpbin.foo: 000
command terminated with exit code 56
curl.legacy to httpbin.bar: 000
command terminated with exit code 56
curl.legacy to httpbin.legacy: 200
...
curl.legacy to httpbin.bar: 000
command terminated with exit code 56

Para refinar la configuración de mTLS por puerto, debe configurar la sección portLevelMtls. Por ejemplo, la siguiente política de autenticación de pares requiere mTLS en todos los puertos, excepto el puerto 8080:

$ cat <<EOF | kubectl apply -n bar -f -
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: "httpbin"
  namespace: "bar"
spec:
  selector:
    matchLabels:
      app: httpbin
  mtls:
    mode: STRICT
  portLevelMtls:
    8080:
      mode: DISABLE
EOF
  1. El valor del puerto en la política de autenticación de pares es el puerto del contenedor.
  2. Solo puede usar portLevelMtls si el puerto está vinculado a un service. Istio lo ignora de lo contrario.
$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec "$(kubectl get pod -l app=curl -n ${from} -o jsonpath={.items..metadata.name})" -c curl -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "curl.${from} to httpbin.${to}: %{\n"http_code}
""; done; done
curl.foo to httpbin.foo: 200
curl.foo to httpbin.bar: 200
curl.foo to httpbin.legacy: 200
curl.bar to httpbin.foo: 200
curl.bar to httpbin.bar: 200
curl.bar to httpbin.legacy: 200
curl.legacy to httpbin.foo: 000
command terminated with exit code 56
curl.legacy to httpbin.bar: 200
curl.legacy to httpbin.legacy: 200

Precedencia de políticas

Una política de autenticación de pares específica de workload tiene precedencia sobre una política a nivel de namespace. Puede probar este comportamiento si agrega una política para deshabilitar mTLS para el workload httpbin.foo, por ejemplo. Tenga en cuenta que ya ha creado una política a nivel de namespace que habilita mTLS para todos los services en el namespace foo y observe que las solicitudes de curl.legacy a httpbin.foo están fallando (ver arriba).

$ cat <<EOF | kubectl apply -n foo -f -
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: "overwrite-example"
  namespace: "foo"
spec:
  selector:
    matchLabels:
      app: httpbin
  mtls:
    mode: DISABLE
EOF

Al volver a ejecutar la solicitud de curl.legacy, debería ver un código de retorno de éxito de nuevo (200), lo que confirma que la política específica del service anula la política a nivel de namespace.

$ kubectl exec "$(kubectl get pod -l app=curl -n legacy -o jsonpath={.items..metadata.name})" -c curl -n legacy -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "%{\n"http_code}
"
200

Limpieza parte 2

Elimine las políticas creadas en los pasos anteriores:

$ kubectl delete peerauthentication default overwrite-example -n foo
$ kubectl delete peerauthentication httpbin -n bar

Autenticación de usuario final

Para experimentar con esta feature, necesita un JWT válido. El JWT debe corresponder al endpoint JWKS que desea utilizar para la demostración. Este tutorial utiliza el token de prueba JWT test y el endpoint JWKS de la base de código de Istio.

Además, para mayor comodidad, exponga httpbin.foo a través de un ingress gateway (para más detalles, consulte la tarea de ingress).

Configure el gateway:

Zip
$ kubectl apply -f @samples/httpbin/httpbin-gateway.yaml@ -n foo

Siga las instrucciones en Determinación de la IP y los puertos de ingress para definir las variables de entorno INGRESS_PORT e INGRESS_HOST.

Ejecute una consulta de prueba a través del gateway:

$ curl "$INGRESS_HOST:$INGRESS_PORT/headers" -s -o /dev/null -w "%{\n"http_code}
"
200

Ahora, agregue una política de autenticación de solicitudes que requiera JWT de usuario final para el ingress gateway.

$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
  name: "jwt-example"
  namespace: istio-system
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  jwtRules:
  - issuer: "testing@secure.istio.io"
    jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.27/security/tools/jwt/samples/jwks.json"
EOF

Aplique la política en el namespace del workload que selecciona, el ingress gateway en este caso.

Si proporciona un token en la cabecera de autorización, su ubicación predeterminada implícita, Istio valida el token utilizando el conjunto de claves públicas, y rechaza las solicitudes si el token de portador no es válido. Sin embargo, las solicitudes sin tokens son aceptadas. Para observar este comportamiento, reintente la solicitud sin un token, con un token incorrecto y con un token válido:

$ curl "$INGRESS_HOST:$INGRESS_PORT/headers" -s -o /dev/null -w "%{\n"http_code}
"
200
$ curl --header "Authorization: Bearer deadbeef" "$INGRESS_HOST:$INGRESS_PORT/headers" -s -o /dev/null -w "%{\n"http_code}
"
401
$ TOKEN=$(curl https://raw.githubusercontent.com/istio/istio/release-1.27/security/tools/jwt/samples/demo.jwt -s)
$ curl --header "Authorization: Bearer $TOKEN" "$INGRESS_HOST:$INGRESS_PORT/headers" -s -o /dev/null -w "%{\n"http_code}
"
200

Para observar otros aspectos de la validación de JWT, use el script gen-jwt.py para generar nuevos tokens para probar con diferentes emisores, audiencias, fechas de vencimiento, etc. El script se puede descargar del repositorio de Istio:

$ wget --no-verbose https://raw.githubusercontent.com/istio/istio/release-1.27/security/tools/jwt/samples/gen-jwt.py

También necesita el fichero key.pem:

$ wget --no-verbose https://raw.githubusercontent.com/istio/istio/release-1.27/security/tools/jwt/samples/key.pem

La autenticación JWT tiene un sesgo de reloj de 60 segundos, lo que significa que el token JWT será válido 60 segundos antes de su nbf configurado y seguirá siendo válido 60 segundos después de su exp configurado.

Por ejemplo, el comando siguiente crea un token que expira en 5 segundos. Como ve, Istio autentica las solicitudes utilizando ese token con éxito al principio, pero las rechaza después de 65 segundos:

$ TOKEN=$(python3 ./gen-jwt.py ./key.pem --expire 5)
$ for i in $(seq 1 10); do curl --header "Authorization: Bearer $TOKEN" "$INGRESS_HOST:$INGRESS_PORT/headers" -s -o /dev/null -w "%{\n"http_code}
""; sleep 10; done
200
200
200
200
200
200
200
401
401
401

También puede agregar una política JWT a un ingress gateway (por ejemplo, el service istio-ingressgateway.istio-system.svc.cluster.local). Esto se usa a menudo para definir una política JWT para todos los services vinculados al gateway, en lugar de para services individuales.

Requerir un token válido

Para rechazar solicitudes sin tokens válidos, agregue una política de autorización con una regla que especifique una acción DENY para solicitudes sin principales de solicitud, mostradas como notRequestPrincipals: ["*"] en el siguiente ejemplo. Los principales de solicitud solo están disponibles cuando se proporcionan tokens JWT válidos. Por lo tanto, la regla deniega las solicitudes sin tokens válidos.

$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: "frontend-ingress"
  namespace: istio-system
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  action: DENY
  rules:
  - from:
    - source:
        notRequestPrincipals: ["*"]
EOF

Reintente la solicitud sin un token. La solicitud ahora falla con el código de error 403:

$ curl "$INGRESS_HOST:$INGRESS_PORT/headers" -s -o /dev/null -w "%{\n"http_code}
"
403

Requerir tokens válidos por ruta

Para refinar la autorización con un requisito de token por host, ruta o método, cambie la política de autorización para que solo requiera JWT en /headers. Cuando esta regla de autorización surte efecto, las solicitudes a $INGRESS_HOST:$INGRESS_PORT/headers fallan con el código de error 403. Las solicitudes a todas las demás rutas tienen éxito, por ejemplo $INGRESS_HOST:$INGRESS_PORT/ip.

$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: "frontend-ingress"
  namespace: istio-system
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  action: DENY
  rules:
  - from:
    - source:
        notRequestPrincipals: ["*"]
    to:
    - operation:
        paths: ["/headers"]
EOF
$ curl "$INGRESS_HOST:$INGRESS_PORT/headers" -s -o /dev/null -w "%{\n"http_code}
"
403
$ curl "$INGRESS_HOST:$INGRESS_PORT/ip" -s -o /dev/null -w "%{\n"http_code}
"
200

Limpieza parte 3

  1. Elimine la política de autenticación:

    $ kubectl -n istio-system delete requestauthentication jwt-example
  2. Elimine la política de autorización:

    $ kubectl -n istio-system delete authorizationpolicy frontend-ingress
  3. Elimine el script generador de tokens y el fichero de claves:

    $ rm -f ./gen-jwt.py ./key.pem
  4. Si no planea explorar ninguna tarea de seguimiento, puede eliminar todos los recursos simplemente eliminando los namespaces de prueba.

    $ kubectl delete ns foo bar legacy
¿Fue útil esta información?
¿Tienes alguna sugerencia para mejorar?

¡Gracias por tus comentarios!