Terraform のエフェメラルリソースが便利!...かと思ったら

Terraform のエフェメラルリソースが便利!...かと思ったら

たとえば AWS Lambda で GitHub にアクセスしたい要求があったとして。

まずはこんな感じで AWS SSM Parameter Store に GitHub の PAT を入れる場所を作ったとして。

resource "aws_ssm_parameter" "github" {
  name  = "/github-personal-access-token"
  type  = "SecureString"
  value = "__SET_LATER__"
  lifecycle {
    ignore_changes = [value]
  }
}

そしてプロビジョニング後、__SET_LATER__ としていた部分を AWS コンソール画面から書き換えたとして。

これでいよいよ準備はできたな、ということで data リソースを使って Parameter Store から取得した値を Lambda に渡そうとしたそこのあなた!

… これはアウトですよね。なぜならば Terraform の State には PAT の値が平文で記録されることになります。機密情報を data リソースで取得したり、outputs することはセキュリティ的に脆弱なのです。

resource "aws_ssm_parameter" "github" {
  name  = "/github-personal-access-token"
  type  = "SecureString"
  value = "__SET_LATER__"
  lifecycle {
    ignore_changes = [value]
  }
}

# PAT の値を取得
data "aws_ssm_parameter" "github" {
  name            = aws_ssm_parameter.github.name
  with_decryption = true
}

resource "aws_iam_role" "lambda" {
  name = "my-app-parameter-reader-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Service = "lambda.amazonaws.com" }
      Action    = "sts:AssumeRole"
    }]
  })
}

resource "aws_lambda_function" "lambda" {
  function_name = "my-lambda"
  role          = aws_iam_role.lambda.arn
  runtime       = "python3.12"
  handler       = "handler.lambda_handler"
  filename      = "my-lambda-handler.py"

  environment {
    variables = {
      # PAT の値を渡す
      GITHUB_PAT = data.aws_ssm_parameter.github.value
    }
  }
}

仕方がないので、Lambda には Parameter Store のパスだけを渡して、あとは Lambda 内で取得する処理を書くことで対応します!

resource "aws_ssm_parameter" "github" {
  name  = "/github-personal-access-token"
  type  = "SecureString"
  value = "__SET_LATER__"
  lifecycle {
    ignore_changes = [value]
  }
}

resource "aws_iam_role" "lambda" {
  name = "my-app-parameter-reader-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Service = "lambda.amazonaws.com" }
      Action    = "sts:AssumeRole"
    }]
  })
}

# Lambda が Parameter Store にアクセスできる必要があるので、アクセス権をつけてあげる必要がある。
resource "aws_iam_role_policy" "lambda" {
  name = "read-github-pat"
  role = aws_iam_role.lambda.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = "ssm:GetParameter"
      Resource = aws_ssm_parameter.github.arn
    }]
  })
}

resource "aws_lambda_function" "lambda" {
  function_name = "my-lambda"
  role          = aws_iam_role.lambda.arn
  runtime       = "python3.12"
  handler       = "handler.lambda_handler"
  filename      = "my-lambda-handler.py"

  environment {
    variables = {
      # PAT が格納されている Parameter Store のパスを渡し、あとは Lambda 内で取得する処理を書いてもらう。
      SSM_PARAMETER_GITHUB_PAT_NAME = aws_ssm_parameter.github.name
    }
  }
}

… とやっていたのがこれまでの私なのですが、ephemeral リソースというものを見つけました!

「インフラストラクチャの管理では、機密性の高い値を扱うことがよくあります。Terraform では、state ファイルなどに保存したくないデータを管理するために、ephemeral があります。」とのこと。

Managing infrastructure often requires creating and handling sensitive values that you may not want Terraform to persist outside of the current operation. Terraform provides two tools for resources to manage data you do not want to store in state or plan files: the ephemeral resource block and ephemeral write-only arguments on specific resources.

https://developer.hashicorp.com/terraform/language/manage-sensitive-data/ephemeral

!!ということは、次のようにすれば解決できるじゃない!

resource "aws_ssm_parameter" "github" {
  name  = "/github-personal-access-token"
  type  = "SecureString"
  value = "__SET_LATER__"
  lifecycle {
    ignore_changes = [value]
  }
}

# ephemeral で取得
ephemeral "aws_ssm_parameter" "github" {
  arn             = aws_ssm_parameter.github.arn
  with_decryption = true
}

resource "aws_iam_role" "lambda" {
  name = "my-app-parameter-reader-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Service = "lambda.amazonaws.com" }
      Action    = "sts:AssumeRole"
    }]
  })
}

resource "aws_lambda_function" "lambda" {
  function_name = "my-lambda"
  role          = aws_iam_role.lambda.arn
  runtime       = "python3.12"
  handler       = "handler.lambda_handler"
  filename      = "my-lambda-handler.py"

  environment {
    variables = {
      # ephemeral で取得した PAT の値を渡す
      GITHUB_PAT = ephemeral.aws_ssm_parameter.github.value
    }
  }
}

… と思った私!甘い!

ephemeral リソースで取得した値は特定のブロックでしか参照できないのです。

Refer to ephemeral resources

You can only refer to ephemeral resources in specific ephemeral contexts. Otherwise, Terraform throws an error when it plans your changes. The following are valid contexts for referring to ephemeral resources:

  • In a managed resource write-only argument
  • In another ephemeral block
  • In the locals block
  • In variable blocks with the ephemeral argument set to true
  • In child module output blocks with the ephemeral argument set to true
  • Configuring providers in the provider block
  • In a provisioner and provisioner connection configuration. Refer to Use a provisioner for more information.

https://developer.hashicorp.com/terraform/language/block/ephemeral

先の例でいくと resource "aws_lambda_function" "lambda"resource ブロックで、このなかで ephemeral の値を参照することは不可です。

*正確には、その resource が Write-only arguments という仕組みに対応していれば参照することができます(参考:https://developer.hashicorp.com/terraform/language/manage-sensitive-data/ephemeral#write-only-arguments)。ただし "aws_lambda_function" は現時点で対応していないので参照できません。

残念でした!😭😭😭