⛓️

Kubernetes API で OIDC 認証を使う

2024/09/08に公開

kubernetes で API を使用する際の認証には 様々な認証方法 がありますが、その 1 つに OIDC 認証 があります。OIDC 認証では外部の OIDC provider のユーザーやグループの認証情報を k8s クラスタ内の role にマッピングして kubectl や api などを実行できます。

セットアップ

今回は k8s クラスタとは別の環境に OIDC provider を構築して OIDC 認証を実現します。OIDC provider としては Authelia を使用し、認証に使用するユーザー、グループは LLDAP 側に登録した情報を参照する構成にします。Authelia, LLDAP は以前記事を書いたので詳細は以下を参照。

概要図としては次のようになります。


概要図

上記の構成は一例なので、例えば authelia 等を k8s クラスタ内に構築したり kubectl を実行するホストをクラスタ内のノードとするような構成でも ok です。

Authelia と LLDAP の構築

Authelia と LLDAP を docker で構築するための docker-compose.yml を以下のように準備。

docker-compose.yml
---
services:
  authelia:
    image: authelia/authelia
    container_name: authelia
    restart: always
    volumes:
      - ./authelia:/config
      - ./certs:/certs
    ports:
      - 9091:9091
    environment:
      - TZ=Asia/Tokyo
  lldap:
    image: lldap/lldap:stable
    container_name: lldap
    restart: always
    ports:
      - "3890:3890"
      - "6360:6360"
      - "17170:17170"
    environment:
      - LLDAP_VERBOSE=true
      - LLDAP_JWT_SECRET=REPLACE_WITH_RANDOM
      - LLDAP_KEY_SEED=REPLACE_WITH_RANDOM
      - LLDAP_LDAP_BASE_DN=dc=ldap,dc=centre,dc=com

ディレクトリ構成

├── authelia
│   ├── configuration.yml
│   └── notification.txt
├── certs
│   ├── authelia.ops.com.crt
│   └── authelia.ops.com.key
├── docker-compose.yml

authelia の設定ファイル configuration.yml認証・認可サーバの OSS Authelia を試す と同様ですが、今回の構成に合わせて以下の部分を更新します。
authentication_backend.ldap はローカルの lldap コンテナと接続するため以下のように設定。

authentication_backend:
  refresh_interval: 1m
  ldap:
    implementation: custom
    address: ldap://lldap:3890
    timeout: 5s
    start_tls: false
    base_dn: "dc=ldap,dc=centre,dc=com"
    users_filter: "(&({username_attribute}={input})(objectClass=person))"
    additional_users_dn: "ou=people"
    additional_groups_dn: "ou=groups"
    attributes:
      username: "uid"
      display_name: "displayName"
      mail: "mail"
      group_name: "cn"
      member_of: "memberOf"
      distinguished_name: "distinguishedName"
    groups_filter: "(member={dn})"
    user: "uid=admin,ou=people,dc=ldap,dc=centre,dc=com"
    password: "password"

kubernetes 接続用の OIDC client を設定

  • client_id: 任意の値
  • client_name: 任意の値
  • client_secret: 任意の値を hash 化した値
docker run --rm authelia/authelia:latest authelia crypto hash generate pbkdf2 --variant sha512  --password kubernetes

Digest: $pbkdf2-sha512$310000$Q0NxDoHL9ciMU/uXTSR1vw$EFjVf2JNydCRLjWGGpA0VSwh7//ySsf05Q.T6SywcTZQhfoecc4NoQ8A5uB6BO49ES.pXBTdS/zY3FYTZG9kVw
  • redirect_uri: k8s kube-apiserver の callback の URL を指定する。この構成では control plane 用のノード ip address が 192.168.3.131 なのでそれを設定。

