LINEヤフー Tech Blog

LINEヤフー株式会社のサービスを支える、技術・開発文化を発信しています。

Service Account Tokenで変わるイメージレジストリ認証(インターンレポート)

はじめに

こんにちは、筑波大学情報学群情報科学類3年の渡邉広希です。社内向けKubernetes as a Serviceの開発チームにインターンシップ生として2025年9月1日から4週間所属していました。所属期間中は社内のコンテナレジストリに格納されたプライベートコンテナイメージを安全かつ自動的にプルするための認証システムの実装を行いました。今回の実装の最大のポイントは、Kubernetes v1.34でBeta機能として導入されたKubeletServiceAccountTokenForCredentialProviders機能を活用した点です。この新機能により、kubeletがPodに紐づいたServiceAccountトークンをイメージプル時にCredential Providerプラグインへ送信できるようになり、Workload Identityを用いたよりセキュアなイメージ認証が可能になりました。この記事では、LINEヤフー独自の認証基盤の実装については簡単に解説し、Image Credential Provider自体の仕組みや、Amazon Elastic Container Registry、Google Cloud Artifact Registry、Azure Container Registryなどでの実装例も紹介し、一般的なImage Credential Providerの価値を理解していただけるよう説明しています。

プライベートイメージをプルする方法

プライベートイメージ認証の課題

Kubernetesでは、プライベートコンテナイメージを利用する際の認証方式について、セキュリティと運用性を向上させるための機能強化が継続的に行われてきました。

最も基本的な方法は、Kubernetes Secretに認証情報を格納しimagePullSecretsとして参照する形です。この方式はKubernetesの標準機能のみで実現できるため導入は容易ですが、トークンが静的でクラスター内に永続的に保存されるためセキュリティリスクが高く、トークンのローテーションも手動となるため有効期限が切れる前に毎回Secretを更新する必要がありました。

この課題を解決するためKubernetes v1.20でImage Credential Providerが導入されました。この仕組みでは認証情報をイメージプル時に動的に外部から取得できるため、トークンの自動ローテーションが可能となり運用負荷が軽減されます。しかし、ノード単位のIAMロールやサービスアカウントキーに依存するため、Pod単位での細かな権限付与が難しく、ノードに割り当てられた権限をすべてのPodが共有してしまうという課題がありました。

さらにKubernetes v1.33でAlpha、v1.34でBeta機能として追加されたKubeletServiceAccountTokenForCredentialProvidersでは、ServiceAccountトークンをCredential Providerプラグインに渡せるようになりました。これによりServiceAccount単位で細かな権限付与が可能となり、Pod(ワークロード)単位のアイデンティティで認証情報を取得できるようになったため、最小権限の原則を実現しセキュリティが向上します。

Image Credential Providerの詳細

基本概念

Image Credential Providerは、Kubernetes v1.20でAlpha機能として導入されたkubeletの拡張機能です。

この機能は、コンテナイメージをプルする際の認証情報取得を外部プログラム(プラグイン)に委譲する仕組みです。kubeletはコンテナランタイムへイメージ取得を指示する際に、必要に応じて外部プラグインをexecで呼び出し、stdin/stdoutを通じて認証情報をやり取りします。Dockerのイメージプルトークンの仕組みを応用した形で実装されている従来のImage Pull Secretsによる静的な認証情報管理から、動的にイメージプルトークンを取得することで、短命のトークンを使うことができるため、より安全かつ自動化された認証を実現することができます。補足として、Image Pull SecretsはKubernetesシークレットなどにクレデンシャルを格納することができるため、稼働中のKubernetesクラスターに簡単に導入できます。一方で、Image Credential Providerでは各ノード内のkubeletがイメージプル時に短命なトークンを取得する仕組みのため、Kubernetesのマニフェストとして導入することができず、ノード毎にImage Credential Providerのバイナリを設置したり、kubeletの起動引数を変更したりする必要があり、やや導入難易度が高いというデメリットがあります。

