Політика автентифікації
Це завдання охоплює основні дії, які можуть знадобитися для увімкнення, налаштування та використання політик автентифікації Istio. Дізнайтеся більше про основні концепції у огляді автентифікації.
Перед початком
Дізнайтесь про політику автентифікації Istio і повʼязані концепції взаємної TLS-автентифікації.
Встановіть Istio в кластер Kubernetes з конфігураційним профілем
default
, як описано в кроках установки.
$ istioctl install --set profile=default
Налаштування
Наші приклади використовують два простори імен: foo
і bar
, з двома сервісами, httpbin
і curl
, які обидва працюють з проксі Envoy. Ми також використовуємо інші екземпляри httpbin
і curl
, що працюють без sidecar у просторі імен legacy
. Якщо ви хочете використовувати ті ж приклади для виконання завдань, виконайте наступне:
$ 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
- Значення порту в політиці однорангової автентифікації — це порт контейнера.
- Ви можете використовувати
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).
Налаштуйте шлюз:
$ kubectl apply -f @samples/httpbin/httpbin-gateway.yaml@ -n foo
Дотримуйтесь інструкцій з Визначення вхідного IP і портів для визначення змінних оточення INGRESS_PORT
і INGRESS_HOST
.
Створіть шлюз:
$ kubectl apply -f @samples/httpbin/gateway-api/httpbin-gateway.yaml@ -n foo
$ kubectl wait --for=condition=programmed gtw -n foo httpbin-gateway
Встановіть змінні оточення INGRESS_PORT
та INGRESS_HOST
:
$ export INGRESS_HOST=$(kubectl get gtw httpbin-gateway -n foo -o jsonpath='{.status.addresses[0].value}')
$ export INGRESS_PORT=$(kubectl get gtw httpbin-gateway -n foo -o jsonpath='{.spec.listeners[?(@.name=="http")].port}')
Виконайте тестовий запит через шлюз:
$ 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
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
name: "jwt-example"
namespace: foo
spec:
targetRef:
kind: Gateway
group: gateway.networking.k8s.io
name: httpbin-gateway
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
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: "frontend-ingress"
namespace: foo
spec:
targetRef:
kind: Gateway
group: gateway.networking.k8s.io
name: httpbin-gateway
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
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: "frontend-ingress"
namespace: foo
spec:
targetRef:
kind: Gateway
group: gateway.networking.k8s.io
name: httpbin-gateway
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
Видаліть політику автентифікації:
$ kubectl -n istio-system delete requestauthentication jwt-example
Видаліть політику авторизації:
$ kubectl -n istio-system delete authorizationpolicy frontend-ingress
Видаліть скрипт генератора токенів і файл ключа:
$ rm -f ./gen-jwt.py ./key.pem
Якщо ви не плануєте досліджувати подальші завдання, ви можете вилучити всі ресурси, просто видаливши тестові простори імен.
$ kubectl delete ns foo bar legacy