kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Terraform Cloud のワークフローを拡張できる Run Tasks 機能

Terraform Cloud の「Run Tasks 機能」を使うと Terraform Cloud のワークフローと外部サービスと統合できて,プラン取得前・プラン取得後などのタイミングにセキュリティスキャンなど「任意の処理」を実行できる💡 例えば取得したプラン結果を外部サービスに送信して,何かしらの非準拠が発見された場合に terraform apply の前に止めるというワークフローを実現できるようになる❗️以下の図を見るとわかるはず〜 \( 'ω')/

Terraform Cloud Run Tasks Integrations Setup | Terraform | HashiCorp Developer より引用

ちなみに Terraform Registry には Run Tasks 機能と統合された外部サービスの一覧が載っていて,例えば Snyk / Infracost / Aqua などは有名な外部サービスですぐに試せる👏 Run Tasks 機能の概要は以下のドキュメントに載っている.

developer.hashicorp.com

今回の記事の前半では Run Tasks 機能と Snyk の連携を試す Terraform チュートリアル「Configure Snyk run task in Terraform Cloud」を紹介しつつ,記事の後半では Run Tasks 機能の仕組みを理解しながら簡単な Run Tasks サービスを実装してみる❗️

1. Configure Snyk run task in Terraform Cloud を試す

Terraform チュートリアル「Configure Snyk run task in Terraform Cloud」はサクッと試せて,Run Tasks 機能の便利さと拡張性の高さを体験できておすすめ✌ 簡単に言うと,Terraform (AWS Provider) でセキュリティグループを追加する前に「プラン結果」を Snyk に送信して,ポリシー違反があるかどうかを検査するというワークフローを構築する.

developer.hashicorp.com

Snyk は Terraform Cloud の Run Tasks 機能をサポートしているため,無償アカウントを作ったらすぐに Webhook URLHMAC Key を取得できる.取得した値を Terraform Cloud の Run Tasks として設定すれば OK👌

Terraform Cloud に Run Tasks の Webhook URL を設定する

そして,Terraform Cloud の Workspace に Run Tasks を紐付ける.以下の画像のように Run Tasks を実行するタイミングと強制するレベルを設定できる👀 よく使いそうなのは Post-planMandatory の組み合わせかしら?

  • Run stage(実行タイミング)
    • Pre-plan(プラン取得前)
    • Post-plan(プラン取得後)
    • Pre-apply(適用前)
  • Enforcement level(強制レベル)
    • Advisory(警告)
    • Mandatory(必須)

Terraform Cloud の Workspace に Run Tasks を紐付ける

まず Enforcement level: Advisory で実行すると実行結果は Failed になりつつ,後続の Cost estimation まで進んでいる👌 そして Enforcement level: Mandatory に設定を変更してから再実行すると実行結果は同じく Failed になり,後続の Cost estimation は実行前で止まっている💨

Enforcement level: Advisory

Enforcement level: Mandatory

Terraform Cloud の Run Tasks 機能は便利だ \( 'ω')/

2. Run Tasks サービスを実装する

Terraform Cloud の Run Tasks 機能は Webhook エンドポイントさえ用意できれば自由に Run Tasks サービスとして実装できるようになっている❗️最初に載せた構成図にある通りPOST リクエストを受け付けて・PATCH リクエストでレスポンスを返す」インタフェースになっていて,詳しい仕様は以下のドキュメントに載っている📝

developer.hashicorp.com

今回は Amazon API Gateway と AWS Lambda を組み合わせた構成でサンプルの Run Tasks サービスを実装してみた(実用的なものではなくあくまで疎通確認として💨).ポイントを抜粋して紹介する❗️

Amazon API Gateway

