Створення 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
Чи була ця інформація корисною?
Чи є у вас пропозиції щодо покращення?

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