Чи може ваша платформа реалізовувати політики? Прискорте команди завдяки функціональності платформних політик L7

Чи є політика вашою основною компетенцією? Ймовірно, ні, але важливо зробити все правильно. Зробіть це один раз з Istio та OPA і поверніть команді фокус на те, що має найбільше значення.

Oct 14, 2024 | Від Antonio Berben - Solo.io, Charlie Egan - Styra

Спільні обчислювальні платформи надають ресурси та функціональність для команд орендарів, щоб їм не доводилося створювати все з нуля. Хоча часом буває важко збалансувати всі запити від орендарів, важливо, щоб платформа ставила питання: яку найціннішу функцію ми можемо запропонувати нашим орендарям?

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

Ми розглянемо, як Istio та Open Policy Agent (OPA) можуть використовуватися для забезпечення політик рівня L7 у вашій платформі. Ми покажемо, як почати з простого прикладу. Ви побачите, як ця комбінація є надійним варіантом для швидкого і прозорого надання політик командам розробки застосунків у бізнесі, а також забезпечує дані, необхідні командам безпеки для аудиту та дотримання стандартів.

Спробуйте самі

Коли OPA інтегровано з Istio, він може використовуватися для забезпечення детальних політик контролю доступу для мікросервісів. У цьому посібнику описано, як забезпечити політики контролю доступу для простого мікросервісного застосунку.

Попередні вимоги

Встановіть Istio і налаштуйте параметри mesh, щоб увімкнути OPA:

$ istioctl install -y -f - <<'EOF'
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  meshConfig:
    accessLogFile: /dev/stdout
    accessLogFormat: |
      [OPA DEMO] my-new-dynamic-metadata: "%DYNAMIC_METADATA(envoy.filters.http.ext_authz)%"
    extensionProviders:
    - name: "opa.local"
      envoyExtAuthzGrpc:
        service: "opa.opa.svc.cluster.local"
        port: "9191"
EOF

Зверніть увагу, що в конфігурації ми визначаємо розділ extensionProviders, який вказує на самостійне встановлення OPA.

Розгорніть приклад застосунків. Httpbin — відомий застосунок, який можна використовувати для тестування HTTP-запитів; він швидко демонструє, як можна працювати з атрибутами запиту та відповіді.

$ kubectl create ns my-app
$ kubectl label namespace my-app istio-injection=enabled

$ kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/httpbin/httpbin.yaml -n my-app

Розгорніть OPA. Це не вдасться, оскільки очікується configMap, що містить стандартне правило Rego для використання. Цей configMap буде розгорнуто пізніше у нашому прикладі.

$ kubectl create ns opa
$ kubectl label namespace opa istio-injection=enabled

$ kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: opa
  name: opa
  namespace: opa
spec:
  replicas: 1
  selector:
    matchLabels:
      app: opa
  template:
    metadata:
      labels:
        app: opa
    spec:
      containers:
      - image: openpolicyagent/opa:0.61.0-envoy
        name: opa
        args:
          - "run"
          - "--server"
          - "--disable-telemetry"
          - "--config-file=/config/config.yaml"
          - "--log-level=debug" # Розкоментуйте цей рядок, щоб увімкнути журнали налагодження
          - "--diagnostic-addr=0.0.0.0:8282"
          - "/policy/policy.rego" # Стандартна політика
        volumeMounts:
          - mountPath: "/config"
            name: opa-config
          - mountPath: "/policy"
            name: opa-policy
      volumes:
        - name: opa-config
          configMap:
            name: opa-config
        - name: opa-policy
          configMap:
            name: opa-policy
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: opa-config
  namespace: opa
data:
  config.yaml: |
    # Тут ви знайдете конфігурацію OPA, яку ви можете знайти в офіційній документації
    decision_logs:
      console: true
    plugins:
      envoy_ext_authz_grpc:
        addr: ":9191"
        path: mypackage/mysubpackage/myrule # Default path for grpc plugin
    # Тут ви можете додати власну конфігурацію з сервісами та пакетами
---
apiVersion: v1
kind: Service
metadata:
  name: opa
  namespace: opa
  labels:
    app: opa
spec:
  ports:
    - port: 9191
      protocol: TCP
      name: grpc
  selector:
    app: opa
---
EOF

Розгорніть AuthorizationPolicy, щоб визначити, які служби будуть захищені OPA.

$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: my-opa-authz
  namespace: istio-system # Цей рядок застосовує політику до всіх мереж в просторі назв налаштувань istio-system
spec:
  selector:
    matchLabels:
      ext-authz: enabled
  action: CUSTOM
  provider:
    name: "opa.local"
  rules: [{}] # Порожнє правило, буде застосовуватися до селекторів з міткою ext-authz: enabled
