NRIネットコム社員が様々な視点で、日々の気づきやナレッジを発信するメディアです

注目のタグ

    Terraformで秘密情報を扱う場合のSecrets Managerという選択肢

    本記事は  IaCウィーク  8日目の記事です。
    ⚙️  7日目  â–¶â–¶ æœ¬è¨˜äº‹ â–¶â–¶  9日目  ðŸ’»

    はじめに

    NTシステム事業2部の北野と申します。 IaCウィークということで、今回はIaCツールであるTerraformで秘密情報をコード化する際に、AWS Secrets Managerを用いる方法をご紹介します。

    Terraformとは

    Terraformとは、あらゆるインフラストラクチャをコードで管理するためのツールで、HashiCorp社によって開発されています。 簡単に言うと、サーバーやネットワークなどのクラウドリソースを「設定ファイル」で定義し、自動的に構築・変更・削除できる仕組みです。

    主な特徴:

    • インフラ構成をコード(HCL: HashiCorp Configuration Language)で記述することで、再現性・バージョン管理が可能です。
    • AWS、Azure、GCPなど複数のクラウドサービスを一括管理できます。

    想定するシーン

    AWSのリソースをTerraformでコーディングしている時、リソースの設定値に秘密情報(パスワード、Token)などを設定しなければならないシーンは少なからず存在します。 例としては以下のような場合です。

    • Amazon Cognitoのidentity providerを設定する際、連携したいidentity providerから発行されたclient_id,client_secretを設定したい。
    • RDSのリソースを初期構築する際のrootパスワードを設定したい。
    • AWS Lambdaの環境変数に外部APIのTokenを設定したい

    その時、一般的な方法としてよく紹介されるのはTF_VARSを用いた方式です。 以下はAmazon Cognitoのuser poolに紐づいたIDPを設定するコードになります。

    変数の定義

    variable "client_id" {
      type      = string
      sensitive = true
    }
    variable "client_secret" {
      type      = string
      sensitive = true
    }

    Amazon Cognitoリソースの定義

    locals {
      issuer_url   = "https://external.auth.provider.example.com/idp"
    }
    
    resource "aws_cognito_user_pool" "example-user-pool" {
      name                     = "example"
      auto_verified_attributes = ["email"]
      tags                     = {}
      username_attributes      = ["email"]
      account_recovery_setting {
        recovery_mechanism {
          name     = "admin_only"
          priority = 1
        }
      }
    }
    resource "aws_cognito_identity_provider" "external-idp" {
      user_pool_id  = aws_cognito_user_pool.example-user-pool.id
      provider_name = "external.idp"
      provider_type = "OIDC"
      provider_details = {
        attributes_request_method     = "GET"
        attributes_url                = "${local.issuer_url}/oidc/userinfo"
        attributes_url_add_attributes = false
        authorize_scopes              = "openid me"
        authorize_url                 = "${local.issuer_url}/oidc/authorization"
        client_id                     = var.client_id
        client_secret                 = var.client_secret
        jwks_uri                      = "${local.issuer_url}/oidc/jwks"
        oidc_issuer                   = local.issuer_url
        token_url                     = "${local.issuer_url}/oidc/token"
      }
    }

    client_id,client_secretに先ほど定義したvar.client_id,var.client_secretを設定します。

    client_id,client_secretの値を別ファイル(terraform.tfvars)にて記述します。

    client_id = "your-id"
    client_secret = "your-secret"

    Terraformのコードはgitなどのリポジトリで管理されることが多いのですが、上記の方法を使用することで terraform.tfvarsをgitリポジトリから除外(.gitignoreファイルに記載)し、セキュアな情報がGitリポジトリにアップロードされることを防げます。

    しかしこの方式は手軽である反面、誤ってgitリポジトリに追加してしまうリスクや開発者のローカルで作成したtfvarsファイルの内容を共有する方法を検討しなければならないなど、 規模の大きな開発になればなるほど課題が出てきます。

    そこでご紹介したいのがSecrets Managerを利用した秘密情報をTerraformから取得する方法です。

    実装方法

    今回は例として上記と同様Amazon Cognitoのidpを実装する方法をご紹介します。

    Secrets Managerの設定

    まずマネジメントコンソールからSecrets Managerリソースを作成します。

    その他のシークレットタイプを選択し、client_id,client_secretの値を設定します。

    ここではキーを/cognito/idpとしておきます。

    ローテーションは無効にしておきます。

    設定が完了したら保存をクリックします。

    Terraform側の実装

    Terraformのコードを以下のように実装します。 dataブロックでSecrets Managerから取得した内容をJSON形式でデコードすると、Terraformのコード上で使用できるようになります。

    data "aws_secretsmanager_secret_version" "idp_secrets" {
      secret_id = "/cognito/idp"
    }
    
    locals {
      idp_secrets = jsondecode(data.aws_secretsmanager_secret_version.idp_secrets.secret_string)
      issuer_url   = "https://external.auth.provider.example.com/idp"
    }
    
    resource "aws_cognito_user_pool" "example-user-pool" {
      name                     = "example"
      auto_verified_attributes = ["email"]
      tags                     = {}
      username_attributes      = ["email"]
      account_recovery_setting {
        recovery_mechanism {
          name     = "admin_only"
          priority = 1
        }
      }
    }
    resource "aws_cognito_identity_provider" "external-idp" {
      user_pool_id  = aws_cognito_user_pool.example-user-pool.id
      provider_name = "external.idp"
      provider_type = "OIDC"
      provider_details = {
        attributes_request_method     = "GET"
        attributes_url                = "${local.issuer_url}/oidc/userinfo"
        attributes_url_add_attributes = false
        authorize_scopes              = "openid me"
        authorize_url                 = "${local.issuer_url}/oidc/authorization"
        client_id                     = local.idp_secrets.client_id
        client_secret                 = local.idp_secrets.client_secret
        jwks_uri                      = "${local.issuer_url}/oidc/jwks"
        oidc_issuer                   = local.issuer_url
        token_url                     = "${local.issuer_url}/oidc/token"
      }
    }

    上記のようにしてセキュアに外部の認証基盤との連携設定が実装できました。

    メリット

    上記の実装でのメリットは以下のようなものが考えられます。

    • コード上に秘密情報を記載しなくても良い。
    • 秘密情報の共有や受け渡しをSecrets Manager経由で行うことができることによって秘密情報の散在やユーザー間の共有時の漏洩が防げる。
    • 秘密情報の取得方式の実装を統一することでコーディングミスやバグの防止ができる。

    注意点

    stateファイルの扱い

    上記方式で実装したとしてもTerraformでリソースを実装した時に作成されるstateファイルには秘密情報が記載されます。 stateファイルは通常ですとS3バケットに保存するのが一般的ですが、ローカルに作成することも可能です。 秘密情報をTerraformで実装する際には上記のことをふまえ以下のアプローチをとってください。

    • TerraformのstateファイルをS3に保管する(S3バックエンド)。そのS3バケットの暗号化を実施する。
    • もしローカルでstateファイルを扱う場合は.gitignoreにterraform.tfstateおよびterraform.tfstate.backupを追加しgitリポジトリにアップロードされないようにする。

    参考:S3バックエンドをTerraformで設定する例

    terraform {
      backend "s3" {
        bucket         = "my-terraform-state"
        key            = "cognito/terraform.tfstate"
        region         = "ap-northeast-1"
        encrypt        = true  # 暗号化を有効化
        dynamodb_table = "terraform-lock"  # 状態ロック用のDynamoDBテーブル
      }
    }

    費用面の問題

    AWS Secrets Managerは有料サービスになりますので、以下の料金がかかります。

    • シークレット1つあたり: 月額$0.40
    • API呼び出し: 10,000回あたり$0.05

    コストを抑えたい場合は、AWS Systems Manager Parameter Store(SecureString)の利用も検討できます。

    AWS Systems Manager Parameter Storeでの実装例:

    data "aws_ssm_parameter" "idp_client_id" {
      name = "/cognito/idp/client_id"
    }
    
    data "aws_ssm_parameter" "idp_client_secret" {
      name = "/cognito/idp/client_secret"
      with_decryption = true # SecureStringを設定している場合こちらのプロパティが必要です。
    }
    
    locals {
      issuer_url   = "https://external.auth.provider.example.com/idp"
    }
    
    resource "aws_cognito_user_pool" "example-user-pool" {
      name                     = "example"
      auto_verified_attributes = ["email"]
      tags                     = {}
      username_attributes      = ["email"]
      account_recovery_setting {
        recovery_mechanism {
          name     = "admin_only"
          priority = 1
        }
      }
    }
    resource "aws_cognito_identity_provider" "external-idp" {
      user_pool_id  = aws_cognito_user_pool.example-user-pool.id
      provider_name = "external.idp"
      provider_type = "OIDC"
      provider_details = {
        attributes_request_method     = "GET"
        attributes_url                = "${local.issuer_url}/oidc/userinfo"
        attributes_url_add_attributes = false
        authorize_scopes              = "openid me"
        authorize_url                 = "${local.issuer_url}/oidc/authorization"
        client_id                     = data.aws_ssm_parameter.idp_client_id.value
        client_secret                 = data.aws_ssm_parameter.idp_client_secret.value
        jwks_uri                      = "${local.issuer_url}/oidc/jwks"
        oidc_issuer                   = local.issuer_url
        token_url                     = "${local.issuer_url}/oidc/token"
      }
    }
    

    まとめ

    IaCで課題になりがちなのが秘密情報の扱い方なのですが、Secrets Managerを使用することでセキュアな情報をコードに記載することなく実装が可能です。 特に外部のSaaSと連携したインフラの構築は昨今要求が高まってきている分野だと思いますので、ぜひご活用ください。

    執筆者 : 北野友亮
    インフラエンジニア。元アプリケーションエンジニア(業務系) 現在はAWSを用いたインフラの設計・構築を担当 2025 Japan All AWS Certifications Engineers