JWT в контексте kubernetes
Многие Devops инженеры, впервые устанавливая kubernetes (обычно minikube или чтонибудь в облаках), и заходя на 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 году.
Аббревиатура расшифровываетя как Json Web Token. JWT состоит из трех частей, разделенных точками:
- Header (заголовок): Содержит тип токена (обычно “JWT”) и тип алгоритма подписи (например, HMAC SHA256 или RSA).
- Payload (нагрузка): Это самая важная часть токена, содержащая утверждения о пользователе или субъекте (claims), такие как идентификатор пользователя, срок действия токена, роли пользователя и другие пользовательские данные. Полезная нагрузка может быть закодирована в формате JSON.
- Signature (подпись): Подпись состоит из заголовка, полезной нагрузки и секретного ключа, который используется для создания подписи. Подпись используется для проверки целостности токена и подтверждения его аутентичности.
JWT используется для аутентификации и передачи информации между двумя сторонами в формате, который может быть легко проверен и декодирован.
Давайте посмотрим на реальный кратковременный токен, созданый командой kubectl -n kubernetes-dashboard create token kubernetes-dashboard --duration 7000h
Я сразу раскодировал его, для удобства просмотра:
HEADER
{
"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 в 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). Кроме того, создается контекст, который позволяет использовать учетные данные для работы с кластером.
- Создание файла ca.pem с корневым сертификатом для проверки сертификатов сервера Kubernetes:
echo "-----BEGIN CERTIFICATE----- MIIC/jCCAeagAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl blablahblahndsjakndjasndjak 0a4= -----END CERTIFICATE----- " \ > ca.pem
- Настройка кластера Kubernetes Production:
kubectl config set-cluster Production --server=https://kuber-prod.company.local:8383 --certificate-authority=ca.pem --embed-certs
- Настройка учетных данных (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'
- Настройка контекста (context) для кластера:
kubectl config set-context Production--cluster=Production --user=admin@company.local
- Использование настроенного контекста для работы с кластером:
kubectl config use-context Production
- Удаление временного файла 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.
Оставить комментарий