EOF

Позначімо застосунок міткою, щоб впровадити політику:

$ kubectl patch deploy httpbin -n my-app --type=merge -p='{
  "spec": {
    "template": {
      "metadata": {
        "labels": {
          "ext-authz": "enabled"
        }
      }
    }
  }
}'

Зверніть увагу, що в цьому ресурсі ми визначаємо OPA extensionProvider, який ви встановили в конфігурації Istio:

[...]
  provider:
    name: "opa.local"
[...]

Як це працює

При застосуванні AuthorizationPolicy панель управління Istio (istiod) надсилає необхідні конфігурації до sidecar проксі (Envoy) вибраних сервісів, зазначених у політиці. Envoy потім відправляє запит на сервер OPA, щоб перевірити, чи дозволено цей запит.

Istio та OPA

Проксі Envoy працює шляхом налаштування фільтрів у ланцюгу. Одним із таких фільтрів є ext_authz, який реалізує зовнішню службу авторизації з певним повідомленням. Будь-який сервер, що реалізує відповідний protobuf, може підʼєднатися до проксі Envoy та надати рішення щодо авторизації; OPA є одним з таких серверів.

Фільтри

Раніше, коли ви встановлювали сервер OPA, ви використовували версію сервера Envoy. Цей образ дозволяє налаштувати втулок gRPC, який впроваджує службу ext_authz protobuf.

[...]
      containers:
      - image: openpolicyagent/opa:0.61.0-envoy # Це версія образу OPA з втулком Envoy
        name: opa
[...]

У конфігурації ви увімкнули втулок Envoy та порт, на якому він слухає:

[...]
    decision_logs:
      console: true
    plugins:
      envoy_ext_authz_grpc:
        addr: ":9191" # Це порт, на якому буде слухати втулок Envoy
        path: mypackage/mysubpackage/myrule # Стандартний шлях для втулка grpc
    # Тут ви можете додати власну конфігурацію з сервісами та наборами даних
[...]

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

OkHttpResponse
{
  "status": {...},
  "denied_response": {...},
  "ok_response": {
      "headers": [],
      "headers_to_remove": [],
      "dynamic_metadata": {...},
      "response_headers_to_add": [],
      "query_parameters_to_set": [],
      "query_parameters_to_remove": []
    },
  "dynamic_metadata": {...}
}

Це означає, що на основі відповіді від сервера authz Envoy може додавати або видаляти заголовки, параметри запиту та навіть змінювати статус відповіді. OPA також може це робити, як описано в його документації.

Тестування

Протестуймо просте використання (авторизацію), а потім створимо більш розширене правило, щоб показати, як можна використовувати OPA для зміни запиту та відповіді.

Розгорніть застосунок для запуску команд curl до тестового застосунку httpbin:

$ kubectl -n my-app run --image=curlimages/curl curl -- /bin/sleep 100d

Застосуйте перше правило Rego і перезапустіть розгортання OPA:

$ kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: opa-policy
  namespace: opa
data:
  policy.rego: |
    package mypackage.mysubpackage

    import rego.v1

    default myrule := false

    myrule if {
      input.attributes.request.http.headers["x-force-authorized"] == "enabled"
    }

    myrule if {
      input.attributes.request.http.headers["x-force-authorized"] == "true"
    }
EOF
$ kubectl rollout restart deployment -n opa

Простий сценарій передбачає дозвіл запитів, якщо вони містять заголовок x-force-authorized зі значенням enabled або true. Якщо заголовок відсутній або має інше значення, запит буде відхилено.

Існує кілька способів створити правило Rego. У цьому випадку ми створили два різні правила. Виконуються вони у порядку, і перше правило, яке задовольняє всі умови, буде застосоване.

Просте правило

Результатом наступного запиту буде відповідь 403:

$ kubectl exec -n my-app curl -c curl  -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get

Наступний запит поверне 200 та тіло відповіді:

$ kubectl exec -n my-app curl -c curl  -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "x-force-authorized: enabled"

Складніші маніпуляції

Тепер складніше правило. Застосуйте друге правило Rego і перезапустіть розгортання OPA:

$ kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: opa-policy
  namespace: opa
data:
  policy.rego: |
    package mypackage.mysubpackage

    import rego.v1

    request_headers := input.attributes.request.http.headers

    force_unauthenticated if request_headers["x-force-unauthenticated"] == "enabled"

    default allow := false

    allow if {
      not force_unauthenticated
      request_headers["x-force-authorized"] == "true"
    }

    default status_code := 403

    status_code := 200 if allow

    status_code := 401 if force_unauthenticated

    default body := "Unauthorized Request"

    body := "Authentication Failed" if force_unauthenticated

    myrule := {
      "body": body,
      "http_status": status_code,
      "allowed": allow,
      "headers": {"x-validated-by": "my-security-checkpoint"},
      "response_headers_to_add": {"x-add-custom-response-header": "added"},
      "request_headers_to_remove": ["x-force-authorized"],
      "dynamic_metadata": {"my-new-metadata": "my-new-value"},
    }
EOF
$ kubectl rollout restart deployment -n opa

В цьому правилі ви можете побачити:

myrule["allowed"] := allow # Зверніть увагу, що `allowed` є обовʼязковим при поверненні обʼєкта, як тут `myrule`.
myrule["headers"] := headers
myrule["response_headers_to_add"] := response_headers_to_add
myrule["request_headers_to_remove"] := request_headers_to_remove
myrule["body"] := body
myrule["http_status"] := status_code

Це значення, які будуть повернуті проксі-серверу Envoy від OPA-сервера. Envoy буде використовувати ці значення для модифікації запиту і відповіді.

Зверніть увагу, що при поверненні JSON-обʼєкта потрібно вказувати allowed, а не тільки true/false. Це можна знайти в документації OPA.

Зміна тіла відповіді

Випробуємо нові можливості:

$ kubectl exec -n my-app curl -c curl  -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get

Тепер ми можемо змінити тіло відповіді. Значення 403 змінює тіло в правилі Rego на «Unauthorized Request» (Несанкціонований запит). За допомогою попередньої команди ви повинні отримати:

Unauthorized Request
http_code=403

Зміна тіла, що повертається і коду статусу

Запустивши запит із заголовком x-force-authorized: enabled ви повинні отримати тіло «Authentication Failed» і помилку «401»:

$ kubectl exec -n my-app curl -c curl  -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "x-force-unauthenticated: enabled"

Додавання заголовків до запиту

Запустивши відповідний запит, ви повинні отримати тіло відповіді з новим заголовком x-validated-by: my-security-checkpoint і видаленим заголовком x-force-authorized:

$ kubectl exec -n my-app curl -c curl  -- curl -s httpbin:8000/get -H "x-force-authorized: true"

Додавання заголовків до відповіді

Запустивши той самий запит, але показавши лише заголовок, ви побачите заголовок відповіді, доданий під час перевірки Authz x-add-custom-response-header: added:

$ kubectl exec -n my-app curl -c curl  -- curl -s -I httpbin:8000/get -H "x-force-authorized: true"

Обмін даними між фільтрами

Останнім кроком є передача даних іншим фільтрам Envoy за допомогою dynamic_metadata. Це корисно, коли потрібно передати дані іншому фільтру ext_authz у ланцюзі або вивести їх у логи застосунку.

Metadata

Для цього перегляньте формат журналу доступу, який ви налаштували раніше:

[...]
    accessLogFormat: |
      [OPA DEMO] my-new-dynamic-metadata: "%DYNAMIC_METADATA(envoy.filters.http.ext_authz)%"
[...]

DYNAMIC_METADATA — це зарезервоване ключове слово для доступу до обʼєкта метаданих. Далі вказується назва фільтра, до якого ви хочете звернутися. У вашому випадку, імʼя envoy.filters.http.ext_authz автоматично створюється Istio. Ви можете перевірити це, вивівши конфігурацію Envoy:

$ istioctl pc all deploy/httpbin -n my-app -oyaml | grep envoy.filters.http.ext_authz

Ви побачите конфігурації для фільтра.

Тепер перевіримо динамічні метадані. У розширеному правилі ви створюєте новий запис метаданих: {"my-new-metadata": "my-new-value"}.

Виконайте запит і перевірте логи застосунку:

$ kubectl exec -n my-app curl -c curl  -- curl -s -I httpbin:8000/get -H "x-force-authorized: true"
$ kubectl logs -n my-app deploy/httpbin -c istio-proxy --tail 1

У виводі ви побачите нові атрибути, налаштовані за допомогою правил OPA Rego:

[...]
 my-new-dynamic-metadata: "{"my-new-metadata":"my-new-value","decision_id":"8a6d5359-142c-4431-96cd-d683801e889f","ext_authz_duration":7}"
[...]

Підсумки

У цьому посібнику ми показали, як інтегрувати Istio та OPA для впровадження політик для простого мікросервісного застосунку. Ми також продемонстрували, як використовувати Rego для модифікації атрибутів запиту та відповіді. Це основний приклад для побудови системи політик на платформі, яку можуть використовувати всі команди розробників застосунків.

Share this post