Terraform Cloud の「Run Tasks 機能」を使うと Terraform Cloud のワークフローと外部サービスと統合できて,プラン取得前・プラン取得後などのタイミングにセキュリティスキャンなど「任意の処理」を実行できる💡 例えば取得したプラン結果を外部サービスに送信して,何かしらの非準拠が発見された場合に terraform apply
の前に止めるというワークフローを実現できるようになる❗️以下の図を見るとわかるはず〜 \( 'ω')/
ちなみに Terraform Registry には Run Tasks 機能と統合された外部サービスの一覧が載っていて,例えば Snyk / Infracost / Aqua などは有名な外部サービスですぐに試せる👏 Run Tasks 機能の概要は以下のドキュメントに載っている.
今回の記事の前半では 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 に送信して,ポリシー違反があるかどうかを検査するというワークフローを構築する.
Snyk は Terraform Cloud の Run Tasks 機能をサポートしているため,無償アカウントを作ったらすぐに Webhook URL
と HMAC Key
を取得できる.取得した値を Terraform Cloud の Run Tasks として設定すれば OK👌
そして,Terraform Cloud の Workspace に Run Tasks を紐付ける.以下の画像のように Run Tasks を実行するタイミングと強制するレベルを設定できる👀 よく使いそうなのは Post-plan と Mandatory の組み合わせかしら?
- Run stage(実行タイミング)
- Pre-plan(プラン取得前)
- Post-plan(プラン取得後)
- Pre-apply(適用前)
- Enforcement level(強制レベル)
- Advisory(警告)
- Mandatory(必須)
まず Enforcement level: Advisory
で実行すると実行結果は Failed
になりつつ,後続の Cost estimation まで進んでいる👌 そして Enforcement level: Mandatory
に設定を変更してから再実行すると実行結果は同じく Failed
になり,後続の Cost estimation は実行前で止まっている💨
Terraform Cloud の Run Tasks 機能は便利だ \( 'ω')/
2. Run Tasks サービスを実装する
Terraform Cloud の Run Tasks 機能は Webhook エンドポイントさえ用意できれば自由に Run Tasks サービスとして実装できるようになっている❗️最初に載せた構成図にある通り「POST
リクエストを受け付けて・PATCH
リクエストでレスポンスを返す」インタフェースになっていて,詳しい仕様は以下のドキュメントに載っている📝
今回は 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点ある.
- リクエストを受ける
- プラン結果を取得して処理する
- 処理結果を返す
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 Tokenplan_json_api_url
: プラン結果を取得する URLtask_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.type
と data.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 チュートリアル「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 を設定してるけど,実際には詳細なエラー理由などを表示したページに遷移させてあげると良さそう.
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 integration
と 10 Workspaces attached
という制限で使えるので検証するには十分❗️
参考: aws-ia/terraform-aws-runtask-iam-access-analyzer
Terraform のプラン結果を AWS IAM Access Analyze の「ポリシー検証」と組み合わせて AWS IAM ポリシーの間違いなどを検出できる tf-policy-validator
コマンドは前に紹介した.
実は tf-policy-validator
コマンド を Terraform Cloud の Run Tasks 機能に組み込むプロジェクト「aws-ia/terraform-aws-runtask-iam-access-analyzer」もある❗️
HashiTalks 2023 のセッション「Validate IAM Policy Using Terraform Run Tasks」もわかりやすくて良かった👏