GitHub Actions とはてなブログの連携テストついでの記事。
k3s の ServiceAccount Token Issuer を独自 URL に変える
LAN 内の private な k3s を、外部の OIDC verifier と federate させたかった。WIF Provider は受信した JWT の iss claim を文字列照合するので、k8s デフォルトの https://kubernetes.default.svc.cluster.local を相手に渡すのは見栄えも悪いし将来 IdP を再利用しづらい。https://oidc.<自分のドメイン> に揃える方法と、その過程で知った小ネタをメモしておく。
kubectl get --raw が便利
最初に「今 apiserver は何を返してるか」を確認したい。/.well-known/openid-configuration を curl で叩こうとすると以下の 2 段でハマる:
$ curl https://192.168.10.110:6443/openid/v1/jwks
curl: (60) SSL certificate problem: self signed certificate in certificate chain
$ curl -sk https://192.168.10.110:6443/openid/v1/jwks
{"kind":"Status","status":"Failure","message":"Unauthorized","code":401}
- TLS: k3s が自前 CA で署名した cert を持ってる。Mac の trust store にはない
- 認証:
/openid/v1/jwksは anonymous には開放されていない (k3s default ではsystem:serviceaccountsgroup のみ accept)
-k で TLS をバイパスして、さらに kubectl create token default | xargs -I {} curl -sk -H "Authorization: Bearer {}" ... みたいに SA token を投げれば見れるが面倒。
実は kubectl get --raw 一発で解決する:
kubectl get --raw /.well-known/openid-configuration | jq . kubectl get --raw /openid/v1/jwks | jq .
kubeconfig の certificate-authority-data で CA verify を解決、client-cert/key で認証を解決してくれる。apiserver の任意エンドポイントを叩けるみたいで、知らなかった。
デフォルトはこう:
{ "issuer": "https://kubernetes.default.svc.cluster.local", "jwks_uri": "https://192.168.10.110:6443/openid/v1/jwks", "response_types_supported": ["id_token"], "subject_types_supported": ["public"], "id_token_signing_alg_values_supported": ["RS256"] }
issuer を変える
kube-apiserver には --service-account-issuer というフラグがあり、ここに渡した値が発行 JWT の iss claim に入る。
k3s は kube-apiserver を別プロセスにせず k3s server に embed しているので、apiserver の独立 systemd unit も静的 manifest (/etc/kubernetes/manifests/kube-apiserver.yaml) も存在しない。vanilla kubeadm 用の情報を探すと混乱する。k3s では k3s server 経由で apiserver の引数を渡す。
sudo EDITOR=vi systemctl edit --full k3s
ExecStart= ブロックの中、既存の '--disable=servicelb' \ の下あたりに 2 行追加:
'--kube-apiserver-arg=service-account-issuer=https://oidc.your-domain.example.com' \ '--kube-apiserver-arg=service-account-issuer=https://kubernetes.default.svc.cluster.local' \
1 行目が「新規 token に署名する時の iss」。2 行目はデフォルト issuer を「検証時の accept リスト」に残しておく保険。--service-account-issuer は複数指定可能で、kube-apiserver の公式仕様にこう書かれている:
When this flag is specified multiple times, the first is used to generate tokens and all are used to determine which issuers are accepted.
なので k3s 再起動直後に「まだ rotation していない古い token を持った Pod」がいても、その token は accept リストに残った旧 issuer で検証され、TokenReview API がコケない。 反映:
sudo systemctl daemon-reload sudo systemctl restart k3s
確認
kubectl get --raw /.well-known/openid-configuration | jq .issuer # => "https://oidc.your-domain.example.com"
念のため実際に発行された JWT を見て iss を確認:
kubectl create token default -n default | cut -d. -f2 | base64 -d 2>/dev/null | jq .iss # => "https://oidc.your-domain.example.com"
ハマったポイント: discovery doc に 1 つしか出ない
--service-account-issuer を 2 回渡したのに、/.well-known/openid-configuration のレスポンスには 1 個目 (primary) しか含まれない。
{ "issuer": "https://oidc.your-domain.example.com", ... }
「旧 issuer 消えた?」と一瞬焦るが正常動作。OpenID Connect Discovery 1.0 仕様で issuer フィールドは単一値と決まっている。2 個目は apiserver 内部の accept リストに乗るだけで外部からは見えない (公示はしないが受理はする)。
config.yaml で書く手もあった
k3s には /etc/rancher/k3s/config.yaml という公式のオプション設定ファイルがある。あれば k3s server 起動時に自動で読まれる。systemd unit 直書きより、こっちの方が k3s upgrade で unit が再生成されたときに編集が消えない、というメリット。
# /etc/rancher/k3s/config.yaml kube-apiserver-arg: - service-account-issuer=https://oidc.your-domain.example.com - service-account-issuer=https://kubernetes.default.svc.cluster.local
systemd unit から該当行を消して systemctl restart k3s すれば移行完了。CLI 引数と config.yaml は merge されるので、混在状態でも動く。
今回は systemd unit に直接書いてしまったが、後から config.yaml に寄せても良いし、寄せなくても害はない。
まとめ
LAN 内 k8s を OIDC IdP として外部 federation に登録したい時の最小手順:
- apiserver に
--service-account-issuer=<新 URL>を渡す - 旧 issuer も並列で渡しておく (既存 token 互換)
kubectl get --raw /.well-known/openid-configurationで確認kubectl create token defaultで実際の JWT のissを確認
これで k8s 側完了。外部 IdP 側 (GCP WIF Provider 等) に同じ issuer URL を登録すれば federation 成立。
なお issuer URL を変えても /openid/v1/jwks を internet 公開する必要は別問題。GCP WIF の場合は WIF Provider 設定の oidc.jwks_json に JWKS を静的に注入できるので、LAN 専用クラスタのまま federation を通せる。
参考
- k3s Server CLI Reference (
--kube-apiserver-arg): https://docs.k3s.io/cli/server - k3s Installation Configuration (
/etc/rancher/k3s/config.yaml): https://docs.k3s.io/installation/configuration - Kubernetes kube-apiserver Reference (
--service-account-issuer): https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/ - Kubernetes Service Account Issuer Discovery: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#service-account-issuer-discovery
- OpenID Connect Discovery 1.0 : https://openid.net/specs/openid-connect-discovery-1_0.html