Featured image of post Kubernetes авторизация через OIDC

Kubernetes авторизация через OIDC

Используем Google и Authentik для авторизации в Kubernetes

Введение

Для того, чтобы авторизоваться в Kubernetes через kubeconfig для доступа через kubectl, как правило, используют ServiceAccounts. Их просто проще создать, но это совсем неправильно. Токены от ServiceAccount доступны любому, кто может читать секреты в пространстве имён, где создан ServiceAccount, а значит от Вашего имени можно произвести диверсию. Небезопасненько…

Можно создать сертификат, подписать в кластере и ходить как User. Вот это уже отлично, никто не может от Вашего имени ничего сделать, потому что приватную часть мы, естественно, генерировали локально и ни с кем не делились. Как это сделать, я тут заодно напишу, раз разобрался.

Недавно, таки дошли руки попробовать Authentik, а заодно попробовал привязать к нему кластер через настройку API сервера. Оказалось, это даже проще, местами, чем возится с сертификатами для пользователя. Вот об этом всём и разскажу

Доступ через ServiceAccount

Создаём ServiceAccount.

1
kubectl create serviceaccount test

Раньше, до Kubernetes 1.22, автоматически генерировался токен, сейчас его нужно создавать отдельно. Можно сгенерировать временный.

1
kubectl create token test --duration=10m

Можно, по-старинке, получить постоянный, создав для него секрет.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: test-sa-secret
  annotations:
    kubernetes.io/service-account.name: test
type: kubernetes.io/service-account-token
EOF
SA_TOKEN="$(kubectl get -o yaml secret test-sa-secret -o jsonpath='{.data.token}' | base64 -d)"

И вот мы его уже можем прописать в kubeconfig и использовать.

1
2
3
4
kubectl config current-context | \
xargs kubectl config get-contexts --no-headers | \
awk '{print $4}' | \
xargs kubectl config set-credentials "--token=${SA_TOKEN}"

Создание пользователя

Тут придётся заморочится. Нужно создать сертификат, запрос на подпись, подписать, забрать публичную часть и вот только теперь мы приплыли. Скрипт я свой прилагаю, а описывать это всё не хочу. Горит именно о OIDC рассказать, так что воспринимайте это как приятный бонус 😄

Как пользоваться:

  1. Пишем своё имя в переменную NAME
  2. Пишем имя компании в переменную GROUP
  3. Меняем CLUSTERROLE если очень хочется
  4. Запускаем и читаем инструкцию на экране
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
NAME='john-johnson'
GROUP='asdfqwer'
CLUSTERROLE='cluster-admin'

openssl ecparam -genkey -name prime256v1 | openssl ec -out "$NAME.key"
KEY_BASE64="$(base64 -w0 "$NAME.key")"
openssl req -new -key "$NAME.key" -out "$NAME.csr" -subj "/CN=$NAME/O=$GROUP"
CSR_BASE64="$(base64 -w0 "$NAME.csr")"
rm -f "$NAME.csr" "$NAME.key"

cat <<EOF
#  ┬ ┐┐─┐┬─┐┬─┐  ┌─┐┬─┐┬─┐┬─┐┌┐┐o┌─┐┌┐┐
#  │ │└─┐├─ │┬┘  │  │┬┘├─ │─┤ │ ││ ││││
#  ┘─┘──┘┴─┘┘└┘  └─┘┘└┘┴─┘┘ ┘ ┘ ┘┘─┘┘└┘

1. Please, execute following code using kubectl that has access to the cluster (using default admin credentials, for example)

kubectl apply -f - <<EOT
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: $GROUP:$NAME
spec:
  expirationSeconds: 31536000 # 1 year
  signerName: kubernetes.io/kube-apiserver-client
  usages: ["client auth"]
  username: $NAME
  request: $CSR_BASE64
EOT
kubectl certificate approve '$GROUP:$NAME'
kubectl apply -f - <<EOT
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: user:${NAME}:${CLUSTERROLE}
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: $CLUSTERROLE
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: $NAME
EOT
kubectl get -o jsonpath='{.status.certificate}' csr '$GROUP:$NAME'
echo

2. Please, execute following code locally, on your PC, where you want to have that credentials inserted

kubectl config set "users.$GROUP:$NAME.client-key-data" "$KEY_BASE64"
kubectl config set "users.$GROUP:$NAME.client-certificate-data" "-" --set-raw-bytes=true

3. Now place certificate that has been printed during step #1 in your kubeconfig client-certificate-data for user $GROUP:$NAME marker
4. Specify $GROUP:$NAME user in the context you want to use
EOF

Google OIDC

  1. Настраиваем OAuth consent screen

  2. Создаём OAuth2 креды тип Web application, Authorized redirect URIs - в списке ниже.

    1
    2
    3
    4
    
    http://127.0.0.1:18000
    http://127.0.0.1:8000
    http://localhost:18000
    http://localhost:8000
    
  3. В итоге получили Client ID и Client Secret

Отлично, теперь нужно поставить kubectl oidc-login плагин.

После установки, пробуем, чтобы у нас всё работало.

