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