従来の認証方式との比較

従来のImage Pull Secrets

apiVersion: v1
kind: Secret
metadata:
  name: registry-secret
type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: <base64-encoded-docker-config>
---
apiVersion: v1
kind: Pod
spec:
  imagePullSecrets:
  - name: registry-secret

課題

上記のようなKubernetes Secretを利用する方式では、認証情報がクラスター内に静的に保存されます。そのため一度セットしたトークンのローテーションが難しいため、期限が切れる前に手動でSecretを更新し直す必要がありました。また複数のレジストリにアクセスする場合、各NamespaceやServiceAccountごとにSecretを配置・管理する必要があり、運用が複雑化します。さらに、有効期限によるローテーションの仕組みや、設定されているトークンの有効性の確認をする仕組みがないため、一般に長期間有効な資格情報を扱うことになり、漏えい時のリスクも大きくなります。

Image Credential Providerによる動的認証

apiVersion: kubelet.config.k8s.io/v1
kind: CredentialProviderConfig
providers:
- name: "my-credential-provider"
  matchImages:
  - "*.example.com"
  defaultCacheDuration: "12h"

利点

Image Credential Providerを利用すると、kubeletは上記の設定にマッチするイメージプル要求時に外部プラグインを呼び出し、動的に認証情報を取得します。これにより以下の利点が得られます。

  • 動的な認証情報の取得: 認証情報をイメージプルのタイミングで動的に外部から取得するため、Kubernetesクラスター内に永続的に保存せずに利用することができます。
  • トークンの自動ローテーション: トークンの有効期限が短く設定され、自動的に更新・取得されるため利用者側での置き換えが不要です。
  • IAMロールやサービスアカウントとの統合: これはクラウドプロバイダーとしてIAMとKubernetesのServiceAccountの連携を実装している場合になりますが、KubeletServiceAccountTokenForCredentialProvidersによってKubernetesのServiceAccountトークンをCredential Providerに提供できるため、Kubernetesの機能を使って細かなアクセス制御や一時的な権限付与を実現できます。
  • マルチレジストリ対応の簡易化: プラグインの設定でパターンマッチさせることで、AWS(Amazon Web Services)、GCP(Google Cloud Platform)、Microsoft Azure、オンプレミスなど複数種のレジストリ認証を一貫した仕組みで対応させることができます。

実装の仕組み

Image Credential Providerの実装には共通のパターンがあります。kubeletはプラグインに対し、必要なリクエスト情報(イメージ名など)を含むCredentialProviderRequestオブジェクトをJSON形式でstdinに書き込みます。プラグインはstdinからリクエストを読み取り、内部で必要な認証フローを実行します(クラウドのメタデータサービスやIAM APIを呼ぶなど)。プラグインは認証に成功すると、CredentialProviderResponseオブジェクト(認証情報を含む)をJSON形式でstdoutに出力します。kubeletはstdoutからレスポンスを受け取り、取得した認証情報を用いてコンテナランタイムにイメージプルを指示します。

プラグインが受け取るリクエスト(JSON)の例:

{
  "apiVersion": "credentialprovider.kubelet.k8s.io/v1",
  "kind": "CredentialProviderRequest",
  "image": "registry.example.com/my-project/my-image:latest",
  "serviceAccountToken": "<Kubernetesのサービスアカウントトークン>",
  "serviceAccountAnnotations": {"example.com/iam-service-account": "<サービスアカウント名など>"}
}

プラグインがkubeletに返すレスポンス(JSON)の例:

{
  "apiVersion": "credentialprovider.kubelet.k8s.io/v1",
  "kind": "CredentialProviderResponse",
  "cacheKeyType": "Registry",
  "cacheDuration": "1h",
  "auth": {
    "registry.example.com": {
      "username": "oauth2accesstoken",
      "password": "<access-token>"
    }
  }
}

