Політика автентифікації

Це завдання охоплює основні дії, які можуть знадобитися для увімкнення, налаштування та використання політик автентифікації Istio. Дізнайтеся більше про основні концепції у огляді автентифікації.

Перед початком

$ istioctl install --set profile=default

Налаштування

Наші приклади використовують два простори імен: foo і bar, з двома сервісами, httpbin і curl, які обидва працюють з проксі Envoy. Ми також використовуємо інші екземпляри httpbin і curl, що працюють без sidecar у просторі імен legacy. Якщо ви хочете використовувати ті ж приклади для виконання завдань, виконайте наступне:

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

Ви можете перевірити налаштування, відправивши HTTP-запит за допомогою curl з будь-якого podʼа curl у просторі імен foo, bar або legacy на будь-який з httpbin.foo, httpbin.bar або httpbin.legacy. Усі запити мають бути успішними з HTTP-кодом 200.

Наприклад, ось команда для перевірки доступності curl.bar до 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 "%{http_code}\n"
200

Ця команда зручно перебирає всі комбінації доступності:

$ 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}: %{http_code}\n"; 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: 200
curl.legacy to httpbin.bar: 200
curl.legacy to httpbin.legacy: 200

Переконайтеся, що в системі немає політики однорангової автентифікації за допомогою наступної команди:

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

Останнім кроком перевірте, що немає правил призначення, які застосовуються до демонстраційних сервісів. Ви можете зробити це, перевіривши значення host: існуючих правил призначення та переконавшись, що вони не збігаються. Наприклад:

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

Автоматичний взаємний TLS

Стандартно Istio відстежує серверні робочі навантаження, перенесені на проксі Istio, і налаштовує проксі клієнтів для автоматичного надсилання трафіку з взаємним TLS до цих робочих навантажень і для надсилання простого текстового трафіку до робочих навантажень без sidecar.

Таким чином, весь трафік між робочими навантаженнями з проксі використовує взаємний TLS, без додаткових дій з вашого боку. Наприклад, візьміть відповідь на запит до httpbin/header. При використанні взаємного TLS проксі вставляє заголовок X-Forwarded-Client-Cert у запит до бекенду. Наявність цього заголовка є доказом використання взаємного TLS. Наприклад:

$ 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"

Коли сервер не має sidecar, заголовок X-Forwarded-Client-Cert відсутній, що вказує на те, що запити передаються у звичайному текстовому режимі.

$ 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

Глобальне увімкнення взаємного TLS Istio в режимі STRICT

Хоча Istio автоматично оновлює весь трафік між проксі та робочими навантаженнями до взаємного TLS, робочі навантаження все ще можуть отримувати трафік у звичайному текстовому форматі. Щоб запобігти невзаємному TLS-трафіку для всієї мережі, встановіть політику однорангової автентифікації для всієї мережі з режимом взаємного TLS, встановленим на STRICT. Політика однорангової автентифікації для всієї мережі не повинна мати selector і повинна застосовуватися, наприклад, у кореневому просторі імен:

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

Ця політика однорангової автентифікації налаштовує робочі навантаження так, щоб вони приймали тільки запити, зашифровані TLS. Оскільки не вказано значення для поля selector, політика застосовується до всіх робочих навантажень у mesh.

Запустіть команду перевірки знову:

$ 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}: %{http_code}\n"; 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, до сервера з проксі, httpbin.foo або httpbin.bar. Це очікувано, оскільки тепер взаємний TLS є обовʼязковим, але робоче навантаження без sidecar не може відповідати вимогам.

Очистка частина 1

Видаліть глобальну політику автентифікації, додану під час сесії:

$ kubectl delete peerauthentication -n istio-system default

Увімкнення взаємного TLS в кожен простір імен або робоче навантаження

Політика для всього простору імен

Щоб змінити взаємний TLS для всіх робочих навантажень у певному просторі імен, використовуйте політику для всього простору імен. Специфікація політики така ж, як і для політики для всього mesh, але ви вказуєте простір імен, до якого вона застосовується, в metadata. Наприклад, наступна політика однорангової автентифікації увімкне строгий взаємний TLS для простору імен foo:

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

Оскільки ця політика застосовується лише до робочих навантажень у просторі імен foo, ви побачите, що тільки запити від клієнта без sidecar (curl.legacy) до httpbin.foo почнуть давати збої.

$ 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}: %{http_code}\n"; 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

Увімкнення взаємного TLS для робочого навантаження

Щоб налаштувати політику однорангової автентифікації для конкретного робочого навантаження, ви повинні налаштувати розділ selector та вказати мітки, які відповідають потрібному робочому навантаженню. Наприклад, наступна політика однорангової автентифікації увімкне строгий взаємний TLS для робочого навантаження 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

Знову запустіть команду probing. Як і очікувалося, запит з curl.legacy до httpbin.bar починає зазнавати невдачі з тих самих причин.

$ 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}: %{http_code}\n"; 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

Щоб уточнити налаштування взаємного TLS для кожного порту, ви повинні налаштувати розділ portLevelMtls. Наприклад, наступна політика однорангової автентифікації вимагає взаємного TLS на всіх портах, окрім порту 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. Значення порту в політиці однорангової автентифікації — це порт контейнера.
  2. Ви можете використовувати portLevelMtls тільки якщо порт привʼязано до сервісу. В іншому випадку Istio ігнорує його.
