Крупнейшее изменение системы аутентификации в K8s за последние годы: новый KEP от «Фланта», Google и Microsoft

KEP 3331 (Structured Authentication Config) — это самое крупное обновление в системе аутентификации Kubernetes за 6 последних лет, в котором учитываются все просьбы пользователей. Предварительно его альфа-версия должна выйти в составе релиза Kubernetes 1.28 — сам KEP уже смерджлили, но планы могут немного подкорректироваться.

В проработке фичи вместе с инженерами из Google и Microsoft активно участвовал наш Platform Lead Максим Набоких, который входит в состав Kubernetes sig-auth и является основным мейнтейнером Dex, свободного провайдера OpenID Connect.

Чтобы понять, в чем его преимущества, посмотрим, какими методами решается вопрос аутентификации пользователей сейчас:

  • Static Token File. Его проблема в том, что это статический файл и токены хранятся в нем в открытом виде. Такая механика не пройдет CIS Benchmark.

  • X509 Client Certs. Это хороший и безопасный способ. Однако сейчас сертификаты нельзя отзывать. И если мы выпустили сертификат, придется либо ждать, когда его срок действия истечет, либо запускать сложный механизм замены сертификатов во всем Kubernetes-кластере. Для больших окружений, где много пользователей и K8s-кластеров, такой вариант не подходит.

  • OpenID Connect Tokens, Authenticating Proxy, Webhook Token Authentication — это уже очень неплохие варианты, и у каждого есть свои плюсы. Однако с появлением Structured Authentication Config ни один из этих вариантов может не понадобиться.

Итак, какие же преимущества дает Structured Authentication Config:

  1. Можно использовать не только OIDC-токены, но и любой ID JWT.

  2. Появляется возможность динамически изменять конфигурацию. В качестве флага передается путь до файла, в котором прописан Structured Authentication Config, а Kubernetes API-сервер автоматически обновляет настройки в случае изменений этого файла.

  3. Можно одновременно подключать несколько провайдеров аутентификации (например, Okta, Keycloak, Gitlab) без использования инструментов вроде Dex.

  4. Можно использовать CEL (Common Expression Language) для определения соответствия claim«ов токена атрибутам пользователя в Kubernetes (имя пользователя, группы).

Как все это будет выглядеть 

Пример Structured Authentication Config:

apiVersion: apiserver.config.k8s.io/v1alpha1
kind: AuthenticationConfiguration
jwt:
- issuer:
    url: https://example.com
    clientIDs:
    - my-app
  claimMappings: {...}
  claimValidationRules: [...]
  userInfoValidationRules: [...]

Файл конфигурации содержит несколько провайдеров, которые в виде массивов передаются в JWT-поле. А с помощью claimMappings можно реализовать правила извлечения информации о пользователях:  

claimMappings:
  username:
    expression: 'claims.username + ":external-user"'
  groups:
    expression: 'claims.roles.split(",")'
  uid:
    claim: 'sub'
  extra:
  - key: '"client_name"'
    value: 'claims.aud'

В этом примере с помощью CEL мы прописали два правила:

  1. Роли пользователя, которые передаются в виде одной строки, автоматически дробятся на элементы массива по разделителю — запятой (claims.roles.split(",")).

  2. К имени каждого внешнего пользователя добавляется преффикс :external-user. Если используется не RBAC, а своя система авторизации и распределения ролей, можно передавать такие атрибуты в авторизационные вебхуки. Очень удобно. 

9fdba707b200c3145692e25e668e4b96.png

Теперь коснемся правил валидации для аутентификации. В документации Kubernetes существует следующее примечание:  

Префикс system: зарезервирован для внутреннего использования в Kubernetes, вы должны убедиться, что у вас случайно не используются пользователи или группы, имена которых начинаются с system:

Допустим, у нас есть LDAP (его веб-интерфейс — на скриншоте) и группа system:masters, которую мы указали для нового пользователя при регистрации. 

9637b6a4660e65e6a060da868f38f0ff.png

Если теперь мы залогинимся через Dex, то в списке групп у этого пользователя и правда появится system:masters, а сам пользователь станет суперадмином кластера и сможет делать все что угодно. Вряд ли кто-то осознанно планировал устраивать нечто подобное, но сценарий вполне реалистичный.

{"level":"info","msg":"login successful: connector \"ldap-local\", username=\"kaksenov\", preferred_username=\"\", email=\"konstantin.aksenov@flant.com\", groups=[\"system:masters\" \"developers:maintainers\"]","time":"2023-06-01T19:12:09Z"}

ATTRIBUTE   VALUE
Username    konstantin.aksenov@flant.com
Groups      [system:masters developers:maintainers system:authenticated]

До появления Structured Authentication Config можно было использовать следующие методы валидации при аутентификации:

  • Задать префикс через параметр --oidc-groups-prefix.

  • Использовать фильтры — например, в Dex у коннекторов есть фильтр для групп.

connectors:
  - type: gitlab
    id: gitlab
    name: GitLab
    config:
      # If `groups` is provided, this acts as a whitelist - only the user's GitLab groups that are in the configured `groups` below will go into the groups claim.  Conversely, if the user is not in any of the configured `groups`, the user will not be authenticated.
      
      groups:
      - my-group

Посмотрим, как это будет работать в Structured Authentication Config. 

В SAC есть claimValidationRules. Мы можем проверить входящие claim«ы, указать в правилах, что существуют обязательные поля и поля, значение которых должно соответствовать определенным ограничениям. Таким образом, на этапе получения claim«ов из токенов мы сумеем распознать и отсеять невалидные токены.

claimValidationRules:
- claim: hd
  requiredValue: example.com
- expression: 'claims.hd == "example.com"'
  message: the hd claim must be set to example.com
- expression: 'claims.exp - claims.nbf <= 86400'
  message: total token lifetime must not exceed 24 hours

Что касается проблемы с system-группами, то в Structured Authentication Config существуют userInfoValidationRules — с их помощью мы можем указать, что ни одно имя пользователя или название группы не может начинаться с system.

userInfoValidationRules:
- rule: "!userInfo.username.startsWith('system:')"
  message: username cannot used reserved system: prefix
- rule: "userInfo.groups.all(group, !group.startsWith('system:'))"
  message: groups cannot used reserved system: prefix

Итак, подведем итог — что же есть в Structured Authentication Config:

  • У kube-apiserver появится новый флаг, в котором можно задать ссылку на файл конфигурации аутентификации.

  • Динамическое применение файла конфигурации после его редактирования.

  • Одновременное использование нескольких провайдеров OIDC.

  • Использование CEL.

  • Правила для валидации токенов.

  • Правила извлечения информации из токена и применения ее к атрибутам пользователя.

  • Конфигурация становится структурированной и можно использовать нормальное версионирование, а не запутываться в куче флагов. 

Это уже второй KEP, значительную часть которого прорабатывал «Флант» — первый появился в Kubernetes 1.27.

© Habrahabr.ru