また、後述の kubelogin ではデフォルトで localhost:8000 で server を起動するため、localhost も追加する必要がある(ないと The 'redirect_uri' parameter does not match any of the OAuth 2.0 Client's pre-registered 'redirect_uris'."} のエラーになる)。

  clients:
    - client_id: "kubernetes"
      client_name: "kubernetes"
      client_secret: "$pbkdf2-sha512$310000$Ye0y8UZeHcDKi3.oJ8BFAg$VwYhrAXz7JZesQ6j5TH/bCvxqrmnjxonwmdUA5QHjQdOoe7Cstfrnvg4..AeJve9qRnCQp5wWhGgucTpkEYgsg"
      public: false
      authorization_policy: "one_factor"
      redirect_uris:
        - https://192.168.3.131:6443/api/v1/auth/callback
        - http://localhost:8000
      scopes:
        - "openid"
        - "profile"
        - "groups"
        - "email"
      require_pkce: true
      pkce_challenge_method: "S256"
      userinfo_signed_response_alg: "none"
      token_endpoint_auth_method: "client_secret_basic"

上記を設定した configuration.yml の全文は以下。

configuration.yml
authelia/configuration.yml
authentication_backend:
  refresh_interval: 1m
  ldap:
    implementation: custom
    address: ldap://lldap:3890
    timeout: 5s
    start_tls: false
    base_dn: "dc=ldap,dc=centre,dc=com"
    users_filter: "(&({username_attribute}={input})(objectClass=person))"
    additional_users_dn: "ou=people"
    additional_groups_dn: "ou=groups"
    attributes:
      username: "uid"
      display_name: "displayName"
      mail: "mail"
      group_name: "cn"
      member_of: "memberOf"
      distinguished_name: "distinguishedName"
    groups_filter: "(member={dn})"
    user: "uid=admin,ou=people,dc=ldap,dc=centre,dc=com"
    password: "password"

access_control:
  default_policy: "deny"
  rules:
    - domain: "authelia.ops.com"
      policy: "one_factor"
session:
  secret: "insecure_session_secret"
  cookies:
    - name: "authelia_session"
      domain: "ops.com" # Should match whatever your root protected domain is
      authelia_url: "https://authelia.ops.com:9091"
      expiration: "1 hour" # 1 hour
      inactivity: "5 minutes" # 5 minutes
regulation:
  max_retries: 3
  find_time: "2 minutes"
  ban_time: "5 minutes"
storage:
  encryption_key: "you_must_generate_a_random_string_of_more_than_twenty_chars_and_configure_this"
  local:
    path: "/config/db.sqlite3"
notifier:
  filesystem:
    filename: "/config/notification.txt"
identity_providers:
  oidc:
    jwks:
      - key_id: "authelia"
        algorithm: "RS256"
        use: "sig"
        key: |
          -----BEGIN PRIVATE KEY-----
          ...
          -----END PRIVATE KEY-----
    clients:
      - client_id: "kubernetes"
        client_name: "kubernetes"
        client_secret: "$pbkdf2-sha512$310000$Ye0y8UZeHcDKi3.oJ8BFAg$VwYhrAXz7JZesQ6j5TH/bCvxqrmnjxonwmdUA5QHjQdOoe7Cstfrnvg4..AeJve9qRnCQp5wWhGgucTpkEYgsg"
        public: false
        authorization_policy: "one_factor"
        redirect_uris:
          - https://192.168.3.131:6443/api/v1/auth/callback
          - http://localhost:8000
        scopes:
          - "openid"
          - "profile"
          - "groups"
          - "email"
        require_pkce: true
        pkce_challenge_method: "S256"
        userinfo_signed_response_alg: "none"
        token_endpoint_auth_method: "client_secret_basic"

LLDAP では OIDC 認証に使用する以下のユーザー・グループを作成しておきます

group user email
developer dev-user dev-user@test.com

kubelogin のインストール

https://github.com/int128/kubelogin

kubelogin は kubectl 用の OIDC 認証の plugin で、kubectl 実行時に OIDC provider へのログインや id_token の取得などを行います。
いくつかインストール方法がありますが、ここでは krew でインストールします。

kubectl krew install oidc-login

kubelogin を使った認証の実行

セットアップが完了したので実際に OIDC 認証を試します。

ユーザーを role にマッピングする

kubelogin のセットアップ方法と使い方は docs/setup.md に記載があるので、まずはこちらに沿って OIDC 側のユーザーを k8s クラスタ内の role にマッピングして API を実行する方法を試します。

kubelogin セットアップのために kubectl で以下を実行。実行するマシンは kubectl が入っていればクラスタ内のノードでもクラスタ外のマシンのどちらでも良いです。

$ kubectl oidc-login setup \
    --oidc-issuer-url=https://authelia.ops.com:9091 \
    --oidc-client-id=kubernetes \
    --oidc-client-secret=kubernetes

実行するとブラウザが開いて Authelia のログイン画面になるので事前に作成した dev-user でログインします。ログインに成功すると同意リクエストができるので同意するを選択

同意すると localhost:8000 の kubelogin の画面で以下が表示されるので一旦ブラウザは閉じて ok

Authenticated
You have logged in to the cluster. You can close this window.

コマンドの方ではその後に実行する手順が表示されます。

出力
$ kubectl oidc-login setup \
    --oidc-issuer-url=https://authelia.ops.com:9091 \
    --oidc-client-id=kubernetes \
    --oidc-client-secret=kubernetes
authentication in progress...
Opening in existing browser session.

## 2. Verify authentication

You got a token with the following claims:

{
  "amr": [
    "pwd"
  ],
  "at_hash": "yZE0axs8-n-8wr8igbOSyQ",
  "aud": [
    "kubernetes"
  ],
  "auth_time": 1725626733,
  "azp": "kubernetes",
  "client_id": "kubernetes",
  "exp": 1725630569,
  "iat": 1725626969,
  "iss": "https://authelia.ops.com:9091",
  "jti": "d0da435c-d96e-4a3a-adef-59caed7ae525",
  "nonce": "7dwLQx1LDeJ3ThEqyA4xrG8e48RYjF5UpnqKyvu4nL4",
  "sub": "6b2acb37-d3d7-4333-91cf-a9f7c503ee05"
}

## 3. Bind a cluster role

Run the following command:

        kubectl create clusterrolebinding oidc-cluster-admin --clusterrole=cluster-admin --user='https://authelia.ops.com:9091#6b2acb37-d3d7-4333-91cf-a9f7c503ee05'

## 4. Set up the Kubernetes API server

Add the following options to the kube-apiserver:

        --oidc-issuer-url=https://authelia.ops.com:9091
        --oidc-client-id=kubernetes

## 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://authelia.ops.com:9091 \
          --exec-arg=--oidc-client-id=kubernetes \
          --exec-arg=--oidc-client-secret=kubernetes

## 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.

手順 2 は OIDC provider (authelia) から取得した id token の中身が書いてあるので 手順 3 の k8s クラスタ側で OIDC ユーザーにマッピングする role の作成から進めます。上記の手順では既存の cluster-admin ロールを OIDC ユーザー https://authelia.ops.com:9091#6b2acb37-d3d7-4333-91cf-a9f7c503ee05 に関連付けるコマンドになっていますが、任意の role を作成して関連付けることもできます。
ここでは pod, service の権限のみ付加した role を作成して結びつけます。

role.yml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: oidc-authelia-developer
rules:
  - apiGroups: [""]
    resources: ["pods", "services"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: oidc-authelia-developer
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: https://authelia.ops.com:9091#6b2acb37-d3d7-4333-91cf-a9f7c503ee05
roleRef:
  kind: ClusterRole
  name: oidc-authelia-reader
  apiGroup: rbac.authorization.k8s.io
kubectl apply -f role.yml

次に手順 4 で k8s クラスタ内の control plane node 上の /etc/kubernetes/manifests/kube-apiserver.yaml に以下を追加します。

  • oidc-issuer-url: Authelia にアクセスする際の URL
  • oidc-client-id: Authelia 側で設定した client id
kube-apiserver.yaml
spec:
  containers:
  - command:
    ...
    - --oidc-issuer-url=https://authelia.ops.com:9091
    - --oidc-client-id=kubernetes

kube-apiserver は static pod なのでマニフェストを変更して保存したら自動で kube-apiserver pod が再起動して変更が反映されます。

手順5 では kubelogin を実行したマシンに戻って表示されているコマンドを実行します。

$ 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://authelia.ops.com:9091 \
          --exec-arg=--oidc-client-id=kubernetes \
          --exec-arg=--oidc-client-secret=kubernetes \

これにより ~/.kube/config に user が追加されます。

以上でクラスタに OIDC で認証するためのセットアップが完了したので、実際にユーザーで認証してみます。context を先ほど作成した oidc に変更

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

この状態で kubectl get pod 等のコマンドでクラスタ内のリソースを確認しようとするとブラウザが開き、oidc ユーザーのログインが要求されます。セットアップ時のセッションが残っていればそのまま先程と同様に権限のリクエストが要求されるので同意を選択。

認証に成功すると oidc ユーザーとして各種 kubernetes api が実行できるようになります。

$ kubectl get pod
NAME                             READY   STATUS    RESTARTS        AGE
coredns-67d68df75b-ghhq5         1/1     Running   0               26h
coredns-67d68df75b-kwl64         1/1     Running   0               26h
etcd-k8s-m1                      1/1     Running   7 (5d6h ago)    47d
kube-apiserver-k8s-m1            1/1     Running   0               19h
kube-controller-manager-k8s-m1   1/1     Running   179 (10m ago)   47d
kube-proxy-pwsvp                 1/1     Running   5 (5d6h ago)    47d
kube-proxy-vgnzw                 1/1     Running   6 (5d6h ago)    47d
kube-scheduler-k8s-m1            1/1     Running   127 (19h ago)   47d
snapshot-controller-0            1/1     Running   1 (5d6h ago)    13d

ユーザーは作成した role に結びついているので、role で許可されていない操作は拒否されます。

$ kubectl get deployments.apps
Error from server (Forbidden): deployments.apps is forbidden: User "dev-user@test.com" cannot list resource "deployments" in API group "apps" in the namespace "kube-system"

ログインした際の認証情報は kubectl を実行したホストの ~/.kube/cache/oidc-login 以下にキャッシュされます。
これを削除すると再度コマンド実行に OIDC ユーザーをログインが要求されるようになります。

$ cat ~/.kube/cache/oidc-login/9eb050114a87cc3298262bbee8b9c9b78362393d6e1461c307f73a70f70b8f98
{"id_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImF1dGhlbGlhIiwidHlwIjoiSldUIn0.eyJhbXIiOlsicHdkIl0sImF0X2hhc2giOiI0ZXV2cGlLUVJCbFpUdTZvSl9nc3FBIiwiYXVkIjpbImt1YmVybmV0ZXMiXSwiYXV0aF90aW1lIjoxNzI1NjI4NjI5LCJhenAiOiJrdWJlcm5ldGVzIiwiY2xpZW50X2lkIjoia3ViZXJuZXRlcyIsImVtYWlsIjoiZGV2LXVzZXJAdGVzdC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZXhwIjoxNzI1NjMyMjY2LCJncm91cHMiOlsiZGV2ZWxvcGVyIl0sImlhdCI6MTcyNTYyODY2NiwiaXNzIjoiaHR0cHM6Ly9hdXRoZWxpYS5vcHMuY29tOjkwOTEiLCJqdGkiOiI5NDZhZDk5Ni1lNzVlLTQyYTktOTNkOS0yZDUzZDdhMTVkYjgiLCJub25jZSI6IlUtVjRkTFY4anl0OGVGSFJVOUxGUlVNc3hYcEVIei1wcjEzM2o3bzhjZHMiLCJzdWIiOiI2YjJhY2IzNy1kM2Q3LTQzMzMtOTFjZi1hOWY3YzUwM2VlMDUifQ.UAaCgkuJGf8krxmRy2MeumN9SnpyTYGaq_ORRDOyn29NPfIwYON-D6Mb7UlVhJQihjDaUgTqTmHTzPj_kM62hBgKFS6M_tvRzJuc2ciwW87sst2gbnPNILReGo0P5t8OPK0YDw-WrK3dkjr4Jj8KOyRO2_n6UWJGQwnKP0aPkHETLSlsdlouilKRC7ubKwSzxtGtaPP15JgNX2Ex1D2KZJRV0hj-PtRxg9TAW6q7uwVny6GzT3EBNMMxkAcL_xWf-_EFWukPcAOPCl4uwycpa27X1SxHQKzAgru4W9Jvk5llvOLAV8-ECaf-K-UslpRttjbRTxTPK01zuhICQh5EIQ"}

email で認証する

上記の手順 3 で user に設定されている際の文字列 6b2acb37-d3d7-4333-91cf-a9f7c503ee05 は id token 内の sub に入っている文字列に対応しています。

## 3. Bind a cluster role

Run the following command:

        kubectl create clusterrolebinding oidc-cluster-admin --clusterrole=cluster-admin --user='https://authelia.ops.com:9091#6b2acb37-d3d7-4333-91cf-a9f7c503ee05'

{
  "amr": [
    "pwd"
  ],
  "at_hash": "yZE0axs8-n-8wr8igbOSyQ",
  "aud": [
    "kubernetes"
  ],
  "auth_time": 1725626733,
  "azp": "kubernetes",
  "client_id": "kubernetes",
  "exp": 1725630569,
  "iat": 1725626969,
  "iss": "https://authelia.ops.com:9091",
  "jti": "d0da435c-d96e-4a3a-adef-59caed7ae525",
  "nonce": "7dwLQx1LDeJ3ThEqyA4xrG8e48RYjF5UpnqKyvu4nL4",
  "sub": "6b2acb37-d3d7-4333-91cf-a9f7c503ee05"
}

kube-apiserver の OIDC 認証のデフォルト設定で username として token 内の sub フィールドの値を読み取るように設定されていることが原因です(たぶん)。
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens

--oidc-username-claim JWT claim to use as the user name. By default sub, which is expected to be a unique identifier of the end user. Admins can choose other claims, such as email or name, depending on their provider. However, claims other than email will be prefixed with the issuer URL to prevent naming clashes with other plugins.

sub

なので kube-apiserver と kubelogin 側の値を変更することで OIDC ユーザーの email などの値を username として使用することもできます。
kubectl oidc-login setup--oidc-extra-scope=email を追加。

$ kubectl oidc-login setup \
          --oidc-issuer-url=https://authelia.ops.com:9091 \
          --oidc-client-id=kubernetes \
          --oidc-client-secret=kubernetes \
          --oidc-extra-scope=email

kube-apiserver.yaml では - --oidc-username-claim=email を追加

kube-apiserver.yaml
spec:
  containers:
  - command:
    ...
    - --oidc-issuer-url=https://authelia.ops.com:9091
    - --oidc-client-id=kubernetes
    - --oidc-username-claim=email

RoleBinding では sub 文字列の代わりに OIDC ユーザーの email を指定。

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: authelia-developer
rules:
  - apiGroups: [""]
    resources: ["pods", "services"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: authelia-develope
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: dev-user@test.com
roleRef:
  kind: ClusterRole
  name: authelia-developer
  apiGroup: rbac.authorization.k8s.io

context では --exec-arg=--oidc-extra-scope=email を追加。

$ 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://authelia.ops.com:9091 \
          --exec-arg=--oidc-client-id=kubernetes \
          --exec-arg=--oidc-client-secret=kubernetes \
          --exec-arg=--oidc-extra-scope=email

残りの部分は同様の手順で、OIDC ユーザーの email を使用してユーザーと k8s role のマッピングが行えます。

グループを role にマッピングする

kubectl oidc-login setup で表示される手順や Kubernetes OpenID Connection authentication はユーザーを k8s 側の Role にマッピングする方法となっていますが、ユーザーだけでなく OIDC のグループをマッピングすることもできます。むしろ RBAC としてはこちらがメインの使い方になる気もしますが。

参考:

基本的には email で認証する の設定と同様に、OIDC ユーザーの groups scope を認証に設定する方法となります。

手順 4 で k8s クラスタ内の control plane node 上の /etc/kubernetes/manifests/kube-apiserver.yaml を編集する際に --oidc-groups-claim=groups を追加します。

kube-apiserver.yaml
spec:
  containers:
  - command:
    ...
    - --oidc-issuer-url=https://authelia.ops.com:9091
    - --oidc-client-id=kubernetes
    - --oidc-groups-claim=groups

また、kubectl oidc-login setup コマンドに --oidc-extra-scope=groups を追加。

$ kubectl oidc-login setup \
          --oidc-issuer-url=https://authelia.ops.com:9091 \
          --oidc-client-id=kubernetes \
          --oidc-client-secret=kubernetes \
          --oidc-extra-scope=groups

ブラウザが開いて初回セットアップ時と同様に権限の同意が要求されますが、初回と比較すると グループメンバーシップにアクセス が追加されていることがわかります。こちらが groups の scope に対応しています。

同意するとセットアップ手順が出力が表示されますが、id Token の中身をはじめのときと比較すると groups が追加されていることがわかります。groups には要求に同意した oidc ユーザーが所属するグループが設定されています。

{
  "amr": [
    "pwd"
  ],
  "at_hash": "3ePkHFLhhnfUiAQUWHJ5dw",
  "aud": [
    "kubernetes"
  ],
  "auth_time": 1725628629,
  "azp": "kubernetes",
  "client_id": "kubernetes",
  "exp": 1725633263,
  "groups": [
    "developer"
  ],
  "iat": 1725629663,
  "iss": "https://authelia.ops.com:9091",
  "jti": "98cd6896-da71-48f5-b0b2-70f627d510ea",
  "nonce": "3IyA0zi6gA5gkyOG0DXcZ5ukNGa321Hls9i5X9gEpOc",
  "sub": "6b2acb37-d3d7-4333-91cf-a9f7c503ee05"
}

k8s 側でロールを作成する部分は同様ですが、RoleBinding の subject で user の代わりに oidc 側のグループ名 developer を指定します。

role-oidc.yml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: oidc-authelia-developer
rules:
  - apiGroups: [""]
    resources: ["pods", "services"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: oidc-authelia-developer
subjects:
  - kind: Group
    name: developer
roleRef:
  kind: ClusterRole
  name: oidc-authelia-reader
  apiGroup: rbac.authorization.k8s.io

kubectl config set-credentials で context を設定する際も oidc-extra-scope=groups を追加します。

$ 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://authelia.ops.com:9091 \
          --exec-arg=--oidc-client-id=kubernetes \
          --exec-arg=--oidc-client-secret=kubernetes \
          --exec-arg=--oidc-extra-scope=groups

これでユーザーと同様に kubectl を実行すると、ユーザーが属する group に基づいた権限でコマンドが実行できます。実行可能な権限も role に基づいて設定されています。

$ kubectl auth can-i --list
Resources                                       Non-Resource URLs   Resource Names   Verbs
selfsubjectreviews.authentication.k8s.io        []                  []               [create]
selfsubjectaccessreviews.authorization.k8s.io   []                  []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                  []               [create]
pods                                            []                  []               [get list watch create update patch delete]
services                                        []                  []               [get list watch create update patch delete]
                                                [/api/*]            []               [get]
                                                [/api]              []               [get]
                                                [/apis/*]           []               [get]
                                                [/apis]             []               [get]
                                                [/healthz]          []               [get]
                                                [/healthz]          []               [get]
                                                [/livez]            []               [get]
                                                [/livez]            []               [get]
                                                [/openapi/*]        []               [get]
                                                [/openapi]          []               [get]
                                                [/readyz]           []               [get]
                                                [/readyz]           []               [get]
                                                [/version/]         []               [get]
                                                [/version/]         []               [get]
                                                [/version]          []               [get]
                                                [/version]          []               [get]

kubelogin を使わない方法

k8s ドキュメントの Option 1 - OIDC Authenticator に示すように kubeconfig に直接 OIDC 関連の情報を指定することで kubelogin を使わずに OIDC 認証することもできます。やや手間がかかるので直接この方法を使用する機会はあまりなさそうですが、備忘録として記載しておきます。

ドキュメントによると必要な設定項目は以下。

kubectl config set-credentials USER_NAME \
   --auth-provider=oidc \
   --auth-provider-arg=idp-issuer-url=( issuer url ) \
   --auth-provider-arg=client-id=( your client id ) \
   --auth-provider-arg=client-secret=( your client secret ) \
   --auth-provider-arg=refresh-token=( your refresh token ) \
   --auth-provider-arg=idp-certificate-authority=( path to your ca certificate ) \
   --auth-provider-arg=id-token=( your id_token )

この中で他の項目は authelia の config に基づいて設定できますが、refresh_tokenid_token は実際にログインして取得する必要があります。OpenID Connect Tokens1. Log in to IdP ~ 2. Provide access_token, id_token, and refresh_token に相当。
まず authelia の configuration.yml で scopes に offline_access を追加します。

configuration.yml
        scopes:
          - "openid"
          - "profile"
          - "groups"
          - "email"
          - "offline_access"

追加したらコンテナを再作成するなどして変更を適用。
次に authelia から authorization_code を取得するため認証用の URL を作成しますが、Authelia 側で PKCE(Proof Key for Code Exchange)を有効化しているので code_verifier と code_challenge を生成する必要があります。

参考

https://www.authlete.com/ja/developers/pkce/
https://developers.line.biz/ja/docs/line-login/integrate-pkce/#generate-code-challenge

code_verifier は例えば以下のコマンドで作成できます。使用可能文字種:半角英数字(a〜z、A~Z、0~9)および記号(-._~)からなるランダムな文字列、文字数:43文字〜128文字 という制限があるので注意。

$ openssl rand -base64 50 | tr -d '=+/'

UDEcJyk3ncBVylCGQVKUjYSKGUKQYv7ecg92o5s4Sjd9C6b3TsE0DLA9A86Pz9VhU

code_challenge は code_verifier を sha256 で hash 化して base64url encode したものになります。例えば以下のようなコードで生成できます。

hash.py
import base64
import hashlib
import sys

argv = sys.argv
code_verify = argv[1]

# Hash the input using SHA-256
hashed_bytes = hashlib.sha256(code_verify.encode('utf-8')).digest()

# Encode the hashed result in base64url format (replace + and / with - and _)
base64url_encoded = base64.urlsafe_b64encode(hashed_bytes).rstrip(b'=').decode('utf-8')

print(base64url_encoded)
$ python3 hash.py UDEcJyk3ncBVylCGQVKUjYSKGUKQYv7ecg92o5s4Sjd9C6b3TsE0DLA9A86Pz9VhU
nWL_jv0qY60oGXA5QDF_RLG7jscL1g8hy8O6XXzxwC4

これを元に以下のような URL を作ります。

https:/authelia.ops.com:9091/api/oidc/authorize?client_id=kubernetes&redirect_uri=http://localhost:8000&response_type=code&scope=openid+profile+email&state=xyz123456789&code_challenge_method=S256&code_challenge=nWL_jv0qY60oGXA5QDF_RLG7jscL1g8hy8O6XXzxwC4

リクエストパラメータは以下の通り。

parameter value 説明
client_id kubernetes authelia 側で指定した client id
redirect_uri http://localhost:8000 何でも良いが authelia の redirect_uris に指定したものである必要がある
response_type openid
profile
email
offline_access
authelia の scope
state xyz123456789 任意の値で良いがある程度の文字列長が必要
code_challenge_method S256 authelia 側で設定しているのと同じ S256 を指定
code_challenge nWL_jv0qY60oGXA5QDF_RLG7jscL1g8hy8O6XXzxwC4 上記で生成した値

ブラウザで上記の URL を入力すると authelia のログイン、権限の同意が要求されるので同意します。
同意すると redirect_uri で指定した http://localhost:8000 にリダイレクトされます。アプリが起動していないので localhost で接続が拒否されました となりますが、返された URL の中に authorization_code が記載されているのでメモします。

http://localhost:8000/?code=authelia_ac_bGHj7DSy0Ipf5IWdmvI0PPnXyM-9-WLEuaFqxtTeknc.cvfQGwUyxEUnVNBO6NWXOUxzSXuDnYbrqM7tPPImyK0&iss=https%3A%2F%2Fauthelia.ops.com%3A9091&scope=openid+profile+email&state=xyz123456789

code は上記のうち code= から &iss= の間の authelia_ac_bGHj7DSy0Ipf5IWdmvI0PPnXyM-9-WLEuaFqxtTeknc.cvfQGwUyxEUnVNBO6NWXOUxzSXuDnYbrqM7tPPImyK0 になります。
次に id_token 等を取得するために authelia の token endpoint /api/oidc/token に以下のようなリクエストを実行します。

curl -H "Authorization: Basic $(echo -n "kubernetes:kubernetes" | base64)" \
      --url 'https://authelia.ops.com:9091/api/oidc/token' \
      --header 'content-type: application/x-www-form-urlencoded' \
      --data "grant_type=authorization_code&code=authelia_ac_xqP9D_24o92BFkPZpuvJd5Q47-xMSPmZb0fbzQr8IC4.hteua34O3FFpTugL7THLUtPo2uI1EUtdBPPzTnzJLLY&redirect_uri=http://localhost:8000&code_verifier=UDEcJyk3ncBVylCGQVKUjYSKGUKQYv7ecg92o5s4Sjd9C6b3TsE0DLA9A86Pz9VhU"

header には client idclient_secret をそれぞれ入力して base64 encode した値を Basic Authentication として指定。
リクエストパラメータは以下の通り。

  • code: 上記で取得した authorization_code
  • redirect_uri: authorization_code を取得した際に指定したものと同じ url を指定
  • code_verifier: 始めに生成した値と同じものを指定

認証に成功すると id_token と refresh_token を含む以下のようなレスポンスが返ってきます。

{
  "access_token": "authelia_at_ZeMaoOGCXCSYfO9hJ-NrnAC0FACRDQJVDEXsTHHpN_A.Blo7i7F7nGkr7utT8TAW9_g_4aBhr1rX0R-Jy7TL4xk",
  "expires_in": 3600,
  "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImF1dGhlbGlhIiwidHlwIjoiSldUIn0.eyJhbXIiOlsicHdkIl0sImF0X2hhc2giOiJfdE9DRkUtTHNUZ3c5eDZMRkljb0lnIiwiYXVkIjpbImt1YmVybmV0ZXMiXSwiYXV0aF90aW1lIjoxNzI1NzE0ODcyLCJhenAiOiJrdWJlcm5ldGVzIiwiY2xpZW50X2lkIjoia3ViZXJuZXRlcyIsImVtYWlsIjoiZGV2LXVzZXJAdGVzdC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZXhwIjoxNzI1NzE4NTI3LCJncm91cHMiOlsiZGV2ZWxvcGVyIl0sImlhdCI6MTcyNTcxNDkyNywiaXNzIjoiaHR0cHM6Ly9hdXRoZWxpYS5vcHMuY29tOjkwOTEiLCJqdGkiOiI5ZmY4YzczZi0wMTg4LTQ1ZjEtOTE5YS02YTU0NTNmODA2MTIiLCJuYW1lIjoiZGV2LXVzZXIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJkZXYtdXNlciIsInN1YiI6IjM5ZWYzOTA1LTcwOGItNGY1Yi1hZmQ1LTdhZjRlYzc0ZWM3NyJ9.PEQ0eXC8czDzeXXopfluRpOoWrkmOIuAmyQ0q7ir3SY7eQq31dXwadS6up3ZIplHMQo8iYKD_Vogr9WIohDQCE5qJs53ovxEebTdWfAcL4gO6rPqmyYh5O6pKkS8v002l_M92aeNtyu7nAr6QFYteU1CiqmzlKZCF6777tfFGKpfDNIv113PJ4ybQeEeE7x3-fm3vcVge10NYSc4fcXxgvY7oeOCpZuvA4RnIDuFYybn4UwSV8rr4bLvQRHfnuHn2CDavaKNneIDsM4srlK5fT73NeIVKuD8HXOR9e8Ijk5NhLJf2QxoS3EKWTCd6fJEnXJa7w9axywlOdO9KelNvw",
  "refresh_token": "authelia_rt_TKjWw8uvsID2MxKqbuA4KItBKofxqz7UU4D5exlsdLY.X2eaFppn9saEAmlWN5tNSufp2idNmY8mPDHjdGilOUY",
  "scope": "openid profile email groups offline_access",
  "token_type": "bearer"
}

これでようやく必要な情報が揃ったので、k8s ドキュメントに沿って credential を設定します。

kubectl config set-credentials oidc-test \
   --auth-provider=oidc \
   --auth-provider-arg=idp-issuer-url=https://authelia.ops.com:9091 \
   --auth-provider-arg=client-id=kubernetes \
   --auth-provider-arg=client-secret=kubernetes \
   --auth-provider-arg=refresh-token=authelia_rt_TKjWw8uvsID2MxKqbuA4KItBKofxqz7UU4D5exlsdLY.X2eaFppn9saEAmlWN5tNSufp2idNmY8mPDHjdGilOUY \
   --auth-provider-arg=id-token="eyJhbGciOiJSUzI1NiIsImtpZCI6ImF1dGhlbGlhIiwidHlwIjoiSldUIn0.eyJhbXIiOlsicHdkIl0sImF0X2hhc2giOiJfdE9DRkUtTHNUZ3c5eDZMRkljb0lnIiwiYXVkIjpbImt1YmVybmV0ZXMiXSwiYXV0aF90aW1lIjoxNzI1NzE0ODcyLCJhenAiOiJrdWJlcm5ldGVzIiwiY2xpZW50X2lkIjoia3ViZXJuZXRlcyIsImVtYWlsIjoiZGV2LXVzZXJAdGVzdC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZXhwIjoxNzI1NzE4NTI3LCJncm91cHMiOlsiZGV2ZWxvcGVyIl0sImlhdCI6MTcyNTcxNDkyNywiaXNzIjoiaHR0cHM6Ly9hdXRoZWxpYS5vcHMuY29tOjkwOTEiLCJqdGkiOiI5ZmY4YzczZi0wMTg4LTQ1ZjEtOTE5YS02YTU0NTNmODA2MTIiLCJuYW1lIjoiZGV2LXVzZXIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJkZXYtdXNlciIsInN1YiI6IjM5ZWYzOTA1LTcwOGItNGY1Yi1hZmQ1LTdhZjRlYzc0ZWM3NyJ9.PEQ0eXC8czDzeXXopfluRpOoWrkmOIuAmyQ0q7ir3SY7eQq31dXwadS6up3ZIplHMQo8iYKD_Vogr9WIohDQCE5qJs53ovxEebTdWfAcL4gO6rPqmyYh5O6pKkS8v002l_M92aeNtyu7nAr6QFYteU1CiqmzlKZCF6777tfFGKpfDNIv113PJ4ybQeEeE7x3-fm3vcVge10NYSc4fcXxgvY7oeOCpZuvA4RnIDuFYybn4UwSV8rr4bLvQRHfnuHn2CDavaKNneIDsM4srlK5fT73NeIVKuD8HXOR9e8Ijk5NhLJf2QxoS3EKWTCd6fJEnXJa7w9axywlOdO9KelNvw"

あとは設定した user を使用するように context を設定すれば、kubelogin のときと同様に OIDC 側の認証情報で各種 kubectl コマンドが実行できるようになります。

Discussion