4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Kubernetes ServiceAccount トークンの発行と利用方法を解説

Last updated at Posted at 2025-01-17

はじめに

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=defaultServiceAccount=default に対して、1時間(--duration=1h)有効なトークンを要求しています。
大変シンプルで使いやすいアプローチです。

curlを使ったToken取得例

kubectl 以外にも、curl でAPIサーバーに直接リクエストを送り、ServiceAccountトークンを発行することが可能です。
ただし、APIサーバーへ直接アクセスするためには、すでに何らかの 認証トークン を所持している必要があります。以下では、

  1. ServiceAccount test-sa にクラスター管理権限を与える
  2. 取得したトークンを使って別のトークンを発行する

という手順を例として解説します。

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)と、それを使用する kubectlkubelet などのツールは、外部コマンドを実行してユーザーの認証情報を取得する仕組みをサポートしています。
これを Credential plugins と呼びます。LDAP や OAuth2、SAML など、client-go がネイティブにサポートしていない認証方式でも外部コマンドを組み合わせることで連携が可能です。

認証 | Kubernetes

kubeconfigの例

Credential plugins を活用すると、kubeconfigusers セクションに「どのコマンドを呼ぶか」「どの引数を渡すか」などを記述できます。以下は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}/tokenPOST する形になっています。

// 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

kubernetes dashboard UI

Dashboardのログイン画面でServiceAccountトークンを直接貼り付ける運用は、最も分かりやすい例の一つです。

3. Argo Workflows

Argo Workflows UI

Argo WorkflowsのUIでもServiceAccountトークンを認証に利用できます。

4. Sveltos Dashboard

Sveltos UI

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_ADDRKUBERNETES_SERVICE_HOST を参照すれば、APIサーバーのURLを取得できます。

kubeconfigを用いてcontroller-runtimeを用意する方法

最後に、マルチクラスタ対応のコントローラを開発したい場合に役立つ例として、controller-runtime のクライアントを動的に構築する方法 を紹介します。
kubeconfigを複数用意することで、複数のKubernetesクラスタを切り替えて操作できます。例えば以下のリポジトリでは、kubeconfig を読み込んで controller-runtime のマネージドクライアントを作成し、クラスタをまたいだリソース管理を行っています。

projectsveltos/sveltoscluster-manager - sveltoscluster_controller.go#L156-L168

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?