上記では、registry.example.com ドメインに対する認証情報(ユーザー名とパスワードトークン)が格納されています。cacheKeyType: "Registry"は、この認証情報がレジストリ単位でキャッシュ可能であることを意味します(同じレジストリ内の別イメージでも認証情報を再利用する)。cacheDurationはこの認証情報がどれくらいキャッシュ有効かを示し、例では1時間となっています。

Kubernetes v1.26以降でCredential Provider機能がGA(General Availability)となり、上記のリクエスト・レスポンス形式が安定版として利用できるようになりました。KubeletServiceAccountTokenForCredentialProvidersはCredential Providerを拡張した形でserviceAccountTokenserviceAccountAnnotationsフィールドが追加されています。このリクエスト・レスポンスのフォーマットに従うだけで、Kubernetes側に手を加えることなく認証プラグインを実装できます。

KubeletServiceAccountTokenForCredentialProviders

新機能の詳細

Kubernetes v1.33でFeature Gateオプションで有効化できるAlpha機能という形で追加され、Kubernetes v1.34でBeta機能となり、デフォルトで有効化されました。前述のImage Credential Provider機能をさらに発展させたものです。従来はノード単位のIAMロールやサービスアカウントキーに頼っていましたが、この機能の追加によりPod単位のアイデンティティで認証情報を取得できるようになりました。

  • Image Credential Provider(Kubernetes v1.20+)ノードに割り当てられたIAMロールや長期有効なサービスアカウントキーを利用し、プラグインがクラウドAPIからトークンを取得する形になります。各ノードには一定の権限を持つアイデンティティが必要であり、クラスター外の権限管理と密接に結びついていました。
  • KubeletServiceAccountTokenForCredentialProviders(v1.33+)Credential Providerを拡張する形で実装されています。Kubernetes自身が発行する各Pod用のServiceAccount JWT(JSON Web Token)トークンをプラグインに渡し、そのトークンを用いて外部システムからイメージプル用資格情報を取得します。これにより、「ノードごとに権限を付与することなく、作成されたPodに紐づいたServiceAccountの認証情報で必要なイメージだけを取得する」という最小権限なアプローチが実現できます。

主要クラウドプロバイダーでの実装

Image Credential Providerは主要なクラウドプロバイダーで既に実装されており、KubeletServiceAccountTokenForCredentialProvidersのサポートも進んでいます。

Amazon Elastic Container Registry

AWSでは公式ecr-credential-provider(Kubernetes向けのCredential Providerプラグイン)を提供しています。ソースコードはkubernetes/cloud-provider-aws内で見ることができます。

従来はノードに付与されたIAMロールを使ってAWS Security Token Service(STS)から一時的な認証トークンを取得していましたが、ecr-credential-providerでは既にServiceAccountTokenによる認証機能が実装されており、EKS Pod Identity環境が含まれるクラスターであれば既に利用できるようです。

Google Cloud Artifact Registry

Google Cloudではgcp-credential-providerプラグインが提供されています。ソースコードはkubernetes/cloud-provider-gcp内で見ることができます。

従来はノードのIAMサービスアカウントやWorkload Identityを通じてアクセストークンを取得していましたが、ServiceAccountToken機能についてはIssueとしてサポート追加に対する議論が上がっており、まだ計画段階のようです。

Azure Container Registry

Azureでは、Azure Kubernetes Service(AKS)向けにACR用のCredential Providerプラグインが提供されています。ソースコードはkubernetes-sigs/cloud-provider-azure内で見ることができます。

