はじめに
Kubernetes では ServiceAccount を用いてアプリケーションやユーザーを認証する方法があります。本記事では、ServiceAccount のトークン発行方法やトークンの仕組み、実際に利用するときのコード例などをまとめて紹介します。具体的には:
-
kubectl create token
コマンドを使った発行 - REST API(curl)を使った発行
- Go言語の
client-go
を使った実装例 - トークンの形式や使用例
- 応用的な kubeconfig 生成方法
など、幅広い観点で詳しく解説しています。本記事の内容は主に kind 環境を用いて動作検証しましたので、「手元で気軽に試してみたい」という方にも参考になるでしょう。
この記事で利用した環境
本記事で紹介するコマンドやAPI呼び出しは、以下のバージョンで動作確認を行いました。
- Kubernetes: 1.32.0
- kind: v0.26.0 go1.23.4 darwin/arm64
備考: すべて、kindで作成したクラスタ上で動作を確認しています。
ServiceAccountトークンAPIのOpenAPI仕様
まず、Kubernetesの公式APIドキュメントが定義している ServiceAccountの /token
サブリソース について整理しましょう。これにより、TokenRequest
オブジェクトを使ってAPI経由でトークンを発行できます。
以下は、OpenAPI仕様を kubectl
で取得して /api/v1/namespaces/{namespace}/serviceaccounts/{name}/token
に関するエンドポイントを確認する例です。
kubectl get --raw '/openapi/v3/api/v1' | jq '.paths["/api/v1/namespaces/{namespace}/serviceaccounts/{name}/token"]'
すると、以下のような JSON が返ってきます。
{
"post": {
"tags": [
"core_v1"
],
"description": "create token of a ServiceAccount",
"operationId": "createCoreV1NamespacedServiceAccountToken",
"requestBody": {
"content": {
"*/*": {
"schema": {
"$ref": "#/components/schemas/io.k8s.api.authentication.v1.TokenRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/io.k8s.api.authentication.v1.TokenRequest"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
"$ref": "#/components/schemas/io.k8s.api.authentication.v1.TokenRequest"
}
},
"application/yaml": {
"schema": {
"$ref": "#/components/schemas/io.k8s.api.authentication.v1.TokenRequest"
}
}
}
},
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/io.k8s.api.authentication.v1.TokenRequest"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
"$ref": "#/components/schemas/io.k8s.api.authentication.v1.TokenRequest"
}
},
"application/yaml": {
"schema": {
"$ref": "#/components/schemas/io.k8s.api.authentication.v1.TokenRequest"
}
}
}
},
"202": {
"description": "Accepted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/io.k8s.api.authentication.v1.TokenRequest"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
"$ref": "#/components/schemas/io.k8s.api.authentication.v1.TokenRequest"
}
},
"application/yaml": {
"schema": {
"$ref": "#/components/schemas/io.k8s.api.authentication.v1.TokenRequest"
}
}
}
},
"401": {
"description": "Unauthorized"
}
},
"x-kubernetes-action": "post",
"x-kubernetes-group-version-kind": {
"group": "authentication.k8s.io",
"version": "v1",
"kind": "TokenRequest"
}
},
"parameters": [
{
"name": "dryRun",
"in": "query",
"description": "When present, indicates that modifications should not be persisted...",
"schema": {
"type": "string",
"uniqueItems": true
}
},
{
"name": "fieldManager",
"in": "query",
"description": "fieldManager is a name associated with the actor or entity...",
"schema": {
"type": "string",
"uniqueItems": true
}
},
{
"name": "fieldValidation",
"in": "query",
"description": "fieldValidation instructs the server on how to handle objects...",
"schema": {
"type": "string",
"uniqueItems": true
}
},
{
"name": "name",
"in": "path",
"description": "name of the TokenRequest",
"required": true,
"schema": {
"type": "string",
"uniqueItems": true
}
},
{
"name": "namespace",
"in": "path",
"description": "object name and auth scope...",
"required": true,
"schema": {
"type": "string",
"uniqueItems": true
}
},
{
"name": "pretty",
"in": "query",
"description": "If 'true', then the output is pretty printed...",
"schema": {
"type": "string",
"uniqueItems": true
}
}
]
}
このOpenAPI仕様を読み解くと、Kubernetes は /api/v1/namespaces/{namespace}/serviceaccounts/{name}/token
というエンドポイントで TokenRequest
を受け取り、新しいトークンを発行していることがわかります。
kubectlを使ったToken取得例
もっとも簡単な取得方法として、kubectl create token
コマンドを使う例を紹介します。以下のコマンドを実行するだけで、指定した ServiceAccount の新しいトークンが発行されます。
kubectl create token default --duration=1h
ここでは、namespace=default
、ServiceAccount=default
に対して、1時間(--duration=1h
)有効なトークンを要求しています。
大変シンプルで使いやすいアプローチです。
curlを使ったToken取得例
kubectl
以外にも、curl でAPIサーバーに直接リクエストを送り、ServiceAccountトークンを発行することが可能です。
ただし、APIサーバーへ直接アクセスするためには、すでに何らかの 認証トークン を所持している必要があります。以下では、
- ServiceAccount
test-sa
にクラスター管理権限を与える - 取得したトークンを使って別のトークンを発行する
という手順を例として解説します。
1. 事前準備
# ServiceAccountの作成
kubectl create serviceaccount test-sa
# clusterrolebindingの作成 (例: cluster-adminを付与)
kubectl create clusterrolebinding test-sa-binding \
--clusterrole=cluster-admin \
--serviceaccount=default:test-sa
2. curl を使ってトークン取得APIを呼び出す
# 1時間(3600秒)のトークンを新規発行する例
curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $(kubectl create token test-sa)" \
-d '{"spec":{"expirationSeconds":3600}}' \
https://127.0.0.1:xxxx/api/v1/namespaces/default/serviceaccounts/default/token --insecure
上記コマンドを実行すると、以下の形式の JSON レスポンスが返ってきます。"status.token"
フィールドに JWT 形式のトークンが格納されていれば発行成功です。
{
"kind": "TokenRequest",
"apiVersion": "authentication.k8s.io/v1",
"metadata": {
"name": "default",
"namespace": "default",
"creationTimestamp": "2025-01-17T05:42:50Z",
"managedFields": [
{
"manager": "curl",
"operation": "Update",
"apiVersion": "authentication.k8s.io/v1",
"time": "2025-01-17T05:42:50Z",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:spec": {
"f:expirationSeconds": {}
}
},
"subresource": "token"
}
]
},
"spec": {
"audiences": [
"https://kubernetes.default.svc.cluster.local"
],
"expirationSeconds": 3600,
"boundObjectRef": null
},
"status": {
"token": "xxx.yyy.zzz",
"expirationTimestamp": "2025-01-17T06:42:50Z"
}
}
Credential plugins
Kubernetesの公式クライアント(k8s.io/client-go
)と、それを使用する kubectl
や kubelet
などのツールは、外部コマンドを実行してユーザーの認証情報を取得する仕組みをサポートしています。
これを Credential plugins と呼びます。LDAP や OAuth2、SAML など、client-go
がネイティブにサポートしていない認証方式でも外部コマンドを組み合わせることで連携が可能です。
kubeconfigの例
Credential plugins を活用すると、kubeconfig
の users
セクションに「どのコマンドを呼ぶか」「どの引数を渡すか」などを記述できます。以下はEKS(Elastic Kubernetes Service)でよく使われる形式の例です。
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: xxxx
server: https://xxxx.gr7.ap-northeast-1.eks.amazonaws.com
name: arn:aws:eks:ap-northeast-1:xxxx:cluster/some-eks-cluster-name
contexts:
- context:
cluster: arn:aws:eks:ap-northeast-1:xxxx:cluster/some-eks-cluster-name
user: arn:aws:eks:ap-northeast-1:xxxx:cluster/some-eks-cluster-name
name: arn:aws:eks:ap-northeast-1:xxxx:cluster/some-eks-cluster-name
current-context: arn:aws:eks:ap-northeast-1:xxxx:cluster/some-eks-cluster-name
kind: Config
preferences: {}
users:
- name: arn:aws:eks:ap-northeast-1:xxxx:cluster/some-eks-cluster-name
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- --region
- ap-northeast-1
- eks
- get-token
- --cluster-name
- some-eks-cluster-name
- --output
- json
command: aws
env:
- name: AWS_PROFILE
value: xxxx
interactiveMode: IfAvailable
provideClusterInfo: false
EKSを例にすると、以下コマンドの出力が exec
によって呼び出され、認証用トークンとして利用されます。
$ AWS_PROFILE=xxxx aws eks get-token --cluster-name some-eks-cluster-name --region ap-northeast-1 --output json
{
"kind": "ExecCredential",
"apiVersion": "client.authentication.k8s.io/v1beta1",
"spec": {},
"status": {
"expirationTimestamp": "2025-01-24T06:01:18Z",
"token": "k8s-aws-v1.xxxx"
}
}
token
フィールドに k8s-aws-v1.xxxx
という値が入り、この文字列が認証に使われます。実際には、この k8s-aws-v1.xxxx
を base64 デコードすると以下のような 署名付きURL が埋め込まれています。
https://sts.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=XXXXJPFRILKNSRC2W5QA%2F20200219%2Fus-xxxx-1%2Fsts%2Faws4_request&X-Amz-Date=20200219T155427Z&X-Amz-Expires=60&X-Amz-SignedHeaders=host%3Bx-k8s-aws-id&X-Amz-Signature=XXXf8f3285e320ddb5e683a5c9a405301ad76546f24f28111fdad09cf648a393
詳細は以下のドキュメントを参照してください。
Identity and Access Management - Amazon EKS
GoコードによるTokenRequest作成例
次に、Go 言語の Kubernetes クライアントライブラリである client-go
を使ってトークンを発行する方法を見てみましょう。内部的には先ほどのエンドポイント /api/v1/namespaces/{namespace}/serviceaccounts/{name}/token
に POST
する形になっています。
// CreateToken takes the representation of a tokenRequest and creates it.
// Returns the server's representation of the tokenRequest, and an error, if there is any.
func (c *serviceAccounts) CreateToken(ctx context.Context, serviceAccountName string, tokenRequest *authenticationv1.TokenRequest, opts metav1.CreateOptions) (result *authenticationv1.TokenRequest, err error) {
result = &authenticationv1.TokenRequest{}
err = c.GetClient().Post().
UseProtobufAsDefault().
Namespace(c.GetNamespace()).
Resource("serviceaccounts").
Name(serviceAccountName).
SubResource("token").
VersionedParams(&opts, scheme.ParameterCodec).
Body(tokenRequest).
Do(ctx).
Into(result)
return
}
このメソッドを呼び出すことで簡単に新しいトークンを取得できます。
kubernetes.io/service-account-token
のSecret
Kubernetes には、kubernetes.io/service-account-token
タイプの Secret リソースを通じて ServiceAccount トークンを管理する仕組みも存在します。マニフェストの例を示すと、以下のようになります。
apiVersion: v1
kind: Secret
metadata:
annotations:
kubernetes.io/service-account.name: test-sa
name: test-sa-token
type: kubernetes.io/service-account-token
この Secret が作成されると、data
フィールドに ServiceAccount トークンやクラスタの CA 証明書等が格納されます。
apiVersion: v1
data:
ca.crt: xxx
namespace: xxx
token: xxx
kind: Secret
metadata:
annotations:
kubernetes.io/service-account.name: test-sa
name: test-sa-token
namespace: default
type: kubernetes.io/service-account-token
この方式で発行されるトークンは 期限がなく、Secretを削除すれば即時に無効化できます。
Tokenの形式
ここでは、同じServiceAccount トークン
でも、どのように発行するかによって JWT の中身が異なる点に着目します。
内部クレーム(Claims)を確認すると、TokenRequest API を使った場合と kubernetes.io/service-account-token
タイプの Secret で発行されたトークンでは、構造が異なることがわかります。
/api/v1/namespaces/{namespace}/serviceaccounts/{name}/token
から取得したtoken
以下のように、kubernetes.io
以下に構造化された情報を持ち、exp
(有効期限) が設定されています。
Token header
------------
{
"alg": "RS256",
"kid": "2UZIz_6ZkOi7DiwG7ILvSNXYX8RUzWyCcXKgc-x8Fd0"
}
Token claims
------------
{
"aud": [
"https://kubernetes.default.svc.cluster.local"
],
"exp": 1737683684,
"iat": 1737680084,
"iss": "https://kubernetes.default.svc.cluster.local",
"jti": "1f035edf-429e-41fc-bb74-af89b3991485",
"kubernetes.io": {
"namespace": "projectsveltos",
"serviceaccount": {
"name": "projectsveltos",
"uid": "e049717e-92f3-4c1f-8147-ec8ae120bf66"
}
},
"nbf": 1737680084,
"sub": "system:serviceaccount:projectsveltos:projectsveltos"
}
kubernetes.io/service-account-token
type の Secret の .data.token
にあるtoken
一方、こちらは kubernetes.io/serviceaccount/namespace
のようにフラットなキーで格納されており、exp
が存在しません。
Token header
------------
{
"alg": "RS256",
"kid": "2UZIz_6ZkOi7DiwG7ILvSNXYX8RUzWyCcXKgc-x8Fd0"
}
Token claims
------------
{
"iss": "kubernetes/serviceaccount",
"kubernetes.io/serviceaccount/namespace": "projectsveltos",
"kubernetes.io/serviceaccount/secret.name": "projectsveltos",
"kubernetes.io/serviceaccount/service-account.name": "projectsveltos",
"kubernetes.io/serviceaccount/service-account.uid": "e049717e-92f3-4c1f-8147-ec8ae120bf66",
"sub": "system:serviceaccount:projectsveltos:projectsveltos"
}
exp
が無い = 有効期限が無期限なので、運用上の管理に注意が必要です。不要になったらSecretを削除すれば使えなくなる点が特徴です。
補足: ServiceAccount 再作成とトークンの破棄
Kubernetesでは、同じNamespaceかつ同じ名前のServiceAccountを削除して再作成すると、service-account.uid
が変わります。
したがって、発行済みトークンを無効化したい場合 は、ServiceAccountを削除して作り直すだけで該当トークンが利用不能になります。
補足: Webhook認証
上記でも例として出しましたが、KubernetesではWebhookトークン認証をサポートしている為、jwt形式ではないtokenが渡されたりする可能性があります.
k8s-aws-v1.xxxx
Tokenの有効性の確認方法
ServiceAccount トークンを検証する場合、よく使われるのが Token Review API です。
認証自体をアプリケーションで実装するのではなく、Kubernetesにトークンの検証を任せられるため、アプリケーションの開発・保守がシンプルになります。
もしくは、Token Review APIを利用せずに、そのtokenを利用して実際にk8sのapiを叩く様な場合もあります。
tokenの使い道
ServiceAccount トークンは Kubernetesクラスタへのアクセス に限らず、外部アプリケーションの認証や認可の仕組みにも活用できます。
ここでは具体的なユースケースとして、いくつかのアプリケーション例を挙げます。
k8sを認証基盤としてアプリに組み込む
1. Token Review API を利用したWebアプリ認証
ユーザーが UI で ServiceAccountトークンを入力し、Webアプリ側で Token Review API を呼び出してトークンの有効性やユーザー情報を確認する、といったパターンです。
KubernetesクラスターがIDプロバイダ
のように振る舞うことができます。
2. Kubernetes公式Dashboard
- GitHub: kubernetes/dashboard
Dashboardのログイン画面でServiceAccountトークンを直接貼り付ける運用は、最も分かりやすい例の一つです。
3. Argo Workflows
- 公式サイト: Argo Workflows
Argo WorkflowsのUIでもServiceAccountトークンを認証に利用できます。
4. Sveltos Dashboard
- 公式サイト: Sveltos Dashboard
Sveltos DashboardもServiceAccountトークンを利用した認証をサポートしています。
Podの中で起動しているプログラムから環境の情報を元にtokenを使ってkubeconfigを組み立てる方法
通常は、Kubernetes上で動作するツールやライブラリ(例: k8s.io/client-go
)が自動的に設定を読み込んでくれるため、自分で kubeconfig を組み立てる必要はあまりありません。しかし、
「Pod 内で動的に kubeconfig を生成し、複数のクラスタへアクセスしたい」
というニーズがある場合は、以下のような手順で kubeconfig を生成できます。
最小限の構成例 (TypeScriptっぽい記述例)
const template = `apiVersion: v1
kind: Config
clusters:
- name: this
cluster:
server: ${process.env.KUBERNETES_PORT_443_TCP_ADDR}
certificate-authority-data: "${readFileSync('/var/run/secrets/kubernetes.io/serviceaccount/ca.crt', 'utf8')}"
users:
- name: this
user:
token: ${readFileSync('/var/run/secrets/kubernetes.io/serviceaccount/token', 'utf8')}
contexts:
- name: this
context:
cluster: this
user: this
current-context: this`;
-
server
: Pod 内からみた Kubernetes APIサーバーアドレス (環境変数KUBERNETES_PORT_443_TCP_ADDR
など) -
certificate-authority-data
:/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
を base64エンコード or 文字列そのまま埋め込み -
token
:/var/run/secrets/kubernetes.io/serviceaccount/token
の中身
Goでの実装例は、以下のリポジトリなどで確認できます。
projectsveltos/sveltoscluster-manager - sveltoscluster_controller.go#L398-L424
Pod内の環境変数
Pod 内では、デフォルトで次のような環境変数が設定されます。
$ env | grep KUBE
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.115.0.1:443
KUBERNETES_PORT_443_TCP_ADDR=10.115.0.1
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP=tcp://10.115.0.1:443
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_SERVICE_HOST=10.115.0.1
KUBERNETES_PORT_443_TCP_ADDR
や KUBERNETES_SERVICE_HOST
を参照すれば、APIサーバーのURLを取得できます。
kubeconfigを用いてcontroller-runtimeを用意する方法
最後に、マルチクラスタ対応のコントローラを開発したい場合に役立つ例として、controller-runtime
のクライアントを動的に構築する方法 を紹介します。
kubeconfigを複数用意することで、複数のKubernetesクラスタを切り替えて操作できます。例えば以下のリポジトリでは、kubeconfig を読み込んで controller-runtime
のマネージドクライアントを作成し、クラスタをまたいだリソース管理を行っています。
projectsveltos/sveltoscluster-manager - sveltoscluster_controller.go#L156-L168