今回 Amazon API Gateway と AWS Lambda を組み合わせた構成は AWS SAM を使って構築した.以下に AWS SAM の template.yaml を載せる.Amazon API Gateway では /run-tasks というエンドポイントで POST リクエストを受け付けて AWS Lambda 関数に渡している👌

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
  Function:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: sandbox-sam-terraform-cloud-run-tasks
      CodeUri: app/
      Handler: app.lambda_handler
      Runtime: python3.11
      Role: arn:aws:iam::123456789012:role/lambda
      Architectures:
        - x86_64
      Events:
        Api:
          Type: Api
          Properties:
            Path: /run-tasks
            Method: post

AWS Lambda 関数

以下に Python で実装した AWS Lambda 関数の app/app.py を載せる(実装はあくまでサンプルレベルなのでご了承を🙏).ポイントは大きく3点ある.

  1. リクエストを受ける
  2. プラン結果を取得して処理する
  3. 処理結果を返す
import json
import requests


def lambda_handler(event, context):
    body = json.loads(event['body'])
    print(body)

    headers = {
        'Authorization': 'Bearer {}'.format(body['access_token']),
        'Content-type': 'application/vnd.api+json',
    }

    # プラン結果を取得する
    planned = requests.get(
        body['plan_json_api_url'],
        headers=headers,
    )

    # プラン結果のサイズを取得する
    size = len(json.dumps(planned.json()))

    # プラン結果が 3000 文字以上の場合はエラーを返す
    if (size > 3000):
        playload = {
            'data': {
                'type': 'task-results',
                'attributes': {
                    'status': 'failed',
                    'message': 'The size of the planned value is too large.',
                    'url': 'https://kakakakakku.hatenablog.com/',
                }
            }
        }
    else:
        playload = {
            'data': {
                'type': 'task-results',
                'attributes': {
                    'status': 'passed',
                    'message': 'Ready to apply!',
                    'url': 'https://kakakakakku.hatenablog.com/',
                }
            }
        }

    # 処理結果を返す
    requests.patch(
        body['task_result_callback_url'],
        headers=headers,
        data=bytes(json.dumps(playload), encoding='utf-8')
    )

    return {
        'statusCode': 200,
        'body': json.dumps({}),
    }

1. リクエストを受ける

まず event['body'] からリクエストのペイロードを取得すると Run Tasks 機能のドキュメント載っている以下のような JSON を取得できる(Post-plan 前提).今回使う代表的な値を抜粋して紹介する.

  • access_token: Terraform 側の API を実行するときに必要な Bearer Token
  • plan_json_api_url: プラン結果を取得する URL
  • task_result_callback_url: 処理結果を返すコールバック用 URL
{
  "payload_version": 1,
  "stage": "post_plan",
  "access_token": "4QEuyyxug1f2rw.atlasv1.iDyxqhXGVZ0ykes53YdQyHyYtFOrdAWNBxcVUgWvzb64NFHjcquu8gJMEdUwoSLRu4Q",
  "capabilities": {
    "outcomes": true
  },
  "configuration_version_download_url": "https://app.terraform.io/api/v2/configuration-versions/cv-ntv3HbhJqvFzamy7/download",
  "configuration_version_id": "cv-ntv3HbhJqvFzamy7",
  "is_speculative": false,
  "organization_name": "hashicorp",
  "plan_json_api_url": "https://app.terraform.io/api/v2/plans/plan-6AFmRJW1PFJ7qbAh/json-output",
  "run_app_url": "https://app.terraform.io/app/hashicorp/my-workspace/runs/run-i3Df5to9ELvibKpQ",
  "run_created_at": "2021-09-02T14:47:13.036Z",
  "run_created_by": "username",
  "run_id": "run-i3Df5to9ELvibKpQ",
  "run_message": "Triggered via UI",
  "task_result_callback_url": "https://app.terraform.io/api/v2/task-results/5ea8d46c-2ceb-42cd-83f2-82e54697bddd/callback",
  "task_result_enforcement_level": "mandatory",
  "task_result_id": "taskrs-2nH5dncYoXaMVQmJ",
  "vcs_branch": "main",
  "vcs_commit_url": "https://github.com/hashicorp/terraform-random/commit/7d8fb2a2d601edebdb7a59ad2088a96673637d22",
  "vcs_pull_request_url": null,
  "vcs_repo_url": "https://github.com/hashicorp/terraform-random",
  "workspace_app_url": "https://app.terraform.io/app/hashicorp/my-workspace",
  "workspace_id": "ws-ck4G5bb1Yei5szRh",
  "workspace_name": "tfr_github_0",
  "workspace_working_directory": "/terraform"
}

