Аутентификация в Kubernetes через Gitlab’овские JWT токены

от автора

Введение

Зачем?

Представим ситуацию, что мы деплоим по push-модели. В качестве платформы для запуска деплоя у нас используется Gitlab: в нём настроен пайплайн и джобы, разворачивающие приложения в разные окружения в Kubernetes

Какой бы инструмент мы не использовали (kubectl, helm), для манипуляций с ресурсами API нам в любом случае будет необходимо аутентифицироваться при выполнении запросов к Kubernetes. Для этого в запросе надо передать данные для аутентификации, будь то токен или сертификат. И тут возникает несколько вопросов:

  1. Где хранить эти креды?

    Хранить креды от кластера можно, например, в Gitlab CI/CD Variables и подставлять в джобу деплоя, но тогда потенциально все пользователи будут деплоить с одними и теми же доступами

  2. Как сделать так, чтобы у каждого пользователя были свои данные для доступа в кластер?

    Можно было бы вручную запускать джобы деплоя и в параметры каждый раз подставлять свои аутентификационные данные, но, очевидно, такой подход неудобен и подходит далеко не всем

А что если сделать так, чтобы в качестве провайдера аутентификационных данных для Kubernetes выступал сам Gitlab? Тогда не надо было бы нигде хранить креды, и каждый пользователь мог бы аутентифицироваться в кубере под своей учёткой при запуске деплоя

Знакомьтесь. Gitlab ID Tokens

В версии Gitlab 15.7 появилась возможность прямо в джобе динамически создавать короткоживущие JWT токены, выпускаемые на имя того, кто запустил джобу

Всё, что нужно сделать — это дать название переменной с токеном и описать в поле aud (audience) имя потенциального получателя токена (сервиса, которому токен будет отправляться)

job_with_id_tokens-job:   id_tokens:     MY_JWT_TOKEN:       aud: https://vault.example.com   script:     - echo $($MY_JWT_TOKEN | base64 -w0) 

Пример полученного токена

{   "namespace_id": "72",   "namespace_path": "my-group",   "project_id": "20",   "project_path": "my-group/my-project",   "user_id": "1",   "user_login": "sample-user",   "user_email": "sample-user@example.com",   "user_identities": [       {"provider": "github", "extern_uid": "2435223452345"},       {"provider": "bitbucket", "extern_uid": "john.smith"},   ],   "pipeline_id": "574",   "pipeline_source": "push",   "job_id": "302",   "ref": "feature-branch-1",   "ref_type": "branch",   "ref_path": "refs/heads/feature-branch-1",   "ref_protected": "false",   "environment": "test-environment2",   "environment_protected": "false",   "deployment_tier": "testing",   "environment_action": "start",   "runner_id": 1,   "runner_environment": "self-hosted",   "sha": "714a629c0b401fdce83e847fc9589983fc6f46bc",   "project_visibility": "public",   "ci_config_ref_uri": "gitlab.example.com/my-group/my-project//.gitlab-ci.yml@refs/heads/main",   "ci_config_sha": "714a629c0b401fdce83e847fc9589983fc6f46bc",   "jti": "235b3a54-b797-45c7-ae9a-f72d7bc6ef5b",   "iss": "https://gitlab.example.com",   "iat": 1681395193,   "nbf": 1681395188,   "exp": 1681398793,   "sub": "project_path:my-group/my-project:ref_type:branch:ref:feature-branch-1",   "aud": "https://vault.example.com" } 

Сам токен содержит множество полей, но нас в первую очередь интересует некий уникальный идентификатор, по которому можно однозначно определить пользователя. Для этого отлично подходит поле user_email, которое содержит, как это не удивительно, пользовательский email

Отлично. Теперь что с этим можно сделать на стороне Kubernetes?

Доступ в Kubernetes через OIDC токены

Официальная документация k8s говорит нам о том, что кубовый API Server умеет работать со сторонними JWT токенами, а именно: проверять их подпись и вытаскивать из определённых полей имя пользователя и названия групп для того, чтобы матчить их с subjects, заданными в RBAC, для дальнейшей авторизации

Всё, что для этого нужно, это передать бинарнику kube-api-server дополнительные параметры при запуске, в которых мы укажем

  • URL того, кто будет выпускать токены (и откуда забирать публичные ключи для проверки подписи)

  • ID того, кто является получателем токена (тот самый aud)

  • Из какого поля (claim) вытаскивать имя пользователя и названия групп

  • Дополнительные параметры, типа уникального префикса для имени пользователя (чтобы не перемешивать с пользователями, аутентифицирующимися другими способами) и CA файла self-hosted инстанса Gitlab

Что же, пришло время проверить, как это всё будет работать на практике

Аутентифицируемся в Kubernetes через Gitlab’овские JWT токены

Чисто для теста в рамках этой статьи представим, что у нас используется:

  • Gitlab.com и его бесплатные шаренные раннеры

  • Свой Kubernetes кластер с публичным белым IP и с открытым всему миру 6443 портом kube-api-server

Нам необходимо сделать так, чтобы:

  1. Можно было аутентифицироваться в кластере по пользовательскому JWT токену, полученному от Gitlab

  2. Доступы к ресурсам были те, которые прописаны в RBAC для пользователя

Настройка OIDC на kube-api-server

Тут всё просто. Передаём kube-api-server следующие параметры запуска

--oidc-issuer-url="https://gitlab.com" --oidc-client-id="a-k8s-01" --oidc-username-claim="user_email" --oidc-username-prefix="oidc:" 
  • Токены выпускает https://gitlab.com (поле iss в токене)

  • Токены предназначены для a-k8s-01 (поле aud)

  • Имя пользователя берём из поля user_email

  • В RBAC мы будем использовать префикс oidc: перед именем пользователя

Создание RBAC

Проверять будем на кластерной роли с какими-нибудь простыми разрешениями (получение списка неймспейсов)

apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata:   name: maintainer rules: - apiGroups:   - ''   resources:   - 'namespaces'   verbs:   - 'get'   - 'list' --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata:   name: maintainer roleRef:   apiGroup: rbac.authorization.k8s.io   kind: ClusterRole   name: maintainer subjects: - apiGroup: rbac.authorization.k8s.io   kind: User   name: oidc:user@example.com 

Создание тестовой джобы в Gitlab

Тут тоже всё просто:

  • Зададим ID Token GITLAB_K8S_JWT_TOKEN с получателем (aud) a-k8s-01

  • Для kubectl создадим конфиг, в который, в том числе, будет добавлен наш динамически создаваемый GITLAB_K8S_JWT_TOKEN

  • Попробуем произвести какие-нибудь манипуляции с ресурсами кластера

stages:   - deploy  kubectl_deploy:   stage: deploy   id_tokens:     GITLAB_K8S_JWT_TOKEN:       aud: a-k8s-01   image:     name: bitnami/kubectl:1.28     entrypoint:       - ""   script:     - |       cat << EOF > /tmp/ca.pem       <CA_CERTIFICATE_CONTENT>       EOF     - API_SERVER="<api_server_url>"     - |       kubectl config set-cluster cluster --server="${API_SERVER}" --certificate-authority=/tmp/ca.pem       kubectl config set-credentials user --token="${GITLAB_K8S_JWT_TOKEN}"       kubectl config set-context context --cluster=cluster --user=user       kubectl config use-context context     - kubectl get ns     - kubectl get svc 

Три. Два. Один. Запуск

Как и ожидалось: неймспейсы получены, к сервисам доступа нет, а значит всё сработало точно так, как нам нужно

Теперь можно продумывать ролевую модель, нарезать доступы по неймспейсам, ограничивать деплои в прод только для ответственных инженеров и прочее, но это уже другая история…


ссылка на оригинал статьи https://habr.com/ru/articles/783586/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *