ちょっと話題の記事

Terraformで予防的ガードレールを実装したい!「Terraform-Compliance」のご紹介

Terraformだって出来るもん
2021.07.07

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

7月7日はクラスメソッドの創立記念日です。今日は次々にブログ記事があがってくることでしょう!

また、わたくしゴトですが 18 期からコンサルティング部の部長を務めさせていただくことになりました。「あいつ、部長になったらブログ書かなくなったよな」と後ろ指さされないように、ブログの書ける部長として頑張っていきたいと思います。

ここから

AWS が一般的に利用されるようになった昨今最近では単に AWS を使いたいという相談よりも、どうやって AWS 環境をうまく運用するべきか?というご相談が多くなっているように感じます。よくある相談の 1 つが「ガバナンス」です。

ある程度ユーザーに権限をもたせ自由にリソースを作成できるようにした結果、

  • 「パブリックアクセスで S3 を公開してました、、」
  • 「パブリック(0.0.0.0)で許可するセキュリティグループが設定されていました、、」
  • 「ログが設定されておらず何が事故ったのか何もわからん、、」

よくある話ですね。

一般的に AWS のベストプラクティスに則れば AWS Control TowerAWS ConfigAWS SecurityHubAWS Organizations(SCP)といったサービスを使い、検出および予防的ガードレールを提案する形になるかと思います。

IaC における予防ガードレール

ガードレールを検討される規模の場合、AWS コンソールだけでなく IaC などの構成ツールを利用している環境も少なくないと思いますが、最近では IaC 向けにも予防的ガードレールの仕組みが提供されています。

CloudFormation をお使いの場合は AWS 純正のツールとして AWS CloudFormation Guard が提供されています。デプロイ前に指定したルールに準拠していることをチェックし、非準拠なリソースのデプロイを未然に防ぎます。

Terraform ユーザ向け

しかしながら、私のような Terraform 派はどうすれば良いのでしょうか?

というのが、今回の趣旨です。

前置きが長くなりましたが、Terraform の場合は選択肢として 2 つです。(私が知っているのは、、)

Sentinel は Terraform 同様に Hashicorp 社が提供していますが、Terraform Cloud(Team & Governance プラン以上)や Terraform Enterprise 向けのプロダクトですので有償版になります。また、Terraform 1.0 のリリースと合わせて Terraform Cloud Run Checks が発表されていますが、こちらはまだベータ版の扱いです。

一方、terraform-compliance はオープンソースとして提供されています。無償で利用可能ということもあり、以前より気になっているツールでした。

本記事では terraform-compliance をインストール、サンプルの実行あたりまで検証して、利用方法等をシェアいたします。

検証環境

今回はとりあえず terraform-compliance の使い方を試してみることが目的ですので、ローカル PC で検証しています。実環境では CI/CD パイプラインに組み込む必要があるかと思いますが、今回はそこまで検証していませんのであしからず。

検証環境のローカル PC 環境は以下のとおりです。

$ terraform -version
Terraform v1.0.1
on darwin_amd64

$ docker -v
Docker version 20.10.6, build 370c289

$ sw_vers
ProductName:	Mac OS X
ProductVersion:	10.15.7
BuildVersion:	19H524

今回は執筆時点の最新バージョン Terraform v1.0.1 を使用していますが、terraform-compliance は Terraform v0.12 以上でサポートされています。

インストール

それではインストールしていきましょう。terraform-compliance のインストール方法は以下の 2 とおりです。

今回はローカル環境をあまり汚したくないという意図もあり、Docker 版で検証します。イメージは Docker Hub より入手します。functionコマンドでユーザ定義関数として登録しておくと呼び出しが楽になります。

ユーザ定義関数 terraform-compliance を作成

$ terraform-compliance
Unable to find image 'eerkunt/terraform-compliance:latest' locally
latest: Pulling from eerkunt/terraform-compliance
b4d181a07f80: Pull complete
de8ecf497b75: Pull complete
69b92f9e5e70: Pull complete
1f2b8e2c8ad8: Pull complete
ce65eeb74c3c: Pull complete
064992249895: Pull complete
82c71336f0ef: Pull complete
0df1d219a873: Pull complete
Digest: sha256:868b2fdf03434ecbcad8bfdac066e9270f57fe51201ec41dfd6ee7069d787682
Status: Downloaded newer image for eerkunt/terraform-compliance:latest

$ function terraform-compliance { docker run --rm -v $(pwd):/target -i -t eerkunt/terraform-compliance "$@"; }

$ terraform-compliance --version
terraform-compliance v1.3.21 initiated

1.3.21

使用方法

BDD (Behavior Driven Development)

terraform-compliance は BDD(ビヘイビア駆動開発)を処理するために radish を使用しています。

BDD については今回の terraform-compliance で初めて触ったので多くを語れませんが、テストコードを自然言語に近い形で記述できるためテストコードの可読性が高く、理解しやすいコンテキストを提供します。

