Gatekeeper с Istio

Те, кто использует Istio для управления микросервисами, не должны быть слишком удивлены его суперспособностями. Работая с различными командами и организациями в рамках предприятия, я должен признать, что Istio иногда может быть очень сложным. К счастью, Google, Solo и многие другие компании продолжают внедрять инновации в этой области, чтобы вы, как конечный пользователь, могли извлечь из этого пользу.

Согласно ZTA, мы никогда не должны доверять, всегда проверяйте.

В этой статье я хочу затронуть вопрос о том, как использовать PaC (он же Policy-As-Code) для обеспечения правильной реализации Istio (чтобы было понятно, что нет абсолютной правильности или неправильности, но следуя лучшим практикам, вы достигаете правильности на данный момент), например, Protocol Selection. По умолчанию Istio может автоматически определять HTTP(/2) трафик, иначе он будет рассматриваться как обычный TCP трафик. Как учит нас Zen of Python, явное лучше неявного. Мы всегда должны стремиться к удобочитаемости и сопровождаемости кода. Поэтому давайте обеспечим соблюдение этого правила во время проектирования и во время выполнения.

Эта статья не предназначена для глубокого погружения в Rego, и я оставлю это на усмотрение читателей.

Согласно Istio Explicit Protocol Selection:

Это может быть настроено двумя способами:

По имени порта: name: <protocol>[-<suffix>].
В Kubernetes 1.18+, по полю appProtocol: appProtocol: <protocol>.

Давайте разберем это.

  • порт: имя

Можно, конечно, использовать встроенную функцию re_match, но это может усложнить шаблон ограничений Gatekeeper’s ConstraintTemplate, о котором мы поговорим чуть позже. Вместо этого мы можем просто split имя_порта на -, и проверить его принадлежность.

protocol := split(port_name, "-")[0]
protocol in protocols
Вход в полноэкранный режим Выход из полноэкранного режима
  • порт: appProtocol

Есть несколько способов проверить принадлежность порта:

#1 Unification, which is different from `:=` and `==` in Rego.

protocol = protocols[_]

#2 `in`, which requires to `import future.keywords`

protocol in protocols

#3 Set

protocol_set := { p | p := input.parameters.protocols[_] }
protocol_set[protocol]

Войти в полноэкранный режим Выйти из полноэкранного режима

Если следовать руководству Styra’s Rego Style Guide, то вариант №2 будет предпочтительным.

Есть одна маленькая тонкость, о которой мы еще не говорили. Когда у сервиса есть и имя порта, и порт appProtocol, последний имеет приоритет в Istio. Итак, как мы выражаем это в Rego?

В Rego внутри правила подразумевается условие AND, что означает, что все тело правила должно быть True, чтобы это правило стало True. Условие OR достигается несколькими Правилами с одним и тем же именем Правила.

# when port.appProtocl exists just use it and ignore port name altogether.
_is_valid(port, protocols) {
  port.appProtocol

  _match_app_protocol(port.appProtocol, protocols)
}

# when port.appProtocol doesn't exit port name has to exist and match the protocols we specified.
_is_valid(port, protocols) {
  not port.appProtocol
  port.name

  _match_port_name(port.name, protocols)
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Давайте соберем все вместе:

package istio.security.protocolselection

import future.keywords

violation[{"msg": msg}] {
  protocols := input.parameters.protocols

  some port in input.review.object.spec.ports
  not _is_valid(port, protocols)

  msg := sprintf("port: %v name or appProtocol is invalid", [port])
}

_is_valid(port, protocols) {
  port.appProtocol

  _match_app_protocol(port.appProtocol, protocols)
}

_is_valid(port, protocols) {
  not port.appProtocol
  port.name

  _match_port_name(port.name, protocols)
}

_match_app_protocol(protocol, protocols) {
  protocol in protocols
}

_match_port_name(port_name, protocols) {
  protocol := split(port_name, "-")[0]

  protocol in protocols
}
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь самое сложное решено, и давайте обратимся к OPA Gatekeeper. Gatekeeper использует OPA Constraint Framework для описания и применения политики. На данный момент есть в основном 3 части, на которые мы должны обратить внимание:

  1. ContraintTemplate: описывает как Rego, который обеспечивает выполнение ограничения, так и схему ограничения.
  2. Constraint: описывает, что и как должно быть реализовано в ContraintTemplate.
  3. Config: описывает поведение для определенных процессов.

1: ContraintTemplate

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  annotations:
    description: Explicit protocol selection either by name or appProtocol
  name: istioexplicitprotocolselection
spec:
  crd:
    spec:
      names:
        kind: IstioExplicitProtocolSelection
      validation:
        openAPIV3Schema:
          type: object
          properties:
            prefixes:
              type: string
            protocols:
              type: array
              items:
                type: string
  targets:
  - target: admission.k8s.gatekeeper.sh
    rego: |-
      package istio.security.protocolselection

      import future.keywords

      violation[{"msg": msg}] {
        protocols := input.parameters.protocols

        some port in input.review.object.spec.ports
        not _is_valid(port, protocols)

        msg := sprintf("port: %v name or appProtocol is invalid", [port])
      }

      _is_valid(port, protocols) {
        port.appProtocol

        _match_app_protocol(port.appProtocol, protocols)
      }

      _is_valid(port, protocols) {
        not port.appProtocol
        port.name

        _match_port_name(port.name, protocols)
      }

      _match_app_protocol(protocol, protocols) {
        protocol in protocols
      }

      _match_port_name(port_name, protocols) {
        protocol := split(port_name, "-")[0]

        protocol in protocols
      }
Вход в полноэкранный режим Выйти из полноэкранного режима

2: Ограничение

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: IstioExplicitProtocolSelection
metadata:
  name: explicitprotocolselection
spec:
  enforcementAction: deny
  match:
    kinds:
    - apiGroups:
      - ""
      kinds:
      - Service
  parameters:
    protocols:
    - http
    - https
    - http2
    - grpc
    - grpc-web
    - tcp
    - tls
Войти в полноэкранный режим Выход из полноэкранного режима

3: Конфигурация

Мы будем использовать его для игнорирования всех пространств имен, к которым мы не хотим применять политику.

apiVersion: config.gatekeeper.sh/v1alpha1
kind: Config
metadata:
  name: config
  namespace: "gatekeeper-system"
spec:
  match:
    - excludedNamespaces: ["kube-*", "istio-*"]
      processes: ["*"]
Войти в полноэкранный режим Выйти из полноэкранного режима

Вуаля!

OPA Gatekeeper имеет больше, чем я только что показал вам здесь. Я оставлю это на ваше усмотрение: Gator, Mutation, Audit, Replication и т.д.

Ссылка: https://github.com/feiyao/gatekeeper-istio

Оцените статью
devanswers.ru
Добавить комментарий