
Введение
Зачем?
Представим ситуацию, что мы деплоим по push-модели. В качестве платформы для запуска деплоя у нас используется Gitlab: в нём настроен пайплайн и джобы, разворачивающие приложения в разные окружения в Kubernetes
Какой бы инструмент мы не использовали (kubectl, helm), для манипуляций с ресурсами API нам в любом случае будет необходимо аутентифицироваться при выполнении запросов к Kubernetes. Для этого в запросе надо передать данные для аутентификации, будь то токен или сертификат. И тут возникает несколько вопросов:
-
Где хранить эти креды?
Хранить креды от кластера можно, например, в Gitlab CI/CD Variables и подставлять в джобу деплоя, но тогда потенциально все пользователи будут деплоить с одними и теми же доступами
-
Как сделать так, чтобы у каждого пользователя были свои данные для доступа в кластер?
Можно было бы вручную запускать джобы деплоя и в параметры каждый раз подставлять свои аутентификационные данные, но, очевидно, такой подход неудобен и подходит далеко не всем
А что если сделать так, чтобы в качестве провайдера аутентификационных данных для 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
Нам необходимо сделать так, чтобы:
-
Можно было аутентифицироваться в кластере по пользовательскому JWT токену, полученному от Gitlab
-
Доступы к ресурсам были те, которые прописаны в 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/
Добавить комментарий