1
2
3
4
kubectl oidc-login setup \
  --oidc-issuer-url=https://accounts.google.com \
  --oidc-client-id='YOUR CLIENT ID' \
  --oidc-client-secret='YOUR CLIENT SECRET'

Если всё в порядке, то у Вас открылся браузер, залогинились под кем-то и в консоли появилась большая красивая инструкция. Типо такой.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
authentication in progress...
Opening in existing browser session.

## 2. Verify authentication

You got a token with the following claims:

{
  "iss": "https://accounts.google.com",
  "azp": "longalphanum",
  "aud": "longalphanum",
  "sub": "longalphanum",
  "at_hash": "longalphanum",
  "nonce": "longalphanum",
  "iat": longalphanum,
  "exp": longalphanum
}

## 3. Bind a cluster role

Run the following command:

	kubectl create clusterrolebinding oidc-cluster-admin --clusterrole=cluster-admin --user='https://accounts.google.com#12345678901234567890'

## 4. Set up the Kubernetes API server

Add the following options to the kube-apiserver:

	--oidc-issuer-url=https://accounts.google.com
	--oidc-client-id=longalphanum

## 5. Set up the kubeconfig

Run the following command:

	kubectl config set-credentials oidc \
	  --exec-api-version=client.authentication.k8s.io/v1beta1 \
	  --exec-command=kubectl \
	  --exec-arg=oidc-login \
	  --exec-arg=get-token \
	  --exec-arg=--oidc-issuer-url=https://accounts.google.com \
	  --exec-arg=--oidc-client-id=longalphanum \
	  --exec-arg=--oidc-client-secret=longalphanum

## 6. Verify cluster access

Make sure you can access the Kubernetes cluster.

	kubectl --user=oidc get nodes

You can switch the default context to oidc.

	kubectl config set-context --current --user=oidc

You can share the kubeconfig to your team members for on-boarding.

Теперь просто следуем тому, что написано выше и перезапускаем API сервер разок.

Вот так мы теперь авторизуемся в браузере

Authentik

Штука отличная! Из прямых аналогов, стоит отметить Okta и Keycloak. Все 3 продукта великолепные, на Okta вообще часто равняются как на эталон. Другое либо лучше, либо хуже, но всегда по сравнению с Okta 😄

Ставил я Authentik из официального чарта. Он у меня запущен локально, а сертификат нужен. Получил certbot-ом используя DNS проверку. После этого делаем следующее.

  1. Applications > Providers

    Создаём провайдер Kubernetes типа OAuth2/OIDC

    • Client type: Confidential
    • Client ID: запоминаем
    • Client Secret: запоминаем
    • Redirect URIs: http://(127.0.0.1|localhost):1?8000(/.*)?
  2. Application > Applications

    Создаем приложение Kubernetes и выбираем провайдером то, что создали в пункте 1.

  3. System

    В пользователях создаём пользователей (как неожиданно, неправда ли?), тут же создаём группы. Я сделал kubernetes:admin и kubernetes:restricted. Первым будет выдаваться cluster-admin роль в самом Kubernetes, а вторым - что-то readonly. Не забываем добавить пользователей в соответсвующие группы.

  4. System > Certificates

    Вот тут не уверен, просто не проверял без этого. Я загрузил сюда сертификат, который получил ранее certbot-ом, а потом этот сертификат выбрал в Application > Providers > Kubernetes > Edit > Signing Key. Kubernetes не ругался, может можно тут и self-signed было стандартный использовать, не знаю, в общем. Попробуете сами.

  5. Customization > Property Mappings

    Создаём вот с такими параметрами

    • Name: Kubernetes

    • Scope name: kubernetes

    • Expression

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      
      all_groups = [
        "kubernetes:admin",
        "kubernetes:restricted"
      ]
      return dict(
        groups=[
          group for group in all_groups
          if ak_is_group_member(request.user, name=group)
        ]
      )
      

    Таким образом, для тех кто запросит этот скоуп, Authentik будет дописывать группы, в которых состоит пользователь, перебирая только кубовские.

  6. Проверяем kube-oidc-login

    1
    2
    3
    4
    5
    
    kubectl oidc-login setup \
      --oidc-issuer-url=https://authentik.example.com/application/o/kubernetes/  \
      --oidc-client-id='YOUR CLIENT ID' \
      --oidc-client-secret='YOUR CLIENT SECRET' \
      --oidc-extra-scope=kubernetes
    

    Всё там заводится, следуем дальше инструкциям.

  7. Выделяем права на группу в Kubernetes

    Authentik передаст список групп, теперь нужно чтобы Kubernetes на это обратил внимание. Добавляем к API серверу вот такие аргументы.

    1
    2
    
    --oidc-groups-claim=groups
    --oidc-groups-prefix=oidc:
    

    Теперь, можно создать, к примеру, такие ресурсы.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: oidc:kubernetes:admin
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: cluster-admin
    subjects:
    - apiGroup: rbac.authorization.k8s.io
      kind: Group
      name: oidc:kubernetes:admin
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: this-name-is-custom
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: view
    subjects:
    - apiGroup: rbac.authorization.k8s.io
      kind: Group
      name: oidc:kubernetes:restricted
    
All rights reserved
Создано при помощи Hugo
Тема Stack, дизайн Jimmy