Skip to content

Instantly share code, notes, and snippets.

@suzuki-shunsuke
Created July 7, 2021 09:32
Show Gist options
  • Save suzuki-shunsuke/9372337aa62a6f8394bb136582ec068e to your computer and use it in GitHub Desktop.
Save suzuki-shunsuke/9372337aa62a6f8394bb136582ec068e to your computer and use it in GitHub Desktop.
Quipper における Rego の活用事例

自己紹介

Rego で何をテストしているか

  • Conftest で Terraform Configuration と k8s manifest をテスト
    • k8s manifest は kustomize build で生成したものに対して実行
    • Terraform は Plan File に対して実行
      • ただし Backend は plan file に含まれていないので、 HCL に対して直接 conftest を実行

いつどうやって実行しているか

  • PR で変更されたコードに関して CI で実行
  • 失敗したら PR にコメントで通知

image

  • opa fmt, conftest verify (policy testing) も CI で実行

Terraform

  • Backend の設定
  • S3 の log の保存先
  • aws_cloudwatch_log_group の retention_in_days が設定されているか
  • etc

k8s

  • nodeAffinity を指定しているか
  • Resource Limit が設定されているか
    • CPU Request
    • Memory Limit/Request
  • Argo Rollouts の Strategy
  • etc

サンプル: S3 の log の保存先の prefix が規約どおりになっているか

package main

deny_aws_s3_bucket_logging_target_prefix[msg] {
	walk(input.planned_values.root_module, [path, value])
	value.type == "aws_s3_bucket"
	exp := sprintf("s3/%s/", [value.values.bucket])

	value.values.logging[_].target_prefix != exp
	msg = sprintf("%s: aws_s3_bucket.logging.target_prefix should be '%s'", [value.address, exp])
}

サンプル: Policy Testing

package main

wrap_single_resource(resource) = x {
	x := {"planned_values": {"root_module": {"resources": [resource]}}}
}

test_deny_aws_s3_bucket_logging_target_prefix__pass {
	resource := {
		"type": "aws_s3_bucket",
		"address": "aws_s3_bucket.main",
		"values": {"bucket": "foo", "logging": [{"target_prefix": "s3/foo/"}]},
	}

	result := deny_aws_s3_bucket_logging_target_prefix with input as wrap_single_resource(resource)

	count(result) == 0
}

test_deny_aws_s3_bucket_logging_target_prefix__failure {
	resource := {
		"type": "aws_s3_bucket",
		"address": "aws_s3_bucket.main",
		"values": {"bucket": "foo", "logging": [{"target_prefix": "s3/foo"}]},
	}

	resources := wrap_single_resource(resource)
	msg := sprintf("%s: aws_s3_bucket.logging.target_prefix should be '%s'", [resource.address, "s3/foo/"])
	deny_aws_s3_bucket_logging_target_prefix[msg] with input as resources
}

サンプル: conftest test -combine

Conftest でファイルパスに依存したテストをしたい場合、 -combine option が便利

package main

deny_slo_terraform_backend_s3_key[msg] {
	elem := input[_]
	path := trim_prefix(elem.path, "./") # ex. ./slo/foo/jp/state.tf => slo/foo/jp/state.tf
	startswith(path, "slo/") # only slo
	ps := split(path, "/")
	exp := concat("/", array.slice(ps, 1, count(ps) - 1))
	key := elem.contents.terraform.backend.s3.key
	not contains(key, sprintf("/%s/", [exp]))
	msg = sprintf("%s: S3 backend's key got %s, want */%s/*", [path, key, exp])
}

サンプル: Conftest の -data option を使って環境の違いを吸収

複数の環境に同じ policy を適用したいが、環境によってパラメータが違う場合

https://www.conftest.dev/options/#-data

data.yaml

aws_s3_bucket:
  logging:
    target_bucket: example.com
package main

import data

deny_aws_s3_bucket_logging_target_bucket[msg] {
	walk(input.planned_values.root_module, [path, value])
	value.type == "aws_s3_bucket"

	exp := data.aws_s3_bucket.logging.target_bucket
	value.values.logging[_].target_bucket != exp
	msg := sprintf("%s: aws_s3_bucket.logging.target_bucket should be '%s'", [value.address, exp])
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment