6 мин на чтение

Многие Devops инженеры, впервые устанавливая kubernetes (обычно minikube или чтонибудь в облаках), и заходя на dashboard впадают в ступор, видя приглашение для ввода: dashboard Так, ну с конфигом понятно, думает инженер - его мне сгенерил сам кубер при установке, а что за токен ? Где нормальные привычные логин и пароль ?

Тут будет лирическое отсупление и поговорим мы немного об аутентификации в Kubernetes. Точнее аж два отступления. Сперва напомню, что Аутентификация и Авторизация - это разные понятия. Аутентификация - это процесс проверки подлинности пользователей, а Авторизация - это определение прав доступа (сюда как раз относится RBAC). Так вот, Аутентификация в кубере, если кратко и на пальцах, устроена следующим образом: Все юзеры кубера делятся на два типа: обычные человеки и сервисы.

Обычными пользователями обычно (но не всегда) должен управлять какой-то внешний сервис типа Службы каталогов, который хранит информацию о юзерах, выдает секретные ключи и т.д. Но можно обойтись и без внешнего сервиса. У кубера есть свой центр сертификации, и если юзеру выдать подписаный этим центром сертификат X509, то при Аутентификации с таким сертификатом кубер вычитывает из этого сертификата имя пользователя и считает его валидным, и дальше обычно RBAC уже определяет что может делать пользователь в кластере. Еще можно создать статичный файл с токенами и скормить kube-api серверу.

Сервисные учетные записи управляются kubernetes api, создаются автоматически или вручную, привязаны к определенным namespaces, и хранят креды в секретах. Сервисная учетка создается командой kubectl create serviceaccount jenkins а токен для нее либо создается кратковременный командой kubectl create token jenkins либо длительный токен создается вручную с помощью объекта Secret типа kubernetes.io/service-account-token Тут мы и сталкиваемся с JWT токеном. Его выдает команда создания токена. Так что же это такое ?

JWT токен - это ключ доступа, стандартизованного в 2015 году. jwt token Аббревиатура расшифровываетя как Json Web Token. JWT состоит из трех частей, разделенных точками:

  • Header (заголовок): Содержит тип токена (обычно “JWT”) и тип алгоритма подписи (например, HMAC SHA256 или RSA).
  • Payload (нагрузка): Это самая важная часть токена, содержащая утверждения о пользователе или субъекте (claims), такие как идентификатор пользователя, срок действия токена, роли пользователя и другие пользовательские данные. Полезная нагрузка может быть закодирована в формате JSON.
  • Signature (подпись): Подпись состоит из заголовка, полезной нагрузки и секретного ключа, который используется для создания подписи. Подпись используется для проверки целостности токена и подтверждения его аутентичности.

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

Давайте посмотрим на реальный кратковременный токен, созданый командой kubectl -n kubernetes-dashboard create token kubernetes-dashboard --duration 7000h Я сразу раскодировал его, для удобства просмотра:

{
  "alg": "RS256",
  "kid": "rDSeVpZWDMwMTh9WhH2pnRnENIEIwT2nDMggLMhg9hE"
}

Эта часть содержит метаданные о том, как токен был подписан. В данном случае, используется алгоритм подписи RS256 (RSA с SHA-256) и ключ идентификатора (kid), который может использоваться для поиска соответствующего открытого ключа для проверки подписи.

PAYLOAD
{
  "aud": [
    "https://kubernetes.default.svc.cluster.local"
  ],
  "exp": 1740052294,
  "iat": 1714852294,
  "iss": "https://kubernetes.default.svc.cluster.local",
  "kubernetes.io": {
    "namespace": "kubernetes-dashboard",
    "serviceaccount": {
      "name": "kubernetes-dashboard",
      "uid": "c9e6e6b2-701e-4339-b72a-03f3e8e3ed7b"
    }
  },
  "nbf": 1714852294,
  "sub": "system:serviceaccount:kubernetes-dashboard:kubernetes-dashboard"
}

Эта часть содержит утверждения (claims) о пользователе или субъекте, а также дополнительные метаданные. В данном примере, iss (Issuer) указывает на URL сервера, который создал токен, sub (Subject) указывает на идентификатор субъекта, exp (Expiration Time) указывает на время истечения токена, а iat (Issued At) указывает на время создания токена. — Теперь создадим долгосрочный токен с помощью секрета.

% kubectl create  serviceaccount build-robot
serviceaccount/build-robot created
% kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: build-robot-secret
  annotations:
    kubernetes.io/service-account.name: build-robot
type: kubernetes.io/service-account-token
EOF
secret/build-robot-secret created
% kubectl describe secrets/build-robot-secret
Name:         build-robot-secret
Namespace:    default
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: build-robot
              kubernetes.io/service-account.uid: 606587a2-89e8-4750-a458-21ec7d4e9e3c

Type:  kubernetes.io/service-account-token

Data
====
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6InJEU2VWcFpXRE13TVRoOVdoSDJwblJuRU5JRUl3VDJuRE1nZ0xNaGc5aEUifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImJ1aWxkLXJvYm90LXNlY3JldCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJidWlsZC1yb2JvdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjYwNjU4N2EyLTg5ZTgtNDc1MC1hNDU4LTIxZWM3ZDRlOWUzYyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmJ1aWxkLXJvYm90In0.yzxwwKKsDom8ERVJD4mwVJZOA6mz79xu7yThR0t-wR6olOEwUH0IFrpC8rQcodHFqhdJiel8T7t_JGRB1ly7brOhama6kitfmSZlJLiJjYWeSxGAEtQvO1AGOMfUq3kBEZn3VABJo9LibFMlY5RZdexNtNMHQCCikx2eE4yLVTsb97vOh9XfZnAAZKkyH_6dSgYOjgpNCLft06LEr73ZadwtSYjuy_ImnnrANF9AjBrRE0vXEDff0xY02JCBfo_NT1sD2LLFQYW8NkEAsY0frV_g_J8MtOwAUOPY7b4sRplUfsUnQw0l3g5ml3R3iJbT_tVqqqbJM0Z9i5KeCs-9-Q
ca.crt:     1111 bytes
namespace:  7 bytes

Теперь берем токен, и либо по отдельности раскодируем Header и Payload или идем на jwt.io и раскодируем целиком (ни в коем случае не раскодируйте там ключи от Production!)

HEADER
{
  "alg": "RS256",
  "kid": "rDSeVpZWDMwMTh9WhH2pnRnENIEIwT2nDMggLMhg9hE"
}
PAYLOAD
{
  "iss": "kubernetes/serviceaccount",
  "kubernetes.io/serviceaccount/namespace": "default",
  "kubernetes.io/serviceaccount/secret.name": "build-robot-secret",
  "kubernetes.io/serviceaccount/service-account.name": "build-robot",
  "kubernetes.io/serviceaccount/service-account.uid": "606587a2-89e8-4750-a458-21ec7d4e9e3c",
  "sub": "system:serviceaccount:default:build-robot"
}

Как видим, токен в отличие от своего предшественника не содержит информацию о сроке действия.

OpenID Connect

Уделим немного внимания также сторонним сервисам аутентификации, распространенных при использовании kubernetes. Много где используются службы каталогов, такие как ldap. И для того чтобы Kubernetes научить аутентифицировать пользователей в этих внешних службах, нужен посредник. Сам Kubernetes умеет работать с OpenID Connect протоколом, который является расширением OAuth2, и его необходимо настроить для связи с нашим посредником, который и будет заниматься аутентификацией пользователя во внешней службе и выдачей токенов. Чаще всего такими посредниками для ldap являются dex или keycloak. Вообще, внешние сервисы аутентификации заслуживают отдельной статьи, и я не буду углубляться в подробности. Приведу общую схему работы с посредником OIDC. oidc Вот краткое описание работы OIDC в Kubernetes:

  • Настройка OIDC провайдера: Администратор настраивает OIDC провайдера (например, Google, Azure AD) для аутентификации пользователей в Kubernetes.
  • Интеграция с API сервером Kubernetes: API сервер Kubernetes (kube-apiserver) настраивается для использования OIDC провайдера для аутентификации запросов от пользователей.
  • Получение токена аутентификации: Пользователь аутентифицируется через OIDC провайдера и получает токен аутентификации (ID токен) в формате JWT.
  • Проверка подлинности токена: API сервер Kubernetes проверяет подлинность токена аутентификации с помощью открытого ключа OIDC провайдера.
  • Извлечение утверждений (claims): После успешной проверки подлинности токена, API сервер Kubernetes извлекает утверждения (claims) из токена, такие как идентификатор пользователя, роли и другие атрибуты.
  • Авторизация доступа: На основе извлеченных утверждений (claims), Kubernetes применяет механизмы авторизации (например, RBAC) для определения прав доступа пользователя к ресурсам в кластере.

Давайте для примера рассмотрим скрипт, который после авторизации в Dex выдает сервис Gangway. Этот скрипт настраивает кластер Kubernetes для аутентификации через OIDC провайдера, используя предоставленные учетные данные (client ID, client secret, refresh token и ID token). Кроме того, создается контекст, который позволяет использовать учетные данные для работы с кластером.

  1. Создание файла ca.pem с корневым сертификатом для проверки сертификатов сервера Kubernetes:
    echo "-----BEGIN CERTIFICATE-----
    MIIC/jCCAeagAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
    blablahblahndsjakndjasndjak
    0a4=
    -----END CERTIFICATE-----
    " \ > ca.pem
    
  2. Настройка кластера Kubernetes Production:
    kubectl config set-cluster Production --server=https://kuber-prod.company.local:8383 --certificate-authority=ca.pem --embed-certs
    
  3. Настройка учетных данных (credentials) для аутентификации OIDC:
    kubectl config set-credentials admin@company.local  \
     --auth-provider=oidc  \
     --auth-provider-arg='idp-issuer-url=https://kuber-prod-auth.company.local'  \
     --auth-provider-arg='client-id=oidc-auth-client'  \
     --auth-provider-arg='client-secret=nfdjsanfjdsanfjasknfjdsaknfjasndjfk' \
     --auth-provider-arg='refresh-token=ndnsjaNJAKncsjakcndjsncjsdkncjksdncjdsncjksncjdsk' \
     --auth-provider-arg='id-token=eyJhbGciOiJIUzUxMiIsImtpZCI6IjU2ZmM3Zjg0YmQ2OTdmMGRkNTRiNTM2Njc5NWFiODBiMzU0NTZlMmEifQ.eyJpc3MiOiJodHRwczovL2t1YmVyLXByb2QtYXV0aC5jb21wYW55LmxvY2FsIiwic3ViIjoiU2pkc25hamRrbnNhamRrbmphc25kamthbmpza2EiLCJhdWQiOiJvaWRjLWF1dGgtY2xpZW50IiwiZXhwIjoxNzE1MDE3NTY5LCJpYXQiOjE3MTQ5MzExNjksImF0X2hhc2giOiJ6Y3lFUkNFWjhtM0ZJdVdhb0d6dGRRIiwiY19oYXNoIjoidEZ6c3lFUk5nOVNZU3ZMOTZyeEN6ZyIsImVtYWlsIjoiYWRtaW4iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZ3JvdXBzIjpbIlVzZXJzIiwiQWRtaW5zIl0sIm5hbWUiOiJTdXBlciBBZG1pbiJ9.ttGyo4aAEi5V1AG7E8v'
    
  4. Настройка контекста (context) для кластера:
    kubectl config set-context Production--cluster=Production --user=admin@company.local
    
  5. Использование настроенного контекста для работы с кластером:
    kubectl config use-context Production
    
  6. Удаление временного файла ca.pem:
    rm ca.pem
    

Тут как раз в шаге 3 фигурирует id-token, который выдает Dex, и который является JWT. Давайте расшифруем и посмотрим внутрь:

HEADER
{
  "alg": "HS512",
  "kid": "56fc7f84bd697f0dd54b5366795ab80b35456e2a"
}

в заголовке ничего необычного, алгоритм и ключ иденификатора

PAYLOAD
{
  "iss": "https://kuber-prod-auth.company.local",
  "sub": "Sjdsnajdknsajdknjasndjkanjska",
  "aud": "oidc-auth-client",
  "exp": 1715017569,
  "iat": 1714931169,
  "at_hash": "zcyERCEZ8m3FIuWaoGztdQ",
  "c_hash": "tFzsyERNg9SYSvL96rxCzg",
  "email": "admin",
  "email_verified": true,
  "groups": [
    "Users",
    "Admins"
  ],
  "name": "Super Admin"
}

Разберем подробно:

  • iss (Issuer): Идентификатор провайдера, который выдал токен. В данном случае, https://kuber-prod-auth.company.local.
  • sub (Subject): Уникальный идентификатор пользователя. В данном случае, Sjdsnajdknsajdknjasndjkanjska.
  • aud (Audience): Аудитория, для которой токен предназначен. В данном случае, oidc-auth-client.
  • exp (Expiration Time): Время, после которого токен становится недействительным (в формате Unix time).
  • iat (Issued At): Время, когда токен был выдан (в формате Unix time).
  • at_hash (Access Token Hash): Хеш-значение для access token.
  • c_hash (Authorization Code Hash): Хеш-значение для authorization code.
  • email: Email пользователя, в данном случае admin.
  • email_verified: Подтверждение верности email.
  • groups: Группы, к которым принадлежит пользователь.
  • name: Имя пользователя, в данном случае Super Admin.

Данный токен будет добавляться к запросу каждый раз при обращении к серверу kubernetes-api.

Теперь вернемся назад, и обратим внимание, что Dex также выдал и refresh-token. Это специальный токен, который позволяет продлить основной токен, если он уже протух. Kubectl занимается этим автоматически. При продлении основного токена в ответ будет выдан новый основой токен, а также будет выдан новый refresh токен. Это называется ротацией токенов.

JWT прикольная штука, которая используется не только в Kubernetes, а во многих веб-приложениях и api.

Дата изменения:

Оставить комментарий