Створення TLS для вихідного трафіку
Завдання Доступ до зовнішніх сервісів демонструє, як HTTP та HTTPS сервіси, що знаходяться поза межами сервісної мережі (service mesh), можуть бути доступні з застосунків всередині mesh. Як описано в цьому завданні, ServiceEntry
використовується для налаштування доступу до зовнішніх сервісів у контрольований спосіб через Istio. У цьому прикладі показано, як налаштувати Istio для виконання створення TLS для трафіку до зовнішнього сервісу. Istio відкриє HTTPS-зʼєднання із зовнішнім сервісом, тоді як оригінальний трафік буде HTTP.
Використання
Розглянемо приклад старого застосунку, який здійснює HTTP-запити до зовнішніх сайтів. Припустимо, організація, що керує застосунком, отримує нову вимогу, яка передбачає шифрування всього зовнішнього трафіку. З Istio цю вимогу можна реалізувати лише за допомогою конфігурації, без необхідності змінювати код застосунку. Застосунок може надсилати незашифровані HTTP-запити, а Istio зашифрує їх для нього.
Ще однією перевагою надсилання незашифрованих HTTP-запитів від джерела та дозволу Istio виконувати оновлення до TLS є те, що Istio може створювати кращу телеметрію та надавати більше контролю за маршрутизацією для запитів, які не зашифровані.
Перш ніж почати
Налаштуйте Istio, дотримуючись інструкцій з Посібника з встановлення.
Запустіть демонстраційний застосунок curl, який буде використовуватися як тестове джерело для зовнішніх викликів.
Якщо у вас увімкнено автоматичну інʼєкцію sidecar, виконайте наступну команду, розгорніть застосунок
curl
:$ kubectl apply -f @samples/curl/curl.yaml@
В іншому випадку вам потрібно вручну виконати інʼєкцію sidecar перед розгортанням застосунку
curl
:$ kubectl apply -f <(istioctl kube-inject -f @samples/curl/curl.yaml@)
Зверніть увагу, що будь-який pod, з якого ви можете виконати
exec
таcurl
, підійде для подальших процедур.Створіть змінну shell для збереження імені podʼа джерела для надсилання запитів до зовнішніх сервісів. Якщо ви використовували curl, виконайте:
$ export SOURCE_POD=$(kubectl get pod -l app=curl -o jsonpath={.items..metadata.name})
Налаштування доступу до зовнішнього сервісу
Спочатку налаштуйте доступ до зовнішнього сервісу, edition.cnn.com
, використовуючи ту саму техніку, що й у завданні Доступ до зовнішніх сервісів. Цього разу, однак, використовуйте один ServiceEntry
для увімкнення як HTTP, так і HTTPS доступу до сервісу.
Створіть
ServiceEntry
, щоб увімкнути доступ доedition.cnn.com
:$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1 kind: ServiceEntry metadata: name: edition-cnn-com spec: hosts: - edition.cnn.com ports: - number: 80 name: http-port protocol: HTTP - number: 443 name: https-port protocol: HTTPS resolution: DNS EOF
Зробіть запит до зовнішнього HTTP-сервісу:
$ kubectl exec "${SOURCE_POD}" -c curl -- curl -sSL -o /dev/null -D - http://edition.cnn.com/politics HTTP/1.1 301 Moved Permanently ... location: https://edition.cnn.com/politics ... HTTP/2 200 ...
Вихідні дані мають бути схожими на наведені вище (деякі деталі замінено на багатокрапки).
Зверніть увагу на прапорець -L
у curl, який вказує curl слідувати за перенаправленнями. У цьому випадку сервер повернув відповідь про перенаправлення (301 Moved Permanently) на HTTP запит до http://edition.cnn.com/politics
. Відповідь про перенаправлення вказує клієнту надіслати додатковий запит, цього разу з використанням HTTPS, до https://edition.cnn.com/politics
. Для другого запиту сервер повернув запитуваний контент і статус-код 200 OK.
Хоча команда curl обробила перенаправлення прозоро, є дві проблеми. Перша проблема — це надмірний запит, який подвоює затримку при отриманні контенту з http://edition.cnn.com/politics
. Друга проблема полягає в тому, що шлях URL, politics у цьому випадку, надсилається у відкритому тексті. Якщо є зловмисник, який перехоплює комунікацію між вашим застосунком та edition.cnn.com
, зловмисник знатиме, які конкретні теми з edition.cnn.com
застосунок отримав. З міркувань конфіденційності ви можете захотіти запобігти такому розголошенню.
Обидві ці проблеми можна вирішити, налаштувавши Istio для виконання створення TLS (TLS origination).
Створення TLS для вихідного трафіку
Перевизначте ваш
ServiceEntry
з попереднього розділу, щоб перенаправляти HTTP-запити на порт 443 і додайтеDestinationRule
для виконання створення TLS:$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1 kind: ServiceEntry metadata: name: edition-cnn-com spec: hosts: - edition.cnn.com ports: - number: 80 name: http-port protocol: HTTP targetPort: 443 - number: 443 name: https-port protocol: HTTPS resolution: DNS --- apiVersion: networking.istio.io/v1 kind: DestinationRule metadata: name: edition-cnn-com spec: host: edition.cnn.com trafficPolicy: portLevelSettings: - port: number: 80 tls: mode: SIMPLE # ініціює HTTPS під час доступу до edition.cnn.com EOF
Вищевказане
DestinationRule
виконає створення TLS для HTTP-запитів на порту 80, аServiceEntry
буде перенаправляти запити на порт 80 на цільовий порт 443.Надішліть HTTP-запит на
http://edition.cnn.com/politics
, як у попередньому розділі:$ kubectl exec "${SOURCE_POD}" -c curl -- curl -sSL -o /dev/null -D - http://edition.cnn.com/politics HTTP/1.1 200 OK ...
Цього разу ви отримали відповідь 200 OK як першу і єдину відповідь. Istio виконав TLS origination для curl, тому оригінальний HTTP запит був перенаправлений до
edition.cnn.com
як HTTPS. Сервер повернув контент без потреби в перенаправленні. Ви усунули подвійний обмін між клієнтом і сервером, а запит залишив мережу у зашифрованому вигляді, не розголошуючи, що ваш застосунок отримав розділ politics зedition.cnn.com
.Зверніть увагу, що ви використали ту ж команду, що і в попередньому розділі. Для застосунків, які отримують доступ до зовнішніх служб програмно, код змінювати не потрібно. Ви отримуєте переваги TLS origination, налаштувавши Istio, без потреби змінювати жодного рядка коду.
Зверніть увагу, що застосунки, які використовували HTTPS для доступу до зовнішнього сервісу, продовжують працювати як і раніше:
$ kubectl exec "${SOURCE_POD}" -c curl -- curl -sSL -o /dev/null -D - https://edition.cnn.com/politics HTTP/2 200 ...
Додаткові міркування з безпеки
Оскільки трафік між застосунком та sidecar проксі на локальному хості все ще не зашифрований, зловмисник, який зможе проникнути на вузол вашого застосунку, зможе побачити нешифрований звʼязок в локальній мережі вузла. У деяких середовищах суворі вимоги до безпеки можуть вимагати, щоб весь трафік був зашифрований, навіть в локальній мережі вузлів. З такими суворими вимогами застосунки повинні використовувати лише HTTPS (TLS). TLS origination, описаний у цьому прикладі, не буде достатнім.
Також слід зазначити, що навіть з HTTPS, ініційованим застосунком, зловмисник може дізнатися, що запити до edition.cnn.com
надсилаються, перевіряючи Server Name Indication (SNI). Поле SNI надсилається нешифрованим під час рукостискання TLS. Використання HTTPS запобігає зловмисникам знати конкретні теми та статті, але не заважає зловмисникам дізнатися, що був виконаний доступ до edition.cnn.com
.
Видалення конфігурації TLS origination
Видаліть створені вами елементи конфігурації Istio:
$ kubectl delete serviceentry edition-cnn-com
$ kubectl delete destinationrule edition-cnn-com
Взаємний TLS для вихідного трафіку
У цьому розділі описується, як налаштувати sidecar для виконання TLS-автентифікації для зовнішнього сервісу, цього разу використовуючи сервіс, що потребує взаємної автентифікації TLS. Цей приклад значно складніший, оскільки він вимагає наступної конфігурації:
- Створення сертифікатів клієнта та сервера
- Розгортання зовнішнього сервісу, який підтримує протокол взаємної автентифікації TLS
- Налаштування клієнта (curl pod) на використання облікових даних, створених на Кроці 1
Коли ця конфігурація буде завершена, ви зможете налаштувати зовнішній трафік так, щоб він проходив через sidecar, який виконає TLS-автентифікацію.
Створення сертифікатів і ключів клієнта та сервера
Для цього завдання ви можете використовувати будь-який зручний для вас інструмент для створення сертифікатів і ключів. Команди нижче використовують openssl.
Створіть кореневий сертифікат та приватний ключ для підписання сертифіката для ваших сервісів:
$ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example.com.key -out example.com.crt
Створіть сертифікат і приватний ключ для
my-nginx.mesh-external.svc.cluster.local
:$ openssl req -out my-nginx.mesh-external.svc.cluster.local.csr -newkey rsa:2048 -nodes -keyout my-nginx.mesh-external.svc.cluster.local.key -subj "/CN=my-nginx.mesh-external.svc.cluster.local/O=some organization" $ openssl x509 -req -sha256 -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in my-nginx.mesh-external.svc.cluster.local.csr -out my-nginx.mesh-external.svc.cluster.local.crt
За бажанням ви можете додати
SubjectAltNames
до сертифіката, якщо хочете увімкнути перевірку SAN для місця призначення. Наприклад:$ cat > san.conf <<EOF [req] distinguished_name = req_distinguished_name req_extensions = v3_req x509_extensions = v3_req prompt = no [req_distinguished_name] countryName = US [v3_req] keyUsage = critical, digitalSignature, keyEncipherment extendedKeyUsage = serverAuth, clientAuth basicConstraints = critical, CA:FALSE subjectAltName = critical, @alt_names [alt_names] DNS = my-nginx.mesh-external.svc.cluster.local EOF $ $ openssl req -out my-nginx.mesh-external.svc.cluster.local.csr -newkey rsa:4096 -nodes -keyout my-nginx.mesh-external.svc.cluster.local.key -subj "/CN=my-nginx.mesh-external.svc.cluster.local/O=some organization" -config san.conf $ openssl x509 -req -sha256 -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in my-nginx.mesh-external.svc.cluster.local.csr -out my-nginx.mesh-external.svc.cluster.local.crt -extfile san.conf -extensions v3_req
Згенеруйте клієнтський сертифікат і приватний ключ:
$ openssl req -out client.example.com.csr -newkey rsa:2048 -nodes -keyout client.example.com.key -subj "/CN=client.example.com/O=client organization" $ openssl x509 -req -sha256 -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 1 -in client.example.com.csr -out client.example.com.crt
Розгортання сервера з взаємною автентифікацією TLS
Щоб симулювати справжній зовнішній сервіс, який підтримує протокол взаємної автентифікації TLS, розгорніть сервер NGINX у вашому кластері Kubernetes, але запустіть його поза мережею сервісів Istio, тобто в просторі імен без увімкненого інжектора sidecar проксі Istio.
Створіть простір імен для представлення сервісів поза мережею Istio, а саме
mesh-external
. Зверніть увагу, що sidecar проксі не буде автоматично додаватися в podʼах цього простору імен, оскільки автоматична інʼєкція sidecar не була увімкнена для цього простору.$ kubectl create namespace mesh-external
Створіть Kubernetes [Secrets] (https://kubernetes.io/docs/concepts/configuration/secret/) для зберігання сертифікатів сервера та центру сертифікації.
$ kubectl create -n mesh-external secret tls nginx-server-certs --key my-nginx.mesh-external.svc.cluster.local.key --cert my-nginx.mesh-external.svc.cluster.local.crt $ kubectl create -n mesh-external secret generic nginx-ca-certs --from-file=example.com.crt
Створіть конфігураційний файл для сервера NGINX:
$ cat <<\EOF > ./nginx.conf events { } http { log_format main '$remote_addr - $remote_user [$time_local] $status ' '"$request" $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log; server { listen 443 ssl; root /usr/share/nginx/html; index index.html; server_name my-nginx.mesh-external.svc.cluster.local; ssl_certificate /etc/nginx-server-certs/tls.crt; ssl_certificate_key /etc/nginx-server-certs/tls.key; ssl_client_certificate /etc/nginx-ca-certs/example.com.crt; ssl_verify_client on; } } EOF
Створіть Kubernetes ConfigMap для зберігання конфігурації сервера NGINX:
$ kubectl create configmap nginx-configmap -n mesh-external --from-file=nginx.conf=./nginx.conf
Розгорніть сервер NGINX:
$ kubectl apply -f - <<EOF apiVersion: v1 kind: Service metadata: name: my-nginx namespace: mesh-external labels: run: my-nginx annotations: "networking.istio.io/exportTo": "." # simulate an external service by not exporting outside this namespace spec: ports: - port: 443 protocol: TCP selector: run: my-nginx --- apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx namespace: mesh-external spec: selector: matchLabels: run: my-nginx replicas: 1 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: nginx ports: - containerPort: 443 volumeMounts: - name: nginx-config mountPath: /etc/nginx readOnly: true - name: nginx-server-certs mountPath: /etc/nginx-server-certs readOnly: true - name: nginx-ca-certs mountPath: /etc/nginx-ca-certs readOnly: true volumes: - name: nginx-config configMap: name: nginx-configmap - name: nginx-server-certs secret: secretName: nginx-server-certs - name: nginx-ca-certs secret: secretName: nginx-ca-certs EOF
Налаштуйте клієнта (curl pod)
Створіть Kubernetes [Secrets] (https://kubernetes.io/docs/concepts/configuration/secret/) для зберігання сертифікатів клієнта:
$ kubectl create secret generic client-credential --from-file=tls.key=client.example.com.key \ --from-file=tls.crt=client.example.com.crt --from-file=ca.crt=example.com.crt
Секрет має бути створений у тому ж просторі імен, у якому розгорнуто клієнтський pod, у даному випадку
default
.Створіть необхідний
RBAC
, щоб переконатися, що секрет, створений на попередньому кроці, доступний клієнтському pod, який ’ у цьому випадку —curl
.$ kubectl create role client-credential-role --resource=secret --verb=list $ kubectl create rolebinding client-credential-role-binding --role=client-credential-role --serviceaccount=default:curl
Налаштування взаємного TLS для вихідного трафіку на sidecar
Додайте
ServiceEntry
для перенаправлення HTTP-запитів на порт 443 і додайтеDestinationRule
для виконання взаємного TLS origination:$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1 kind: ServiceEntry metadata: name: originate-mtls-for-nginx spec: hosts: - my-nginx.mesh-external.svc.cluster.local ports: - number: 80 name: http-port protocol: HTTP targetPort: 443 - number: 443 name: https-port protocol: HTTPS resolution: DNS --- apiVersion: networking.istio.io/v1 kind: DestinationRule metadata: name: originate-mtls-for-nginx spec: workloadSelector: matchLabels: app: curl host: my-nginx.mesh-external.svc.cluster.local trafficPolicy: loadBalancer: simple: ROUND_ROBIN portLevelSettings: - port: number: 80 tls: mode: MUTUAL credentialName: client-credential # це має збігатися з секретом, створеним раніше для зберігання клієнтських сертифікатів, і працює тільки тоді, коли DR має workloadSelector sni: my-nginx.mesh-external.svc.cluster.local # subjectAltNames: # можна ввімкнути, якщо сертифікат було згенеровано за допомогою SAN, як зазначено в попередньому розділі # - my-nginx.mesh-external.svc.cluster.local EOF
Вищевказане правило
DestinationRule
виконає створення mTLS для HTTP-запитів на порт 80, аServiceEntry
буде перенаправляти запити на порт 80 на цільовий порт 443.Переконайтеся, що обліковий запис підʼєднано до sidecar та активовано.
$ istioctl proxy-config secret deploy/curl | grep client-credential kubernetes://client-credential Cert Chain ACTIVE true 1 2024-06-04T12:15:20Z 2023-06-05T12:15:20Z kubernetes://client-credential-cacert Cert Chain ACTIVE true 10792363984292733914 2024-06-04T12:15:19Z 2023-06-05T12:15:19Z
Надішліть HTTP-запит до
http://my-nginx.mesh-external.svc.cluster.local
:$ kubectl exec "$(kubectl get pod -l app=curl -o jsonpath={.items..metadata.name})" -c curl -- curl -sS http://my-nginx.mesh-external.svc.cluster.local <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> ...
Перевірте лог podʼа
curl
на наявність радка, що відповідає вашому запиту.$ kubectl logs -l app=curl -c istio-proxy | grep 'my-nginx.mesh-external.svc.cluster.local'
Ви повинні побачити рядок, схожий на наступний:
[2022-05-19T10:01:06.795Z] "GET / HTTP/1.1" 200 - via_upstream - "-" 0 615 1 0 "-" "curl/7.83.1-DEV" "96e8d8a7-92ce-9939-aa47-9f5f530a69fb" "my-nginx.mesh-external.svc.cluster.local:443" "10.107.176.65:443"
Видалення конфігурації взаємного TLS origination
Видаліть створені ресурси Kubernetes:
$ kubectl delete secret nginx-server-certs nginx-ca-certs -n mesh-external $ kubectl delete secret client-credential $ kubectl delete rolebinding client-credential-role-binding $ kubectl delete role client-credential-role $ kubectl delete configmap nginx-configmap -n mesh-external $ kubectl delete service my-nginx -n mesh-external $ kubectl delete deployment my-nginx -n mesh-external $ kubectl delete namespace mesh-external $ kubectl delete serviceentry originate-mtls-for-nginx $ kubectl delete destinationrule originate-mtls-for-nginx
Видаліть сертифікати та приватні ключі:
$ rm example.com.crt example.com.key my-nginx.mesh-external.svc.cluster.local.crt my-nginx.mesh-external.svc.cluster.local.key my-nginx.mesh-external.svc.cluster.local.csr client.example.com.crt client.example.com.csr client.example.com.key
Видаліть згенеровані конфігураційні файли, використані у цьому прикладі:
$ rm ./nginx.conf
Очищення загальної конфігурації
Видалити сервіс curl
та розгортання:
$ kubectl delete service curl
$ kubectl delete deployment curl