Bound Service Account Tokenとは何か

## はじめに

Bound Service Account Tokenは、従来のService Account Tokenの問題を解消し、Kubernetes APIサーバへのアクセスだけでなく広い用途で利用されることを想定したService Account Tokenです。

2017年から検討が始まり、1.13でアルファ、v1.21でようやくベータに昇格する見込みです。
https://github.com/kubernetes/kubernetes/pull/95667

Bound Service Account Tokenを使った機能については[以前にエントリ](https://qiita.com/hiyosi/items/35c22507b2a85892c707)を書きましたが、このエントリではBound Service Account Tokenそのものに焦点を当てて、どのようなものでどのような経緯で誕生したのか、これまでの検討内容を踏まえて整理してみたいと思います。

## Bound Service Account Tokenとは

Bound Service Account Tokenとは、従来のService Account Tokenにはない以下の3つの紐付け情報を持つJWTのデータでService AccountのIdentityを表現しています。

1. **Audience Binding**
2. **Time Binding**
3. **Object Binding**

Audience Bindingについて、Tokenの受け取り手を`aud`(audience)クレームにセットすることでTokenのスコープを明確にします。Tokenを受け取る側は`aud`クレームに自身のIdentityが含まれていることを確認し、含まれていない場合は拒否しなければなりません。

Time Bindingについて、`exp`(expiration time),  `nbf`(not before),  `iat`(issued at)の3つのクレームでTokenが有効期間を表現します。(`iat` <= now & `nbf` <= now <= `exp`)。有効期限が切れたTokenは無効になるため、Tokenのローテーションが必要です。

Object Bindingについて、発行されたTokenはPodなどのKubernetesのObjectに紐付けることができます。Objectに紐付いたTokenはObjectが存在する間のみ有効となります。

つまり、Bound Service Account Tokenは既存のTokenと同様にService AccountのIdentityですが、Object(Podなど)や用途毎に異なるクレデンシャルとなります。

これらの情報を紐付けることにより、意図しないコンテキストでTokenが流用されるリスクを軽減することができます。

ペイロードの例

    {
      "iss": "https://example.com/some/path",
      "sub": "system:serviceaccount:default:default,
      "aud": [
        "https://kubernetes.default.svc"
      ],
      "exp": 24412841114,
      "iat": 1516841043,
      "nbf": 1516841043,
      "kubernetes.io": {
        "serviceAccountUID": "c0c98eab-0168-11e8-92e5-42010af00002",
        "boundObjectRef": {
          "kind": "Pod",
          "apiVersion": "v1",
          "uid": "a4bb8aa4-0168-11e8-92e5-42010af00002",
          "name": "pod-foo-346acf"
        }
      }
    }


このTokenは以下のフローでPodに渡されます。

![スクリーンショット 2021-01-29 8.49.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15164/c8d0ac00-0b92-913d-47da-ddc4a36cb2fb.png)
https://static.sched.com/hosted_files/kccna18/33/KubeCon_18_Container_Identity_WG_Deep_Dive.pdf

## Service Account TokenとBound Service Account Tokenの比較

Bound Service Account Tokenと従来のService Account Tokenを比較すると以下のような違いがあります。
 
|   | 発行者 | 署名鍵 | 検証鍵 | スコープ(宛先) | 期限 | Podへの 受け渡し|
|:--|:------|:------|:------|:----|:-----|:--------------|
| Service Account Token | Token Controller(kube-controller-manager) | `--service-account-private-key-file`(kube-controller-manager) | `--service-account-key-file`(kube-apiserver) | なし | なし | Secret |
| Bound Service Account Token | kube-apiserver | `--service-account-signing-key-file`(kube-apiserver) | `--service-account-key-file`(kube-apiserver) | あり | あり | ProjectedVolume |

## Bound Service Account Token登場の背景
### 他のサービスと通信するためのIdentityの需要

Kubernetes上で動作するコンテナは多くの場合、他のインフラストラクチャサービス、Secret Vaultのようなサービス、ローカルコンテキストの他のサービス、より広いコンテキストのサービス、などと連携して動作します。多くのシステムではアクセス制御が行われているため、コンテナは通信相手に自身のIdentityを主張する必要があります。

当時、Podが(ユーザによるブートストラップなしに)利用できるIdentityとしてService Account Tokenを含めて、以下のものが利用できましたが課題もありました。

**Node Certificates**: (kubeletに設定されるような)Node Certificateはスコープが広すぎる。PodやContainerといったより細かい単位で識別できるIdentityが必要。

**Service Account Tokens**: Service Account Tokenについては、期限がないため永久に利用できることや、トークンに対するスコープが設定されていないため、Kubernetes APIサーバ以外のシステムで利用することはセキュリティ上難しい。さらに、Service Account TokenはSecretリソースを使ってPodに渡す仕組みになっており、Secretを参照できる権限をもっているコンポーネントがService Accountと同等の権限を持ててしまう等の課題がある。

**OIDC Tokens**: 外部のOIDC Providerから発行されるToken(ID Tokenなど)は広い範囲での用途に利用できますが、PodにTokenを渡すことが難しい。

### Container Identity Working Group

そこで、必要とされるIdentityシステムを検討する**Container Identity Working Group**が発足しました。より簡単により安全にKubernetesの外のシステムやKubernetes内の他のPodと通信するためのIdentityシステムについて検討するWGです。WGにはKubernetes以外のプロジェクトからもメンバーが参加していました。当時の議事録を見るとこのときからSPIFFEやIstio、Athenzといったプロジェクトの名前が出てきています。

![スクリーンショット 2021-01-31 0.37.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15164/5623c26a-ddf8-4fc4-90c4-935c0aa7688b.png)

WGでは、Identityのフォーマット(JWT,X.509証明書など)についてや、Identityの単位(Pod、Container、Service Account)、どのように配布するのか(SPIFFE Workload APIやFlexVolumeなど)について議論されていました。

WGは最終的に14ヶ月継続しましたが、キックオフから4ヶ月後にはKubernetes APIサーバのOIDC化の話や**Bound Service Account Token**の仕組みが提案されていました。
一方X.509証明書についてはProposalなどは作られませんでした。当時はKubernetesのCSR APIは十分に公開されていたものではなかったため、Istioなどのサービスメッシュの利用が選択肢となったようです。

PodがどのようにTokenを取得するかについては、FlexVolumeやCSI pluginを使った方法なども検討が進んでいましたが、Bound Service Account TokenをVolume ProjectionでPodに渡す方法が提案されました。

## Bound Service Account Token提供までのステップ

Bound Service Account TokensとService Account Token Volumesの2つの提案をベースに以下のような流れで作業が進められました。
現在は必要な機能も実装され、次のKubernetesバージョンではBound Service Account Tokenの機能がbetaに昇格し、デフォルトで有効化される見込みです。

- **TokenRequest API**の追加 
    - Feature Gate: **TokenRequest** (`v1.10`でalpha, `v1.12`でbeta, `v1.20`でGA)
    - audience, expiration time, objectに紐付いたTokenを発行できるAPIを追加
- **TokenReview API**の拡張
    - FeatureGate: **TokenReview**
    - `audience`パラメータの検証をサポート
- **TokenRequestProjection**の実装
    - FeatureGate: **TokenRequestProjection** (`v1.11`でalpha, `v1.12`でbeta, `v1.20`でGA)
    - Projected Volumeを使ってTokenをPodにマウントする
- **RootCAConfigMap**の追加(v1.13でalpha, v1.20でbeta)
    - FeatureGate: **RootCAConfigMap**
    - Kubernetes APIサーバのサーバ証明書を検証するためのCA証明書バンドルをConfigMapですべてのNamespaceに配布する
- **BoundServiceAccountVolume**の追加
    - FeatureGate: **BoundServiceAccountVolume** (`v1.13`でalpha)
    - **TokenRequest**, **TokenRequestProjection**の有効化が必要。
    - 有効化するとこれまでのService Account Tokenに変わってBound Service Account TokenがPodに渡されるようになる
- **OIDC Discovery**機能の提供
    - FatureGate: **ServiceAccountIssuerDiscovery** (`v1.18`でalpha, `v1.20`でbeta)
    - Discoveryエンドポイントに加えてBound Service Account Tokenの署名検証鍵を公開するJWKSエンドポイントが提供される
    - Proposal docからKEPへの移行のタイミングでこの機能についての記述が消えている？
      - https://github.com/kubernetes/community/pull/2314
- すべてのワークロードでTokenのローテーションをサポートしたライブラリに更新
    - Go: >= v0.15.7
    - Python: >= v12.0.0
    - Java: >= v9.0.0
    - Javascript: >= v0.10.3
    - Ruby: master branch
    - Haskell: v0.3.0.0
    - `--service-account-extend-token-expiration`フラグによる一時的な延命処置も提供
- Bound Service Account Tokenのデフォルト有効化

### 関連機能について補足

上記で出てきたAPIやFeatureGateの機能について、補足を交えてそれぞれの関係性を整理してみます。

**TokenReview API**はKubernetes v1.3でベータとして追加された機能で、Tokenの検証をAPIサーバに依頼することができます。これにより、各アプリケーションのコードにJWTを検証する処理を組み込まずともAPIを呼び出すだけでよくなります。

参考: https://qiita.com/hiyosi/items/b6a1d07b30ff7dff1961

APIサーバの負荷が気になる場合など、アプリケーションで検証したい場合にはv1.20でbetaに昇格した**ServiceAccountIssuerDiscovery**の機能を利用することができます。これはOIDCでID Tokenを検証するようなフローでTokenを検証することができます。APIサーバがJWTの署名検証鍵をJWKSet形式で公開してくれるため、アプリケーションはその検証鍵を取得してJWTの署名を検証することができます。

参考: https://qiita.com/hiyosi/items/43ddb0ff9b83070d95a8

**TokenRequest API**はAPIサーバにBound Service Account Tokenの発行を依頼することができます。
リクエストパラメータとして`audience`や`expirationSeconds`を指定してTokenを発行してもらうことができます。
**TokenRequestProjection**はFeatureGateの名前であり、**TokenRequest API**を使って発行したBound Service Account TokenをProjectedVolumeを使ってPodに渡すことができます。

参考: https://qiita.com/hiyosi/items/feec917d502af8ad8863

**BoundServiceAccountTokenVolume**はこれまで**TokenRequestProjection**の機能などでオプショナルに利用できていたBound Service Account Tokensをデフォルトとして、これまでのService Account Tokenの代わりにPodに渡す機能です。

参考: https://qiita.com/hiyosi/items/35c22507b2a85892c707

もう一つ、**Projected Service Account Token**はいくつかのドキュメントやIssueなどで見かける言葉ですが、TokenRequestProjectionの機能を使ってPodに渡されたBound Service Account Tokenを表現している言葉と認識しています。**SecureServiceAccountTokenVolume**という単語も見かけますが、こちらは**BoundServiceAcccountVolume**を指しているのではないかと思われます。(もし、別のものを指していたらご指摘ください) 

## 既存のService Account Tokenについて

今後はBound Service Account TokenがデフォルトのTokenとなりますが、Token Controllerが生成している既存のService Account Tokenが生成されなくなったり、即時無効化されるようなことはありません(`--service-account-private-key-file` から鍵を除外したりしない限り)。

SIG-Auth Meetingでは **BoundServiceAccountVolume**がGAになったらToken Controlelrの機能を取り除きたという話が挙がっていたようですが、これにはService Account TokenのSecretをウォッチしているようなWorkloadがいなくなる必要があります。

廃止に向けての計画ですが、以下が関連するissueのようですが現時点ではまだ動きがないようです。 

- https://github.com/kubernetes/kubernetes/issues/77599
- https://github.com/kubernetes/kubernetes/issues/77600

近いうちに既存Service Account Tokenが非推奨な旨やTokenRequest APIへの移行がアナウンスされるのではないかと思いますが、こちらは引き続き動向のチェックが必要そうです。

自身のクラスタで既存のService Account Tokenがどの程度使われているのかという情報は `serviceaccount_legacy_tokens_total`というメトリクスを取得することで計測できます。

## まとめ

- 既存のService Account Tokenにはセキュリティの課題がある(使い回し、失効が難しい)
- Bound Service Account Tokenという新しいTokenに切り替えが進められている
- **BoundServiceAccountVolume**を有効にするとPodに渡されるTokenがデフォルトでBound Service Account Tokenになる
- Bound Service Account Tokenは有効期限があるため、クライアントは再発行または再読み込み等のローテーション対応が必要
- 既存のService Account Tokenはしばらくは使えそうだが、将来的には無くなる見込み

## 今後注目したい機能

JWT以外のフォーマットとして、Service AccountレベルのX.509証明書をPodにマウントするKEPが作られています。もしかしたら、各Service Meshで実装されている証明書発行の機能がこちらに置き換えられていくかもしれません。さらにSPIFFEに準拠してくれると、Identityに互換性を持たせることができて異なるService Meshが混在できるようになる？などの想像(妄想)も広がります。

https://github.com/kubernetes/enhancements/pull/1388

## 関連リンク

KEP: Bound Service Account Tokens

- https://github.com/kubernetes/enhancements/blob/master/keps/sig-auth/1205-bound-service-account-tokens/README.md
- https://github.com/kubernetes/community/blob/master/contributors/design-proposals/auth/bound-service-account-tokens.md

KEP:  Service Account Token Volumes

- https://github.com/kubernetes/enhancements/blob/c26cc57548c1f9ca0abb51f57d2b1c6569b1baa6/keps/sig-storage/20180515-svcacct-token-volumes.md
- https://github.com/kubernetes/community/blob/master/contributors/design-proposals/storage/svcacct-token-volume-source.md

Draft: Kubernetes Identity Design Discussion

- [https://docs.google.com/document/d/1no8rYJ_nzhMeXYLL6JLjVSDtrj7pd6CBBfE3cPGOv8g/edi](https://docs.google.com/document/d/1no8rYJ_nzhMeXYLL6JLjVSDtrj7pd6CBBfE3cPGOv8g/edit)

Draft:  Enabling Container Identity via Service Accounts

- [https://docs.google.com/document/d/1Ki7K9rAC5XIAgyYJXUis1UpgwmtRq2Xz68mF3_LggEY/edit](https://docs.google.com/document/d/1Ki7K9rAC5XIAgyYJXUis1UpgwmtRq2Xz68mF3_LggEY/edit)

Meeting Notes: SIG-Auth (2017)

- https://github.com/kubernetes/community/blob/master/sig-auth/archive/meeting-notes-2017.md

Meeting Notes: SIG-Auth (2018)

- https://github.com/kubernetes/community/blob/master/sig-auth/archive/meeting-notes-2018.md

Meeting Notes: SIG-Auth (2019)

- https://github.com/kubernetes/community/blob/master/sig-auth/archive/meeting-notes-2019.md

Meeting Notes: SIG-Auth (2020)

- https://docs.google.com/document/d/1woLGRoONE3EBVx-wTb4pvp4CI7tmLZ6lS26VTbosLKM/edit# 

Meeting Notes: Container Identity Working Group

- [https://docs.google.com/document/d/1uH60pNr1-jBn7N2pEcddk6-6NTnmV5qepwKUJe9tMRo/edit](https://docs.google.com/document/d/1uH60pNr1-jBn7N2pEcddk6-6NTnmV5qepwKUJe9tMRo/edit)