2. プラン結果を取得して処理する

plan_json_api_url にプラン結果を取得する URL が設定されているため,GET リクエストを実行するとプラン結果を JSON 形式で取得できる.あとはプラン結果をゴニョゴニョして,セキュリティスキャンなど任意の処理を実行する.今回はあくまでサンプルとして「プラン結果のサイズが大きすぎたら警告する」という処理を実装してみた.特に意味はないけども〜🔥

プラン結果を処理して問題がなければ passed,問題があれば failed を処理結果としてコールバック用 URL に返す.コールバックに関しては次に紹介する💡

3. 処理結果を返す

最後は task_result_callback_url に設定されているコールバック用 URL に PATCH で処理結果を返す.ペイロードの形式は決まっていて,ドキュメントには以下のように載っている.項目の中には (Recommended, but optional)(Optional) もあるため,必須という意味だと data.typedata.attributes.status のみで OK👌

{
  "data": {
    "type": "task-results",
    "attributes": {
      "status": "passed",
      "message": "4 passed, 0 skipped, 0 failed",
      "url": "https://external.service.dev/terraform-plan-checker/run-i3Df5to9ELvibKpQ",
    },
    "relationships": {
      "outcomes": {
        "data": [...]
      }
    }
  }
}

動作確認

さっそく Amazon API Gateway のエンドポイントを Terraform Cloud の Run Tasks として設定する.今回はサンプルなので HMAC Key は設定しなかった.

Terraform Cloud に Run Tasks の Webhook URL を設定する

前半で試した Terraform チュートリアル「Configure Snyk run task in Terraform Cloud」の Workspace に Run Tasks を紐付けて実行すると,期待通りに Post-plan で警告を出せた❗️

今回は Enforcement level: Advisory にしてるから実行結果は Failed になりつつ,後続の Cost estimation まで進んでいるし,Details には AWS Lambda 関数側で設定した data.attributes.message の値が表示されている👌 また View more details をクリックすると data.attributes.url の URL に遷移する.今回はサンプルとして kakakakakku blog の URL を設定してるけど,実際には詳細なエラー理由などを表示したページに遷移させてあげると良さそう.

Enforcement level: Advisory

Terraform Cloud の Run Tasks サービスを実装できちゃったぜー \( 'ω')/

まとめ

Terraform Cloud の「Run Tasks 機能」を使うと Terraform Cloud のワークフローと外部サービスと統合できる.今回の記事では Run Tasks 機能と Snyk の連携をサクッと試せる Terraform チュートリアル「Configure Snyk run task in Terraform Cloud」の紹介と Run Tasks サービスを実装する流れを紹介した.Terraform Cloud の Run Tasks 機能はワークフローを拡張できて活用の幅が広そう✌

ちなみに Terraform Cloud の Run Tasks 機能は Plus プランだと制限なく使えるけど,Free プランでも 1 Run task integration10 Workspaces attached という制限で使えるので検証するには十分❗️

www.hashicorp.com

参考: aws-ia/terraform-aws-runtask-iam-access-analyzer

Terraform のプラン結果を AWS IAM Access Analyze「ポリシー検証」と組み合わせて AWS IAM ポリシーの間違いなどを検出できる tf-policy-validator コマンドは前に紹介した.

kakakakakku.hatenablog.com

実は tf-policy-validator コマンド を Terraform Cloud の Run Tasks 機能に組み込むプロジェクト「aws-ia/terraform-aws-runtask-iam-access-analyzer」もある❗️

github.com

HashiTalks 2023 のセッション「Validate IAM Policy Using Terraform Run Tasks」もわかりやすくて良かった👏

www.youtube.com