スクエニ ITエンジニア ブログ

OIDCを介してTerraform Cloudをよりセキュアに使ってみる

みなさん、こんにちは!

そろそろ新卒と名乗るのもおこがましい気がするオバカムです。

オバカムがいるチームでは、インフラの管理にTerraform Cloudを使っています。
Terraform Cloudから管理対象のリソースへのアクセスには(当然ながら)認証情報が必要になります。
昨今、永続的な認証情報はセキュリティリスクとして受け取られるようになってきているため、例えばOIDCを利用して一時認証を使うなど、永続的な認証情報を避ける方法が重要視され始めています。

というわけで今回はTerraform CloudとOIDCにまつわる小ネタをいくつか紹介します! 今回は管理リソースはAWSとGoogle Cloudにあることを前提とします。1

OIDCを利用したTerraform Cloudでのリソース管理の概略図は以下を想定します。

OIDCによるリソース管理の概略図

以下のような流れで一時的な認証情報を取得します。

  1. Terraform CloudのWorkspaceでPlan/Applyをする際に各クラウドのIAMサービス2にアクセスキーを要求
  2. 各クラウドのIAMサービスはTerraform CloudのOpenID ProviderにWorkspaceの情報を問い合わせ
  3. OpenID ProviderはWorkspaceの情報をIDトークンとして各クラウドのIAMサービスに提供
  4. 各クラウドのIAMサービスは提供されたIDトークンをもとにアクセス権限を設定し、期限付きのアクセスキーを生成し提供

OIDCを利用してTerraform Cloudからリソースに一時認証でアクセス

OIDCを介してTerraform Cloudが動的認証情報を得るにはいくつかのステップを踏む必要があります。

  1. 各クラウドにTerraform CloudのOIDC Providerを認識してもらう
  2. 各クラウドでのアクセス権限設定
  3. Terraform CloudのWorkspaceの環境変数設定

順を追って説明していきます。

管理クラウド側に利用するOIDC Providerを設定

まずは管理対象のクラウドに利用するOIDC Providerを認識してもらいましょう。

OIDC Providerの認識部分でどちらも抑えておくべきポイントは以下です。

  1. Issuer(OIDC ProviderのURL)を指定
    • Terraform Cloudではhttps://app.terraform.io
  2. Audience(OIDC Providerが認証情報を発行する対象)の指定
    • AWSではaws.workload.identity
    • Google Cloudではhttps://iam.googleapis.com/projects/{Project ID}/locations/global/workloadIdentityPools/{Workload Identity Pool Name}/providers/{Provider Name}

各クラウドでの設定を設定画面のスクリーンショットを出しながら説明します。
今回はすべてコンソールから設定します。

AWS

  1. コンソールからIAM>ID プロバイダと進み、プロバイダを追加ボタンをクリック
  2. プロバイダの各種設定を行う
    • プロバイダのタイプOpenID Connectを選択
    • プロバイダのURLhttps://app.terraform.ioを入力
    • サムプリントを取得ボタンをクリック
    • 対象者aws.workload.identityを入力
      OIDC Provider設定画面
  3. プロバイダを追加ボタンをクリック

GCP

  1. コンソールからIAMと管理>Workload Identity 連携に進み、Workload Identity Poolの構成ボタンをクリック
  2. ID プールの設定
    • 名前に好きなID プール名(例ではovercome-test)を入力
    • 続行ボタンをクリック
      OIDC Provider設定画面
  3. ID プールにプロバイダの設定をする
    • プロバイダの選択OpenID Connect (OIDC)を選択
    • プロバイダ名に好きなプロバイダ名(例ではovercome-test)を入力
    • 発行元 (URL)https://app.terraform/ioを入力
    • オーディエンスデフォルトのオーディエンスでOK
      • Terraform Cloud側でAudienceの値を変更する場合は許可するオーディエンスを選択して値を入力
    • 続行ボタンをクリック
      OIDC Provider設定画面
  4. Google Cloud側が参照できるOIDCの属性と認証条件を設定
    • Google Cloud側で参照したい情報をマッピング
      • マッピングはGoogle ${n}OIDC ${n}が対応
        • Google ${n}にはGoogle Cloud側での参照名を入力
        • OIDC ${n}にはOpenIDの属性情報のキーを入力
      • google.subjectassertion.subをマッピング
        • このマッピングは必須
      • attribute.terraform_workspace_nameassertion.terraform_workspace_nameをマッピング
        • Google Cloud側の参照名はattribute.workspace_nameとかでもよい
      • そのほかの属性についてはTerraform CloudのOpenIDの構成情報claims_supportedを参照
    • Google Cloudが認証を通すための条件を設定
      • この条件がないとTerraform Cloudの全ワークスペースから認証が通ってしまうので設定必須
      • ここでは以下のように設定
        • Organizationovercome-organizationの時のみ、
          かつProjectovercome-testの時のみ
      google.subject.startsWith('organization:overcome-organization:project:overcome-test:')
      
OIDC Provider設定画面
  1. 保存ボタンをクリック

より詳しい設定方法は各クラウドのドキュメントを見ていただければと思います。
OpenID Connect プロバイダーの作成(AWS)
Workload Identity 連携を構成する(Google Cloud)

管理クラウド側のリソースへのアクセス権限設定

次に管理リソースへのアクセス権限を設定していきます。

AWS

AWSではOIDCを利用した動的認証の権限はIAM Roleで設定します。

IAM Roleはコンソールから設定が可能ですが、デフォルトの設定ではTerraform Cloudに存在する全ワークスペースがこのアカウントにアクセスできてしまうので、カスタムポリシーを自分で作ることにします。

信頼ポリシーには以下のJSONを設定します。
なお、以下のプレイスホルダーにはそれぞれ利用するAWSアカウント/Terraform Cloudワークスペースの情報を入れてください。

  • AWS ACCOUNT ID
  • Organization Name
  • Project Name
    • Projectに所属していない場合は省略可能です。
  • Workspace Name
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Principal": {
                "Federated": "arn:aws:iam::${AWS ACCOUNT ID}:oidc-provider/app.terraform.io"
            },
            "Condition": {
                "StringEquals": {
                    "app.terraform.io:aud": [
                        "aws.workload.identity"
                    ]
                },
                "StringLike": {
                    "app.terraform.io:sub": "organization:${Organaization Name}:project:${Project Name}:workspace:${Workspace Name}:*"
                }
            }
        }
    ]
}

許可ポリシーは必要な権限を設定してあげればOKです。
OIDC特有の設定はここでは使わないので通常通りAWSマネージドポリシーの利用やカスタム許可ポリシーの設定をすれば問題ないです。

Google Cloud

Google Cloudでは動的認証の権限は実行するサービスアカウントに紐づいています。
まずはサービスアカウントとOIDCのWorkload Identityを連携させましょう。

  1. コンソールからIAMと管理>Workload Identity連携と進み、先ほど設定したID プールの名前をクリック
  2. 画面上部のアクセス権を付与をクリック
  3. 必要な設定を入力し、保存ボタンをクリック
    • サービスアカウントには設定するサービスアカウントを選択
    • プリンシパルの選択にてOpenIDがサービスアカウントにアクセスできる条件を設定
      • ここではプロバイダの属性マッピングの時に設定したterraform_workspace_nameの値がoidc-testの時だけサービスアカウントにアクセスできるようにしています
        OIDC Provider設定画面

無事サービスアカウントにアクセスできるようになったら、サービスアカウントに必要な権限を付与します。

Terraform Cloud側のOIDCの設定

最後にTerraform Cloudのリポジトリに一時認証情報を渡すための環境変数を設定します。
各クラウドごとに違う変数が用意されているので利用するクラウドを確認して設定します。

AWS

  • TFC_AWS_PROVIDER_AUTH
    • AWSの認証にOIDCを利用するかどうかのBool値
    • Bool (true/false)
  • TFC_AWS_RUN_ROLE_ARN
    • 権限を借用するIAM RoleのARN
    • String

Google Cloud

  • TFC_GCP_PROVIDER_AUTH
    • GCPの認証にOIDCを利用するかどうかのBool値
  • TFC_GCP_RUN_SERVICE_ACCOUNT_EMAIL
    • 権限を借用するService AccountのEメールアドレス
  • TFC_GCP_WORKLOAD_PROVIDER_NAME
    • OIDCで利用するIDプールの(完全な)ID
      • フォーマットは
        projects/{{project}}/locations/global/workloadIdentityPools/{{workload_identity_pool_id}}/providers/{{workload_identity_pool_provider_id}}
    • 以下3つの環境変数に分離可能
      • TFC_GCP_PROJECT_NUMBER
        • Google Cloudのプロジェクト番号
      • TFC_GCP_WORKLOAD_POOL_ID
        • 利用するIDプールの名前
      • TFC_GCP_WORKLOAD_PROVIDER_ID
        • IDプールに紐づいているプロバイダの名前

PlanとApplyの参照権限を変える

Terraform CloudではOIDCの設定は環境変数経由になるため、一見すると複数の認証情報を一度に扱うのは難しいように見えます。

Terraform Cloudではそれを見越してか、PlanとApplyでそれぞれ別々の認証情報を渡すことができます。

  • Plan
    • TFC_AWS_PLAN_ROLE_ARN (AWS)
    • TFC_GCP_PLAN_SERVICE_ACCOUNT_EMAIL (Google Cloud)
  • Apply
    • TFC_AWS_APPLY_ROLE_ARN (AWS)
    • TFC_GCP_APPLY_SERVICE_ACCOUNT_EMAIL (Google Cloud)

なお、OIDCを利用した動的認証を使う場合には環境変数にTFC_*_RUN_*を設定するか、TFC_*_PLAN_*TFC_*_APPLY_*の両方を設定するかのどちらかが必須です。