従来はノードに割り当てられたManaged IdentityやService Principalを使ってACRへのアクセストークンを取得していましたが、ServiceAccountToken機能を追加するプルリクエスト(feat(credential-provider): support k8s service account token #8836)が作成されており、コメントで頻繁に議論が行われており現在も実装作業を行っている途中のようです。

Flavaにおけるプライベートイメージの利用

FKE環境における課題の再確認

FKE環境では、前述の一般的な課題に加えてさらに固有の課題がありました。FCRで発行されるトークンは短期間のみ有効であるため手動でのローテーションは現実的ではなく、CronJobによるトークン自動更新の仕組みを提供してきました。しかし、そのためには事前に証明書の発行、サイドカーコンテナの配置、複雑なRBAC(Role-Based Access Control)設定などが必要でした。さらにCronJob自体の運用にも手間がかかっていました。CronJobが正常に動作しているか継続的に監視する必要があり、失敗時には手動で原因を調査して対応しなければなりませんでした。また、セキュリティアップデートや機能改善のたびにCronJobで利用するコンテナイメージのバージョン管理も必要で、これらの運用負荷は実際のKubernetesを利用したアプリケーションを開発するチームにとって大きな負担となっていました。

Flavaの基盤構成

LINEヤフーではFlavaという社内クラウド基盤を運用しています。FlavaにはIaaS(Infrastructure as a Service)、PaaS(Platform as a Service)、DBaaS(Database as a Service)など様々なサービスが含まれていますが、今回の主題となるのは以下の2プロダクトです。

  • FKE: Kubernetes as a Service(KaaS)で、シングルテナント型のマネージドKubernetes環境を提供しています。
  • FCR: コンテナイメージやArtifactを格納できるレジストリです。パブリックおよびプライベートなイメージの格納に対応しています。

また、Flavaでは下記のシステムによってパブリッククラウドのWorkload Identityに相当する統一されたIAM(Identity and Access Management)管理が実現されています。

  • Athenz: 旧Yahoo! Inc. が設計・開発し、オープンソースとして公開されている認証認可基盤。サービスやユーザーに対する細かなRBACとX.509証明書による認証認可を実現。 社内のゼロトラストセキュリティの基盤として、Athenzを用いたアイデンティティ管理が行われています。詳しくは アクセス制御オープンソース「Athenz」、CNCF Landscape 参加までの道のり をご覧ください。
  • Copper Argos: Athenzの機能の一つで、外部のidentity(Kubernetes ServiceAccountトークンなど)を利用してAthenzの認証を通し、X.509証明書を取得する仕組みです。Kubernetes上のPodが自分のServiceAccountトークンを提示すると、Copper Argosの仕組みを通じてAthenzからそのPodに対応する短期資格情報(証明書やトークン)を取得できます。AWSやGoogle CloudのWorkload IdentityのLINEヤフー社内版ともいえます。詳しい実装については Copper Argos をご覧ください。

FCRに対してイメージのプルやプッシュに用いるトークンはAthenzで設定された権限に基づいて発行されます。

今回のインターンでのテーマであるCredential Providerの実装は、Kubernetes標準のKubeletServiceAccountTokenForCredentialProvidersと、Athenz + Copper Argosをフルに活用する形で行いました。

システム構成

実装したシステムは以下のコンポーネントで構成されています。

システム構成

  1. Athenz: ServiceAccountに対応する権限管理とトークン発行を行います。Copper Argosの仕組みを通じて、Kubernetes ServiceAccountトークンをAthenzのアクセストークンに交換できます。
  2. Image Credential Providerプラグイン: kubeletから呼び出され、Copper Argosの仕組みを使ってAthenzからFCR用の短期トークンを取得する外部プログラムです。
  3. FCR: プライベートコンテナイメージの格納先レジストリです。Image Credential Providerプラグインがここへの認証に必要なトークンを取得します。

主にImage Credential Providerプラグイン(fcr-credential-provider)の新規実装に取り組みました。

実装の詳細

認証フロー

実装した認証フローは以下のシーケンスで動作します。

認証フロー

  1. Podの起動: ユーザーがプライベートイメージを含むPodを作成すると、通常通りkube-apiserverにPodが登録されます。
  2. kubeletによるプラグイン呼び出し: 該当Podがスケジュールされたノード上で、kubeletはPodのコンテナイメージURLを確認します。もし設定されたコンテナレジストリにマッチすれば、Image Credential Providerプラグイン(以下「FCRプラグイン」)をexecで起動します。
  3. Credential Providerプラグインでの認証要求: FCRプラグインは、kubeletから渡されたServiceAccountトークン(JWT)とアノテーション情報を使い、Copper Argosの仕組みを利用してAthenzに対して認証要求を行います。具体的には、「JWT」、「アノテーションに含まれるAthenzでのサービスアカウント名」、「Podのメタ情報」をAthenzに送り、対応するAthenzアクセストークンを要求します。
  4. Athenzでのトークン交換: Athenzは受け取ったJWTが正当なクラスター内Podのものであるか検証した上で、トークン交換を行います。
  5. Athenzからアクセストークン取得: Athenzはリクエストを検証し、許可されたサービス用のトークンを発行します。AthenzはこのトークンをFCRプラグインに返します。
  6. FCRでのイメージプル: FCRプラグインは受け取ったAthenzアクセストークンを用いて、FCRドメインに対する有効なusernameおよびpassword(アクセストークン)のペアをCredentialProviderResponseとして返します。kubeletはこの情報でコンテナランタイムを介し、FCRからプライベートイメージを無事プルすることができます。

技術的な実装ポイント

1. CredentialProviderConfigの設定

apiVersion: kubelet.config.k8s.io/v1
kind: CredentialProviderConfig
providers:
  - name: "fcr-credential-provider"
    matchImages:
      - "registry.example.com"
    defaultCacheDuration: "10m"
    apiVersion: "credentialprovider.kubelet.k8s.io/v1"
    tokenAttributes:
      serviceAccountTokenAudience: "<Copper Argosで使えるAudience>"
      requireServiceAccount: true
      requiredServiceAccountAnnotationKeys:
        - "example.com/iam-service-account"

ユーザー体験の改善

Before(従来手法)

従来は、FCR用トークンを定期取得するCronJobや、それを各Podに配布する仕組みを自作して対応していました。例えばCronJobで6時間ごとにSecretを更新するマニフェストは以下のように複雑でした。

apiVersion: batch/v1
kind: CronJob
metadata:
  name: fcr-sa-secret-cron
spec:
  schedule: "0 */6 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: fcr-sa-updater
          containers:
          - name: updater
            image: custom-registry/fcr-updater:latest
            # ... FCRからトークン取得しSecretを更新するロジック ...
  • CronJobの設定や、内部で実行するスクリプトの作成、ビルドされたコンテナイメージの管理が必要でした。
  • また、上記Updater用ServiceAccountにはFCRの認証情報を取得するための証明書が事前に付与されており、その発行や管理も煩雑でした。
  • 各アプリケーションPodはimagePullSecretsとしてこのSecretを参照する必要があり、マニフェストごとに記述が増えます。

After(新実装)

新しい仕組みでは、Image Credential ProviderとServiceAccount連携が裏側で動作するため、FKEを利用するテナントユーザーは何も意識する必要がありません。シンプルなPod定義例を示します。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: secret-manager
  annotations:
    example.com/iam-service-account: "<Athenzで作成したサービスアカウント名>"
---
apiVersion: v1
kind: Pod
metadata:
  name: test
spec:
  containers:
  - name: test-container
    image: registry.example.com/org/private-test:latest
  serviceAccountName: secret-manager  # ←Copper ArgosによってAthenzと紐付いたサービスアカウントを指定

このようにユーザーは単に適切なServiceAccountをPodに割り当てるだけでプライベートレジストリのコンテナを利用できます。このマニフェストをデプロイすれば、裏でkubeletが自動的にFCRプラグインを呼び出し、認証情報を取得してイメージプルが行われます。CronJobも特別なSecret管理も不要となりました。このBefore/Afterの比較からも、今回の新規実装により設定の簡素化と意識対象の削減が実現します。

実装の成果

1. 設定簡素化

  • CronJobやサイドカーの排除: 従来必要だったトークン更新用CronJob、サイドカーコンテナ、証明書の事前発行などが不要になりました。
  • シンプルなマニフェスト: Pod定義においてserviceAccountNameを指定するだけでよく、ユーザーがimagePullSecretsを書く必要がなくなりました。
  • RBAC設定の削減: 特殊なSecret配布用ServiceAccountや、それにまつわるRBACルールを作成する必要がなくなりました。

2. セキュリティの向上

  • 短期間有効なトークン: 長期トークンではなく、数十分〜数時間で期限が切れる一時的なトークンのみを扱うようになったため、万一漏えいしても被害を最小限にできます。
  • ヒューマンエラーの排除: 手動運用を極力無くしたことで、ミスによりトークンを漏えいしたり期限切れに気付かないといったリスクを低減しました。
  • Kubernetes標準への準拠: Kubernetesが標準提供する仕組み(ServiceAccountトークンとCredential Providerの連携)に沿っているため、将来的なアップデートでも互換性や保守性が高く、セキュリティレビューもしやすい設計です。

3. 運用負荷の軽減

  • 自動トークン管理: kubeletが自動でプラグイン経由でのトークン発行を行うため定期的にトークンを発行・配布する作業が不要になりました。
  • 障害点の減少: CronJobや外部スクリプトなど、動作させ続けるためのコンポーネントが減ったことで、システム全体として信頼性が向上しました。
  • 監視の簡素化: 監視すべき対象がKubernetesコアの動作(PodがImagePullBackOffになっていないかなど)に集約され、個別のトークン更新ジョブの成否をチェックする必要がなくなりました。以上のように、開発者体験と運用性の両面で大きな改善が確認できました。

まとめ

今回のインターンシップでは、Kubernetes v1.33の新機能を活用して、FKE環境におけるプライベートイメージ認証の改善を実現できました。

今回の実装を通じて、kubeletから実行されるCredential Providerの実装、ServiceAccountトークンのJWT構造やTokenRequest APIによる動的トークン取得、OAuth 2.0トークンエクスチェンジを用いた認証フローなど、Kubernetes内部の認証手順を詳細に理解できました。また、大手クラウドプロバイダーのCredential Provider実装を調査する中で、Kubernetesの標準インターフェースを活用して独自実装との差異を吸収していることを知ることができました。今回FKE向けに実装したCredential Providerは、Kubernetes標準に準拠した形態であるため、今後のKubernetesの更新などにも追従しやすく、保守性の高い実装が実現できたと考えています。

メンターから一言

こんにちは。今回渡邉さんのメンターを務めさせていただきました土谷続季です。

今回のインターンシップでは、LINEヤフーを支える巨大なプライベートクラウド基盤「Flava」の中で、Kubernetes as a ServiceであるFKEの改善に取り組んでいただきました。FlavaはLINEヤフー統合後に誕生した新しいプライベートクラウドで今後多くのサービスが稼働しユーザも増えることが予想されるため、セキュリティとUXの向上は非常に重要なテーマです。渡邉さんはKubernetesの新機能であるKubeletServiceAccountTokenForCredentialProvidersをいち早くキャッチアップし、Flavaの認証基盤に適用することで、FKEユーザーの利便性向上とセキュリティ強化を実現しました。

渡邉さんは短いインターンシップ期間の中で、Kubernetesの深い理解と実装力を発揮し、非常に高品質な成果物を提供してくれました。特に、KubernetesのCredential Providerの仕組みとをコードレベルから詳細に調査し実装までいただきました。、FlavaのIAMを支えるAthenzについても短期間で理解を深め、AthenzおよびCopper Argosと連携させるため仕組みを深く理解しつつも迅速に実装を進めていた点が素晴らしかったです。

日頃から技術への高い関心が伺えインターンシップのメンターとしても刺激をもらい貴重な体験となりました。今回の経験が渡邉さんの今後のキャリアにとっても大きな糧になることを願っています。ありがとうございました!