BDD には 3 つのコンポーネントがあります。

  • Feature
  • Scenario/Scenario Outline
  • Steps

Feature

Feature はいくつかの Scenario で構成される機能ファイルの全体像を記述します。

Featureの例

Feature: Security Groups should be used to protect services/instances
  In order to improve security
  As engineers
  We'll use AWS Security Groups as a Perimeter Defence

この機能は「サービス/インスタンスを保護する為にセキュリティグループを使用する必要」があり、「セキュリティを向上させるために、エンジニアとして、AWS 境界防御にセキュリティグループを使用する」ためのテストがここに書かれてるんだな、というのがすぐに理解できますね。

Senario

Senario では以下の BDD ディレクティブを使用した複数の Steps を含むテストを定義します。

  • GIVEN
    • 検索するリソースを指定。必須パラメータ。
  • WHEN
    • GIVEN で指定されたリソースのフィルタリング条件。必須ではない。
  • THEN
    • マッチング基準を定義。シナリオが失敗するか成功するかの判断をするステップ。必須ではない。

WEHNまたはTHENに対してANDで始まる追加の拡張ステップを含めることも可能です。

Senarioの例

Scenario: Ensure all resources have tags
    Given I have resource that supports tags defined
    Then it must contain tags
    And its value must not be null

このシナリオは「すべてのリソースでタグを持っていることを確認」するためのもので

  • Given(検索するリソース) : 持っているリソースで、タグがサポートされれているものすべて
  • Then(マッチング基準) : タグが含まれていること
  • AND(Thenの拡張ステップ) : かつ、値が null でないこと

各ディレクティブで記述可能な書式が数パターンあるので、詳細はドキュメントを参照ください。

実行方法

terraform-compliance は以下のように実行して使用します。

terraform-compliance 実行方法

$ terraform-compliance -p plan.out -f /path/to/feature/files/

-p plan.outterraform planの結果をファイル出力したものです。terraform planのファイル出力は以下のコマンドで実行できます。

terraform-compliance 実行方法

$ terraform plan -out=plan.out

-f /hogehoge/は機能ファイルが配置されているディレクトリを指定します。-f git:https://github.com/user/repoのように Git リポジトリを指定することも可能です。ディレクトリ、またはリポジトリ内のすべての機能ファイルが非再帰的に処理されます。

コンプライアンスチェック

それでは実行してみましょう。以下のとおり、ただ単に S3 バケットを作成するだけのテンプレートを準備しました。

コンプライアンス非準拠なS3バケットの作成

resource "aws_s3_bucket" "test-bucket" {
  bucket = "cm-marumo-tf-compliance-test"
  acl    = "private"
}

次に terraform-compliance の機能ファイルを作成します。こちらは公開リポジトリから S3.feature を拝借しました。内容としては

  • 「SSEが設定されていること」
  • 「ログ設定されていること」
  • 「バージョニングが有効化されていること」

が準拠項目となっています。

tf-compliance/S3.feature

