Kubernetes/MinikubeでService Account Tokenを試してみる

## はじめに

Kubernetesを学習している。過去に用意された資料でAuthn/Authz/RBACなどについて学んでいたがServiceAccountを用意してもv1.24以降デフォルトでSecretを生成しなくなった様でつまづいたのでその対応メモ。

ハンズオンや勉強資料として[KubeKubernetes道場 Advent Calendar 2018](https://cstoku.dev/posts/2018/k8sdojo-20/)での手順を元にした。今はもう2025年だが…。

なお私が確認した環境は

- minikube v1.35.0
- k8s v1.32.0

## 手順

### ServiceAccountの作成

まずアカウント名をtest-saとしてServiceAccountを作成する

```sh
$ kubectl create sa test-sa
serviceaccount/test-sa created
```

どんな感じにできたか確認してみる。

```sh
$ kubectl get sa/test-sa -o yaml
```
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: "2025-01-28T00:09:09Z"
  name: test-sa
  namespace: default
  resourceVersion: "414"
  uid: 7a0f0bb6-1714-4193-99c0-553d7375a9ff
```

以前はここに次のようなsecretのエントリーがあり、

```yaml
secrets:
- name: test-sa-token-XXXX
```

自動でTokenが作られていた様だが、現在はこれがなくTokenを作成してくれなくなったとの事。

https://qiita.com/momotaro98/items/1edb189f1a627a367d48

そこで、次の方法でSecretを作成する。

### Secret Tokenの作成

次の様なManifestを用意し、

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: test-sa-secret
  annotations:
    kubernetes.io/service-account.name: test-sa
type: kubernetes.io/service-account-token
```

適用する。

```sh
$ kubectl apply -f sa-secret.yaml 
secret/test-sa-secret created
```

ServiceAccountにTokenができた様子。

```sh
$ kubectl describe sa/test-sa
Name:                test-sa
Namespace:           default
Labels:              <none>
Annotations:         <none>
Image pull secrets:  <none>
Mountable secrets:   <none>
Tokens:              test-sa-secret
Events:              <none>
```

実際のSecretの内容を見てみるとTokenができている。

```
$ kubectl get secret/test-sa-secret -o yaml
```
```yaml
apiVersion: v1
data:
  ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCakNDQWU2Z0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwdGFXNXAKYTNWaVpVTkJNQjRYRFRJMU1ERXhPVEF3TVRreE1Wb1hEVE0xTURFeE9EQXdNVGt4TVZvd0ZURVRNQkVHQTFVRQpBeE1LYldsdWFXdDFZbVZEUVRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTndhCnNaaXR4Z3ZkRmNSVExNZERrRUpEMmNTVDkvem9XWnNGMGV3T1dEVVFZMVlwbmF3bUlBTG9HSlpyaUZTQktFeTcKNnhwQVJoMlhJQ29JbnFYN1R4UGljZXRxMjJ5b2k2RFNhVXY1S3p0TlFRd2tnWjY5Z3MxUWt5T2JNN2pTTmtQTApnUmF5NHJOYXNtWnE0NFdhQlZUc0VlcDdKOTNTam1aNnY3bzhycUZYUWlrbVlJUXhQcnpUQmFPZVF4Y2JuYk91CnViMW5teW5ENmMyNFVJdDc2ay92U1R0Z3I4aEdycXZHZ0NRSGJGL1VMaHhvcG5nT2MrekdsREs5WEx1NFN2VHcKMmZpVGVnYm01Y3g1TGR0amt1ak5kMXMxaXlCbnFJRWFVMGQ5S2ttaDNTbUhSYldGZUV0MGo3U2h0RmQzY3dUZwpNQzhZUkpZNVZKdEJGSFJlTlhVQ0F3RUFBYU5oTUY4d0RnWURWUjBQQVFIL0JBUURBZ0trTUIwR0ExVWRKUVFXCk1CUUdDQ3NHQVFVRkJ3TUNCZ2dyQmdFRkJRY0RBVEFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQjBHQTFVZERnUVcKQkJTYTFMNkRIaktVL200T1lZdUdqK2wwNjFiZy96QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUF4NkM1QkI2VgpsS2tFRm5KZkxuSmU5cWd5WmRTcWFqUEJycE9qN0dQbzRXYTA2ZjVJRHp5V1g1NHB4ZVVDdC9heENEcXVRbUR4CnlCczZ4VGg0cU9tM20vN0p3TGRPK2tKbXJaREplbFRyK3pKK1Q1blJ0Ti9ZK0tiV25MRXkvd3BiNm5VWWw2UUEKczlQbHAzaVZESWJSOE96QkQrWXNFVElQRlVtSU1QWm5aekE2L3JreGprTEhCc041L3VDMnNJU3laQ0txOEk0RgpPclZCdHlOUG4yVGJLSmVablBmZnNaOGppT0JWRS95Zm1OMWw5QTNnVzVjbUlPOE1LZ3dNNkVJbjVKM2FwcmRhCmZ3WTJ6ZlFmU2xFdFhlY3hqSHFiSEdDdFFZcFJRVWV0d2NEeGxXOTFBT0Jjb0FZWDdqdk5VQmtlOGY4K2g0cGEKRXB6cHNoRDlwUEE3NEE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
  namespace: ZGVmYXVsdA==
  token: ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklrZDFZWFpqVmxwMFlWOXFVRVJMT1hBdFJYUjFUVVZqV1ZWdFdFSlVieTEyYlVnNWJVNVVTa0ZwUlRRaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJblJsYzNRdGMyRXRjMlZqY21WMElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVibUZ0WlNJNkluUmxjM1F0YzJFaUxDSnJkV0psY201bGRHVnpMbWx2TDNObGNuWnBZMlZoWTJOdmRXNTBMM05sY25acFkyVXRZV05qYjNWdWRDNTFhV1FpT2lKa05qWXhZV1ppTmkwell6SmtMVFJrTW1FdFlXWXpaaTAwT0RVNU1XWXlPVGRrTW1VaUxDSnpkV0lpT2lKemVYTjBaVzA2YzJWeWRtbGpaV0ZqWTI5MWJuUTZaR1ZtWVhWc2REcDBaWE4wTFhOaEluMC5kVjBYRV90Z3RrRGhHOW9rUmsxc25wMUFFbkROZU0wMWJxOUdBaUxGd3FFSzBGZ0oycVk2TjRUR1M4S0RXSFRUUGZLbFprX0hpZE5jdzZKckdma3FWY09MRTVnbzhpRHJQSk1QUXgtcHF5OHdqNXZxU0RkQU9UUHVYOEg2bkkyLXRWRXJ1WWhwUGUtXzVqWEpCTklFQTZCNnJTbzRyYkR3QUozTzZnSEYtSTVXbHY3bFllcUlYUUJUNkxXcXlIU2VLRTZaMWowS1FZVVk5ZXp3cldjNE44Zy1vY1E3QXVYZE13S2hzcTYzZUhlOEM5b3NrUFFkQnpTQnZrQ1JpMVJibEJxWHR1aTE0bzRzajVia1ktUW84LTZFa01DdGNHZWlheGdIY29SNGE1Rk1sMGFLRFI1VjJKak5XQUJTOHREc2IzX3BlUHJEbkNtZmRlUk5MWVFLOGc=
kind: Secret
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Secret","metadata":{"annotations":{"kubernetes.io/service-account.name":"test-sa"},"name":"test-sa-secret","namespace":"default"},"type":"kubernetes.io/service-account-token"}
    kubernetes.io/service-account.name: test-sa
    kubernetes.io/service-account.uid: d661afb6-3c2d-4d2a-af3f-48591f297d2e
  creationTimestamp: "2025-01-28T01:07:15Z"
  labels:
    kubernetes.io/legacy-token-last-used: "2025-01-28"
  name: test-sa-secret
  namespace: default
  resourceVersion: "1152"
  uid: 48461965-40a1-4356-99d8-0360467bd408
type: kubernetes.io/service-account-token
```

data.tokenはBase64エンコードされているのでデコードすると実際のTokenが得られる。

とりあえずコピペで行うと…

```sh
$ echo -n 'ZXlKaGJHY2lPaUp.....<snip>' | base64 -d
eyJhbGciOiJ.....<snip>
```

後でCredential設定で使用するので環境変数に格納しておく。ワンライナーで書くと次の感じ。

```sh
$ TEST_SA_TOKEN=$(kubectl get secret/test-sa-secret -o jsonpath='{.data.token}' | base64 -d)
```
```sh
$ echo $TEST_SA_TOKEN
eyJhbGciOiJ.....<snip>
```

### Contextの設定

上記までの操作でContext自体は出来ている模様。

```sh
$ kubectl config get-contexts 
CURRENT   NAME              CLUSTER          AUTHINFO         NAMESPACE
          docker-desktop    docker-desktop   docker-desktop   
*         minikube          minikube         minikube         default
          test-sa-context   minikube         test-sa  
```

しかしこのContextに切り替えても

```sh
$ kubectl config use-context test-sa-context
Switched to context "test-sa-context".
```

ログインしてない/CredentialにTokenが設定されていないので拒否される。

```sh
$ kubectl get po
error: You must be logged in to the server (Unauthorized)
```

一旦、デフォルトのminikubeに戻す

```sh
$ kubectl config use-context minikube
Switched to context "minikube".
```

先ほど環境変数に入れていたTokenをCredentialに設定

```sh
$ kubectl config set-credentials test-sa --token $TEST_SA_TOKEN  
User "test-sa" set.
```

### Contextを切り替え確認

```sh
$ kubectl config use-context test-sa-context
Switched to context "test-sa-context".
```

Podを取得してみる

```sh
$ kubectl get po
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:default:test-sa" cannot list resource "pods" in API group "" in the namespace "default"
```

作成したServiceAccountで認証はされたがPodを取得する権限がないのでエラーとなる。

この先はRBACなどの話なのでここでは触れない(RBAC設定する前に`kubectl config use-context minikube`しよう)。

次の記事を参考にすると良さそう（というかこの記事を読んでServiceAccountとTokenの仕様が変わったので調べながらこの記事を書いている）。

https://cstoku.dev/posts/2018/k8sdojo-20/