Створення 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:

    Zip
    $ kubectl apply -f samples/curl/curl.yaml

    В іншому випадку вам потрібно вручну виконати інʼєкцію sidecar перед розгортанням застосунку curl:

    Zip
    $ 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 доступу до сервісу.

  1. Створіть 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
  2. Зробіть запит до зовнішнього 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 для вихідного трафіку

  1. Перевизначте ваш 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.

  2. Надішліть 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, без потреби змінювати жодного рядка коду.

  3. Зверніть увагу, що застосунки, які використовували 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. Цей приклад значно складніший, оскільки він вимагає наступної конфігурації:

  1. Створення сертифікатів клієнта та сервера
  2. Розгортання зовнішнього сервісу, який підтримує протокол взаємної автентифікації TLS
  3. Налаштування клієнта (curl pod) на використання облікових даних, створених на Кроці 1

Коли ця конфігурація буде завершена, ви зможете налаштувати зовнішній трафік так, щоб він проходив через sidecar, який виконає TLS-автентифікацію.

Створення сертифікатів і ключів клієнта та сервера

Для цього завдання ви можете використовувати будь-який зручний для вас інструмент для створення сертифікатів і ключів. Команди нижче використовують openssl.

  1. Створіть кореневий сертифікат та приватний ключ для підписання сертифіката для ваших сервісів:

    $ 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
  2. Створіть сертифікат і приватний ключ для 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
  3. Згенеруйте клієнтський сертифікат і приватний ключ:

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

  1. Створіть простір імен для представлення сервісів поза мережею Istio, а саме mesh-external. Зверніть увагу, що sidecar проксі не буде автоматично додаватися в podʼах цього простору імен, оскільки автоматична інʼєкція sidecar не була увімкнена для цього простору.

    $ kubectl create namespace mesh-external
  2. Створіть 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
  3. Створіть конфігураційний файл для сервера 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
  4. Створіть Kubernetes ConfigMap для зберігання конфігурації сервера NGINX:

    $ kubectl create configmap nginx-configmap -n mesh-external --from-file=nginx.conf=./nginx.conf
  5. Розгорніть сервер 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)

  1. Створіть 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.

  2. Створіть необхідний 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

  1. Додайте 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.

  2. Переконайтеся, що обліковий запис підʼєднано до 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
  3. Надішліть 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> ...
  4. Перевірте лог 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

  1. Видаліть створені ресурси 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
  2. Видаліть сертифікати та приватні ключі:

    $ 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
  3. Видаліть згенеровані конфігураційні файли, використані у цьому прикладі:

    $ rm ./nginx.conf

Очищення загальної конфігурації

Видалити сервіс curl та розгортання:

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

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