Feature: S3 related general feature
	Implemented
		- Data must be encrypted at rest (what if it's suppose to be public?, maybe check if it's suppose to be public before? What if it's mistakenly set as public?)
		- Data stored in S3 has versioning enabled


	Questionable checks (only checks if one pass)
	- S3 must have access logging enabled


	Scenario: Data must be encrypted at rest
		Given I have aws_s3_bucket defined
		Then it must have server_side_encryption_configuration

	
	# check if at least one s3 has logging enabled, because logging will require another s3
	@noskip_at_line_20
	Scenario: S3 must have access logging enabled 
		Given I have aws_s3_bucket defined
		When it has logging


	Scenario: Data stored in S3 has versioning enabled
		Given I have aws_s3_bucket defined
		Then it must have versioning
		Then it must have enabled
		And its value must be true

では、terraform planの結果をファイル出力して、terraform-complianceを実行してみます。

$ terraform plan -out=plan.out

$ terraform-compliance -p plan.out -f tf-compliance/
terraform-compliance v1.3.21 initiated

. Converting terraform plan file.
ERROR: Failed to convert terraform plan file to JSON format via terraform. Here is the error :
None
╷
│ Error: error configuring S3 Backend: no valid credential sources for S3 Backend found.
│
│ Please see https://www.terraform.io/docs/language/settings/backends/s3.html
│ for more information about providing credentials.
│
│ Error: NoCredentialProviders: no valid providers in chain. Deprecated.
│ 	For verbose messaging see aws.Config.CredentialsChainVerboseErrors
│

・・・。なぜか plan.outの変換に失敗していますし、クレデンシャルのエラーが出ていますね、、。

terraform-compliance の中にも terraform が含まれているのですが、使用したバージョンがv1.0.1と新しすぎたので変換できなかったので、state を読みに行こうとしてるのでしょうか??

ちょっと原因が特定できていませんが、plan.outの JSON 変換に失敗しているのであれば、terraform show -jsonJSON 形式に変換したうえで渡してあげれば問題ありません。

$ terraform show -json plan.out > plan.out.json

$ terraform-compliance -p plan.out.json -f tf-compliance/
terraform-compliance v1.3.21 initiated

? Features	: /target/tf-compliance/
? Plan File	: /target/plan.out.json

? Running tests. ?

Feature: S3 related general feature  # /target/tf-compliance/S3.feature
    Implemented
    - Data must be encrypted at rest (what if it's suppose to be public?, maybe check if it's suppose to be public before? What if it's mistakenly set as public?)
    - Data stored in S3 has versioning enabled
    Questionable checks (only checks if one pass)
    - S3 must have access logging enabled

    Scenario: Data must be encrypted at rest
        Given I have aws_s3_bucket defined
		Failure: aws_s3_bucket.test-bucket (aws_s3_bucket) does not have server_side_encryption_configuration property.
        Then it must have server_side_encryption_configuration
          Failure:

    @noskip_at_line_20
    Scenario: S3 must have access logging enabled
        Given I have aws_s3_bucket defined
		Failure: Can not find any logging property for aws_s3_bucket resource in terraform plan.
        When it has logging
          Failure:

    Scenario: Data stored in S3 has versioning enabled
        Given I have aws_s3_bucket defined
		Failure: aws_s3_bucket.test-bucket (aws_s3_bucket) does not have versioning property.
        Then it must have versioning
          Failure:
        Then it must have enabled
        And its value must be true

1 features (0 passed, 1 failed)
3 scenarios (0 passed, 3 failed)
8 steps (3 passed, 3 failed, 2 skipped)
Run 1625605475 finished within a moment

おぉ!正しく非準拠なテンプレートであることを検出していますね。

修正して再実行

では、コンプライアンス非準拠として指摘された箇所を修正して、以下のとおり S3 テンプレートを更新します。

コンプライアンス準拠なS3バケットの作成

resource "aws_s3_bucket" "test-bucket" {
  bucket = "cm-marumo-tf-compliance-test"
  acl    = "private"

  logging {
    target_bucket = "cm-marumo-testlog"
  }

  server_side_encryption_configuration {
    rule {
      apply_server_side_encryption_by_default {
        sse_algorithm     = "aws:kms"
      }
    }
  }

  versioning {
    enabled = true
  }
}

再度、plan.outを出力して JSON 変換したファイルを、terraform-compliance に渡します。

$ terraform plan -out=plan.out
$ terraform show -json plan.out > plan.out.json
$ terraform-compliance -p plan.out.json -f tf-compliance/

terraform-compliance v1.3.21 initiated

? Features	: /target/tf-compliance/
? Plan File	: /target/plan.out.json

? Running tests. ?

Feature: S3 related general feature  # /target/tf-compliance/S3.feature
    Implemented
    - Data must be encrypted at rest (what if it's suppose to be public?, maybe check if it's suppose to be public before? What if it's mistakenly set as public?)
    - Data stored in S3 has versioning enabled
    Questionable checks (only checks if one pass)
    - S3 must have access logging enabled

    Scenario: Data must be encrypted at rest
        Given I have aws_s3_bucket defined
        Then it must have server_side_encryption_configuration

    @noskip_at_line_20
    Scenario: S3 must have access logging enabled
        Given I have aws_s3_bucket defined
        When it has logging

    Scenario: Data stored in S3 has versioning enabled
        Given I have aws_s3_bucket defined
        Then it must have versioning
        Then it must have enabled
        And its value must be true

1 features (1 passed)
3 scenarios (3 passed)
8 steps (8 passed)
Run 1625607902 finished within a moment

すべて PASS することが出来たので、コンプライアンスに準拠したリソース定義であることが確認できましたので、あとはterraform applyでデプロイするだけです。

今回の検証はココまでです!

実環境での利用

今回はとりあえず terraform-compliance を使ってみたく、ローカル環境で検証していますが実環境においては IaC が CI/CD パイプラインで運用されていなければ意味がありません。(ローカルの terraform からデプロイされたらコンプライアンスチェックとおりませんので、、)

近いうちに AWS CodePipeline に terraform-compliance に組み込んでパイプラインのなかでコンプライアンスチェックを行い、問題なければデプロイが実行されるような CI/CD 環境を作ってみたいと思います。

まとめ

  • AWS の利用が広がるにつれて、ガバナンスを利かせた運用が課題になりつつある
  • IaC として Terraform を利用している場合、terraform-compliance も 1 つの選択肢
  • terraform-compliance は BDD 構文を使用したガードレール定義であるためテストコードの可読性が高い
  • Git リポジトリから機能ファイルを読み込めるので、リポジトリで管理しておくとマルチアカウントでも同じガバナンスを利かせることが容易
  • コンプライアンスチェックのツールは CI/CD パイプラインに組み込み、デプロイは必ずパイプラインから実行しなければ意味がありません

以上!大阪オフィスの丸毛(@marumo1981)でした!

参考