OIDCで複数の認証情報を渡す

Terraform CloudではOIDCの設定は環境変数経由になるため、一見すると複数の認証情報を一度に扱うのは難しいように見えます。3

Terraform Cloudで同じクラウドの別々のアカウントの動的認証情報を渡すには、各クラウドのproviderモジュールとvariablesに少し細工をする必要があります。

まずは各クラウドのproviderモジュールです。
各クラウドのproviderモジュールはaliasを設定することで複数の設定プロファイルを使える……というのはTerraformを使っている皆様ならおなじみですよね。
ここで設定したaliasをキーとして細工します。

動的認証は(暗黙の)variable tfc_${provider}_dynamic_credentialsを通じてproviderへ渡されます。
このvariableをaliasをキーとしたmapとして扱えるように明示的に宣言します。

variable "tfc_${provider}_dynamic_credentials" {
  type = object({
    default = object({
      ...
    })
    aliases = map(object({
      ...
    }))
  })
}

各クラウドのproviderからは以下のように参照します。

provider "${cloud}" {
    ... = [var.tfc_${cloud}_dynamic_credentials.default....]
}
    
provider "aws" {
  alias = "ALIAS"
  ... = [var.tfc_${cloud}_dynamic_credentials.aliases["ALIAS"]....]
}

各クラウドの実装例を以下に示しておきます。

  • AWS
    • ここではアプリとDNS(Route53)が別クラウドアカウント管理という想定での設定例です。
    TFC_AWS_PROVIDER_AUTH=true
    TFC_AWS_RUN_ROLE_ARN=${App Account Role ARN}
    TFC_AWS_PROVIDER_AUTH_DNS=true
    TFC_AWS_RUN_ROLE_ARN_DNS=${DNS Account Role ARN}
    
    variable "tfc_aws_dynamic_credentials" {
      description = "Object containing AWS dynamic credentials configuration"
      type = object({
        default = object({
          shared_config_file = string
        })
        aliases = map(object({
          shared_config_file = string
        }))
      })
    }
    
    # For Application
    provider "aws" {
      shared_config_files = [var.tfc_aws_dynamic_credentials.default.shared_config_file]
    }
    
    # For Route53
    provider "aws" {
      alias = "DNS"
      shared_config_files = [var.tfc_aws_dynamic_credentials.aliases["DNS"].shared_config_file]
    }
    
  • GCP
    • ここではアプリとBigQueryが別プロジェクト管理という想定での設定例です。
    TFC_GCP_PROVIDER_AUTH=true
    TFC_GCP_RUN_SERVICE_ACOUNT_EMAIL=${App Project Service Account Email}
    TFC_GCP_PROJECT_NUMBER=${App Project Number}
    TFC_GCP_WORKLOAD_POOL_ID=${App Project Workload Pool ID}
    TFC_GCP_WORKLOAD_PROVIDER_ID=${App Project Workload Provider ID}
    TFC_GCP_PROVIDER_AUTH_BQ=true
    TFC_GCP_RUN_SERVICE_ACCOUNT_EMAIL_BQ=${BQ Project Service Account Email}
    TFC_GCP_PROJECT_NUMBER=${BQ Project Number}
    TFC_GCP_WORKLOAD_POOL_ID=${BQ Project Workload Pool ID}
    TFC_GCP_WORKLOAD_PROVIDER_ID=${BQ Project Workload Provider ID}
    
    variable "tfc_gcp_dynamic_credentials" {
      description = "Object containing GCP dynamic credentials configuration"
      type = object({
        default = object({
          credentials = string
        })
        aliases = map(object({
          credentials = string
        }))
      })
    }
    
    # For Application
    provider "google" {
      credentials = var.tfc_gcp_dynamic_credentials.default.credentials
    }
    
    # For BigQuery
    provider "google" {
      alias = "BQ"
      credentials = var.tfc_gcp_dynamic_credentials.aliases["BQ"].credentials
    }
    

まとめ

OIDCを介してTerraform Cloudに動的認証情報を渡す方法を上げてみました。
設定する部分は多いですが、やっていることは以下3つです。

  1. クラウド側でOIDC Providerを認識させる
  2. クラウド側でアクセス権限を設定する
  3. Terraform Cloud側で権限を借用するアカウント・ロールを設定する

それでは、よいTerraform Cloudライフを!


  1. オバカムがTerraformで触ったことあるリソースがAWSとGoogle Cloudだけだったりします。 ↩︎

  2. Identity and Access Management、つまりIDとアクセス権限の管理をするサービスのことです。AWSやGoogle CloudではIAMという名前のサービスなのでわかりやすいはず。 ↩︎

  3. 大事なことなので2回言いました。 ↩︎

この記事を書いた人

記事一覧
SQUARE ENIXでは一緒に働く仲間を募集しています!
興味をお持ちいただけたら、ぜひ採用情報ページもご覧下さい!