$ 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}: %{http_code}\n"; 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

Пріоритет політики

Політика однорангової автентифікації для конкретного робочого навантаження має пріоритет над політикою для всього простору імен. Ви можете перевірити таку поведінку, додавши політику вимкнення взаємного TLS для робочого навантаження httpbin.foo, наприклад. Зверніть увагу, що ви вже створили політику для всього простору імен, яка вмикає взаємне TLS для всіх служб у просторі імен foo, і помітили, що запити від curl.legacy до httpbin.foo зазнають невдачі (див. вище).

$ 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

Повторно запустивши запит з curl.legacy, ви знову побачите успішний код повернення (200), що підтверджує перевизначення політики для конкретного сервісу по відношенню до політики для всього простору імен.

$ 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 "%{http_code}\n"
200

Очистка частина 2

Видаліть політики, створені в попередніх кроках:

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

Автентифікація кінцевих користувачів

Щоб експериментувати з цією функцією, вам потрібен дійсний JWT. JWT повинен відповідати точці доступу JWKS, яку ви хочете використовувати для демонстрації. Цей посібник використовує тестовий токен JWT test та точку доступу JWKS з коду Istio.

Також, для зручності, експонуйте httpbin.foo через ingress gateway (для отримання додаткової інформації, див. завдання з ingress).

Налаштуйте шлюз:

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

Дотримуйтесь інструкцій з Визначення вхідного IP і портів для визначення змінних оточення INGRESS_PORT і INGRESS_HOST.

Виконайте тестовий запит через шлюз:

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

Тепер додайте політику автентифікації запитів, яка вимагає від кінцевого користувача JWT для вхідного шлюзу.

$ 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.24/security/tools/jwt/samples/jwks.json"
EOF

Застосуйте політику в просторі імен робочого навантаження, яке вона вибирає, у цьому випадку — у шлюзі входу.

Якщо ви надасте токен у заголовку авторизації, його стандартне місце, Istio перевіряє токен за допомогою набору публічних ключів та відхиляє запити, якщо токен недійсний. Однак запити без токенів приймаються. Щоб спостерігати за цією поведінкою, повторіть запит без токена, з недійсним токеном та з дійсним токеном:

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

Щоб спостерігати за іншими аспектами перевірки JWT, використовуйте скрипт gen-jwt.py, щоб генерувати нові токени для тестування з різними емітентами, аудиторіями, термінами дії тощо. Скрипт можна завантажити з репозиторію Istio:

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

Вам також потрібен файл key.pem:

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

Автентифікація JWT має розбіжність годинника в 60 секунд, що означає, що токен JWT стане дійсним на 60 секунд раніше, ніж його налаштоване значення nbf, і залишиться дійсним на 60 секунд після його налаштованого значення exp.

Наприклад, команда нижче створює токен, який закінчується через 5 секунд. Як ви бачите, Istio спочатку успішно автентифікує запити з цим токеном, але відхиляє їх після 65 секунд:

$ 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 "%{http_code}\n"; sleep 10; done
200
200
200
200
200
200
200
401
401
401

Ви також можете додати політику JWT до шлюзу входу (наприклад, сервіс istio-ingressgateway.istio-system.svc.cluster.local). Це часто використовується для визначення політики JWT для всіх сервісів, привʼязаних до шлюзу, замість окремих сервісів.

Потрібен дійсний токен

Щоб відхиляти запити без дійсних токенів, додайте політику авторизації з правилом, яке вказує дію DENY для запитів без принципалів запиту, показаних як notRequestPrincipals: ["*"] у наступному прикладі. Принципали запиту доступні лише тоді, коли надано дійсні токени JWT. Отже, правило відхиляє запити без дійсних токенів.

$ 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

Повторіть запит без токена. Запит завершився невдачею з кодом помилки 403:

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

Вимагати дійсні токени для кожного шляху

Щоб уточнити авторизацію з вимогою токена для кожного хоста, шляху або методу, змініть політику авторизації, щоб вимагати JWT тільки на /headers. Коли це правило авторизації вступить в силу, запити до $INGRESS_HOST:$INGRESS_PORT/headers завершаться з кодом помилки 403. Запити до всіх інших шляхів успішно обробляються, наприклад, $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 "%{http_code}\n"
403
$ curl "$INGRESS_HOST:$INGRESS_PORT/ip" -s -o /dev/null -w "%{http_code}\n"
200

Очистка частина 3

  1. Видаліть політику автентифікації:

    $ kubectl -n istio-system delete requestauthentication jwt-example
  2. Видаліть політику авторизації:

    $ kubectl -n istio-system delete authorizationpolicy frontend-ingress
  3. Видаліть скрипт генератора токенів і файл ключа:

    $ rm -f ./gen-jwt.py ./key.pem
  4. Якщо ви не плануєте досліджувати подальші завдання, ви можете вилучити всі ресурси, просто видаливши тестові простори імен.

    $ kubectl delete ns foo bar legacy
Чи була ця інформація корисною?
Чи є у вас пропозиції щодо покращення?

Дякуємо за ваш відгук!