K8s StructuredAuthenticationConfiguration

## StructuredAuthenticationConfigurationとは

:::note warn
この記事は v1.29でのalphaリリース時点の情報を参考にしています。
将来のリリースではAPIや挙動が変更になる可能性があります。
:::

KEP: https://github.com/kubernetes/enhancements/tree/master/keps/sig-auth/3331-structured-authentication-configuration

Doc: https://kubernetes.io/docs/reference/access-authn-authz/authentication/#using-authentication-configuration

K8sにおける認証に関する設定をフラグベースから設定ファイルで指定できるようにする機能(フィーチャーゲート)です。設定フラグが機能要求にともなって増えていく問題を解消することがモチベーションだったようです。設定ファイルでの指定にともなって**CEL式での制御やバリデーションができる機能も追加**されています。

e.g. 
```yaml
  claimMappings:
    username:
      expression: 'claims.username + ":external-user"'
    groups:
      expression: 'claims.roles.split(",")'
```

この機能はOIDC(JWT)認証の設定のみ可能です。([KEP](https://github.com/kubernetes/enhancements/tree/master/keps/sig-auth/3331-structured-authentication-configuration)を見ても他の認証設定は対象外と記載されています)
## AuthenticationConfiguration

設定ファイルは`AuthenticationConfiguration`の仕様で記述します。

see: https://github.com/kubernetes/kubernetes/blob/v1.29.0/staging/src/k8s.io/apiserver/pkg/apis/apiserver/types.go#L164

今回、動作確認ではOIDCのOP(OpenID Provider)にはDexをデプロイしE-mail + Passwordによる認証を利用したため、それにより得られるID Tokenのclaimに合わせて設定しました。

```yaml:auth.yaml
apiVersion: apiserver.config.k8s.io/v1alpha1
kind: AuthenticationConfiguration
jwt:
- issuer:
    url: https://dex.dex.svc.cluster.local:5556
    audiences:
    - example-app
    certificateAuthority: | # 今回は証明書の準備で生成したCA証明書
      -----BEGIN CERTIFICATE-----
      MIIBfDCCASKgAwIBAgIUB00+pEjrHI4Cyw1Cs15ybcT+U98wCgYIKoZIzj0EAwIw
      HDELMAkGA1UEBhMCSlAxDTALBgNVBAsTBFRlc3QwHhcNMjMxMjI2MDEzNjAwWhcN
      MjgxMjI0MDEzNjAwWjAcMQswCQYDVQQGEwJKUDENMAsGA1UECxMEVGVzdDBZMBMG
      ByqGSM49AgEGCCqGSM49AwEHA0IABBwzZGKDn6I4mryeGPCKHrjt1R1gGeNKPW+/
      v5ACkqBPuSxhBBnu0SzbMFAk9MoKNqCNt0VmXtIteU0skYFcICSjQjBAMA4GA1Ud
      DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRgbWLYqlw7Ru2y
      PIUo6m3zYMIgMjAKBggqhkjOPQQDAgNIADBFAiBF0e5NlNWOBU2euhV3zO4COQPz
      NQvFcxRRMEMQRr+qIgIhAPOw9O6fy9Wfs9sk0juSfNuKAHMdISjkh9EzTxMqwXRS
      -----END CERTIFICATE-----
  claimMappings:
    username:
      claim: 'name'
      prefix: ''
    uid:
      claim: 'sub'
```

上記の設定はkube-apiserverの`--authentication-config`フラグにファイルを指定することで反映できます。`v1.29` 時点ではあわせて`StructuredAuthenticationConfiguration`フィーチャーゲートを有効にする必要があります。

設定ファイルは自動リロードに対応(または対応予定)しているようです。

今回、K8sクラスタはkindで作成したものを利用しました。kindによる設定ファイルの指定や起動方法などは[kindを使ったk8sクラスタの準備](#kindを使ったk8sクラスタの準備)を参照してください。

## 動作確認

1. [Dexの準備](#Dexの準備)に従って作成したK8sクラスタにDexをデプロイ

2. ID Tokenを取得するためのアプリケーションをビルド
```
> git clone https://github.com/dexidp/dex.git
> cd dex/examples-example-app
> GOOS=linux GOARCH=amd64 go build -o example-app
> cp example-app /path/to/workdir/.
```

3. 動作確認用アプリケーションをデプロイ

<details><summary>コンテナイメージ作成の例</summary><div>

```dockerfile:Dockefile
FROM debian:12.4

COPY example-app .
COPY ca.pem .

ENTRYPOINT ["./example-app"]
CMD ["--issuer" "https://dex.dex.svc.cluster.local:5556" "--issuer-root-ca" "/etc/ssl/certs/ca-certificates.crt" ]
```

```
> docker build -t hiyosi/example-app -f Dockerfile .
> docker push hiyosi/example-app
```

</div></details>

<details><summary>K8sマニフェスト</summary><div>

```yaml:app.yaml
apiVersion: v1
kind: Pod
metadata:
  name: example-app
spec:
  containers:
  - name: example-app
    image: hiyosi/example-app
    imagePullPolicy: Always
    ports:
    - containerPort: 5555
```

</div></details>

```
> kubectl apply -f app.yaml
```

```
> kubectl port-forward ${EXAMPLE_APP} 5555:5555
```

4. 認証
[Dexの認証画面](https://127.0.0.1:32000/auth?client_id=example-app&redirect_uri=http%3A%2F%2F127.0.0.1%3A5555%2Fcallback&response_type=code&scope=openid+profile+email+offline_access&state=I+wish+to+wash+my+irish+wristwatch) にアクセスしE-mail + Passwordでログインします。
(認証情報はdexの設定ファイル参照)

認証に成功すると動作確認用アプリケーション(http://127.0.0.1:5555/callback)にコールバックされてID TokenやClaim情報が表示されます。

5. kubeconfigの設定

取得したID Tokenを使って`~/.kube/config`で以下のような設定を追加します (一部抜粋)。

```yaml:~/.kube/config
contexts:
- context:
    cluster: kind-kind
    user: dex-idp
  name: kind-oidc
users:
- name: dex-idp
  user:
    auth-provider:
      config:
        client-id: example-app
        id-token: ${ID_TOKEN} # Dexで認証に成功すると得られるID Token
        idp-certificate-authority: /tmp/ca.pem # 証明書の準備で生成したCA証明書
        idp-issuer-url: https://dex.dex.svc.cluster.local:5556
      name: oidc
```

6. 動作確認用のRBACの設定
```yaml:rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
- apiGroups: [""] # "" indicates the core API group
  resources: ["pods"]
  verbs: ["get", "watch", "list"]

---

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: default
subjects:
- kind: User
  name: admin
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io
```

```
> kubectl apply -f rbac.yaml
```

7. 動作確認
```
> kubectl --context kind-oidc get pods
NAME                                READY   STATUS    RESTARTS   AGE
example-app                         1/1     Running   0          19m

> kubectl --context kind-oidc get node
Error from server (Forbidden): nodes is forbidden: User "admin" cannot list resource "nodes" in API group "" at the cluster scope
```
権限を付与したリリースは想定通り操作でき、それ以外のリソースは操作できないことが確認できました。

## その他準備など
### kindを使ったk8sクラスタの準備

※ 動作確認目的だけのため、File sharingやPort mappingなど、動作確認用に少々強引な設定を入れています。

```yaml:kind.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    kubeadmConfigPatches:
    - |
      kind: ClusterConfiguration
      apiServer:
        extraArgs:
          authentication-config: /tmp/auth/auth.yaml
        extraVolumes: 
          - name: "auth-config" 
            hostPath: /tmp/docker
            mountPath: /tmp/auth
    extraMounts: # ローカルのファイルをDocker for MacのFile sharingを使って渡す
    - hostPath: /tmp/docker
      containerPath: /tmp/docker
    extraPortMappings: # Dexでの認証画面にローカルからアクセスするため
    - containerPort: 32000 
      hostPort: 32000
  - role: worker
featureGates:
  "StructuredAuthenticationConfiguration": true
```

```
> kind create cluster --image kindest/node:v1.29.0 --config kind.yaml
```

### Dexの準備

https://dexidp.io/docs/kubernetes/ を参考にデプロイしました。

証明書は上記の手順に従わず [証明書の準備](#証明書の準備) の項目の手順で作成したものを使いました。
また認証はemail + passwordのみを利用するため、Dexの設定ファイルからGitHubを使った認証等の設定は省きました。

<details><summary>dex.yamlの差分</summary><div>

```diff
diff --git a/examples/k8s/dex.yaml b/examples/k8s/dex.yaml
index c8d83944..be351080 100644
--- a/examples/k8s/dex.yaml
+++ b/examples/k8s/dex.yaml
@@ -37,18 +37,6 @@ spec:
         - name: tls
           mountPath: /etc/dex/tls

-        env:
-        - name: GITHUB_CLIENT_ID
-          valueFrom:
-            secretKeyRef:
-              name: github-client
-              key: client-id
-        - name: GITHUB_CLIENT_SECRET
-          valueFrom:
-            secretKeyRef:
-              name: github-client
-              key: client-secret
-
         readinessProbe:
           httpGet:
             path: /healthz
@@ -72,7 +60,7 @@ metadata:
   namespace: dex
 data:
   config.yaml: |
-    issuer: https://dex.example.com:32000
+    issuer: https://dex.dex.svc.cluster.local:5556
     storage:
       type: kubernetes
       config:
@@ -82,14 +70,6 @@ data:
       tlsCert: /etc/dex/tls/tls.crt
       tlsKey: /etc/dex/tls/tls.key
     connectors:
-    - type: github
-      id: github
-      name: GitHub
-      config:
-        clientID: $GITHUB_CLIENT_ID
-        clientSecret: $GITHUB_CLIENT_SECRET
-        redirectURI: https://dex.example.com:32000/callback
-        org: kubernetes
     oauth2:
       skipApprovalScreen: true

diff --git a/examples/k8s/gencert.sh b/examples/k8s/gencert.sh
index 77435857..2ac373fd 100755
--- a/examples/k8s/gencert.sh
+++ b/examples/k8s/gencert.sh
@@ -16,6 +16,7 @@ subjectAltName = @alt_names

 [alt_names]
 DNS.1 = dex.example.com
+DNS.2 = dex.dex.svc.cluster.local
 EOF

 openssl genrsa -out ssl/ca-key.pem 2048
```
```
</div></details>

```
> kubectl create ns dex

> kubectl -n dex create secret tls dex.example.com.tls --cert=server.pem --key=server-key.pem

> kubectl apply -f dex.yaml
```

#### /etc/hosts


```
$DEX_SVC_IP dex.dex.svc.cluster.local
```

### 証明書の準備

(色々と試している中で証明書は下記の手順で作成しましたが、 結果的に[Dexの準備](#Dexの準備)の参考URLの方法でも問題なかったかもしれません)

ca.json
```json
{
  "hosts": [
    "ca.example.com"
  ],
  "key": {
    "algo": "ecdsa",
    "size": 256
  },
  "names": [
    {
      "C": "JP",
      "OU": "Test"
    }
  ]
}
```

```
> cfssl gencert -initca ca.json | cfssljson -bare ca
```

config.json
```json
{
  "signing": {
    "default": {
      "expiry": "8760h"
    },
    "profiles": {
      "server": {
        "usages": [
          "signing",
          "digital signing",
          "key encipherment",
          "server auth"
        ],
        "expiry": "8760h"
      },
      "client": {
        "usages": [
          "signing",
          "digital signature",
          "key encipherment",
          "client auth"
        ],
        "expiry": "8760h"
      }
    }
  }
}
```

server.json
```json
{
    "hosts": [
        "dex.example.com",
        "dex.dex.svc.cluster.local"
    ],
    "key": {
        "algo": "ecdsa",
        "size": 256
    },
    "names": [{
        "C": "JP",
        "L": "Tokyo",
        "OU": "Test"
    }]
}
```

```
> cfssl gencert -ca ca.pem -ca-key ca-key.pem -config config.json -profile=server server.json | cfssljson -bare server
```


```
> openssl verify -CAfile ca.pem server.pem
server.pem: OK
```