はじめに
この記事はCyberAgent Group SRE Advent Calendar 2023の18日目の記事です。
Open Policy Agent(OPA)は、JSONやYAMLベースのInfrastructure as Code(IaC)管理において、近年広く用いられるようになっています。特に、Kubernetesのマニフェストに対するOPAとGateKeeperの導入が標準化されつつあります。一方で、Terraformのような、複雑なクラウドインフラの記述に長けたツールとの統合は、まだ挑戦的であり、実際の連携例は少ない状況です。Terraform CloudやStyraとの組み合わせでの利用事例は存在しますが、純粋なTerraform環境(例えば、バックエンドがS3の場合)でのOPAの活用は、まだそれほど多くはありません。この記事では、Terraform環境でのOPAのユースケースとその実践的な利用方法に焦点を当てていきます。
OPAとRegoについて
OPAは、Regoポリシーを集中的に管理するエンジンです。JSON形式の入力データに対して、複数のシステム間で一貫性のあるポリシーを適用することが可能です。RegoはOPA用のドメイン固有言語であり、データに対するポリシーを表現するために使用されます。
Regoを用いた複雑な論理処理や動的なJSON構造の処理は難易度が高いです。開発体制が整っていない状況でRegoの開発に着手すると、メンテナンスの面で大きな負担を背負うことになる可能性があります。そのため、コミュニティによって既に作成されたRegoを利用することを推奨します。
-
Kubernetes shared policies
Kubernetesのセキュリティに関するポリシーセット -
Rego Library
OPAコミュニティで共有されているポリシーの例
OPAを実装しているリポジトリは多くありませんが、特におすすめのツールとしてRegula for IaCがあります。これはTerraform、CloudFormation、Kubernetes manifestsなどの検証とセキュリティ検知ツールです。このツールにはすでにいくつかの事前定義されたポリシーが含まれており、そのまま利用可能です。
ただし、これら事前定義のポリシーは、セキュリティチェックのベストプラクティスに基づいており、tfsecやtflintの機能と一部重複する場合があります。
Regoのベストプラクティスについては、OPAの開発会社が提供するStyleGuideとよくあるRego bugsを参照すると良いでしょう。また、優れたLinterであるregalも利用可能ですので、ぜひ活用してみてください。
Terraform OPA連携のユースケース
OPAの検証はJSONベースなので、Terraformの各検証対象に対し、JSONとして出力する必要があります。
Terraformの検証対象は概ね以下の種類があります。
- state JSON
-
tfstate
、定期実行ジョブで現時点のインフラ構成を検証する用途です
-
- plan JSON
-
terraform plan
のJSON出力、検証目的によって利用する部分も違います。 - 詳細は以下のドキュメントをご参考ください。
-
- output JSON
組織固有ポリシーの定義と適用
組織固有のポリシー、例えば統計用のタグ指定やマルチ環境対応のフォーマット指定、特定リソースへのIAM割り当てなどは、OPAを用いて柔軟に設定できます。これらのポリシーは組織の実際の要件に沿って慎重に検討する必要があります。
例えば、以下のような例があります。
全てのVariableは、Terrafrom Workspaceごとに区別できるmap構成
variable "min_size" {
type = map(number)
default = {
"default" = 1
"prd" = 3
"stg" = 3
"dev" = 1
}
}
resource "aws_autoscaling_group" "main" {
# ...
min_size = var.min_size[terraform.workspace]
# ...
}
package terraform
import future.keywords.if
import future.keywords.in
import input as tfplan # terraform planの結果をjsonとして保存する
variable_compliance if {
required_keys := {"dev", "stg", "prd"} # 全てのvariableは、これらのKeyを所持する
all_variables_have_required_keys(tfplan.variables, required_keys)
}
all_variables_have_required_keys(variables, required_keys) if {
count(variables) == count({var_name |
var_value := variables[var_name].value
is_map(var_value)
has_all_required_keys(var_value, required_keys)
})
}
is_map(object) if {
not null in object
}
has_all_required_keys(object, keys) if {
count(keys) == count({key | object[key] != null})
}
violation[msg] if {
not variable_compliance
msg := "Validation failed: Not all variables are map or have the required keys (dev, stg, prd)."
}
$ opa eval --format pretty --data policy.rego --input input.json "data.terraform.violation"
{
"Validation failed: Not all variables are map or have the required keys (dev, stg, prd).": true
}
整合性チェック
Providerがまだ提供していないバリデーションを補完する目的で、細かい値の制御を検証する用途です。
簡単な値チェックなら、Terraform Variables
のValidation
ブロックで簡単にできますが、複雑条件の制御はValidationブロックだけでは難しいので、この場合Regoを使ってもいいと考えています。
もちろん、ほとんどの場合、変数構成の再設計とValidationブロックやpre/postconditionブロックなど組み合わせによって、解決できると思います。Regoを使って、複雑条件の制御を書くのに向いています。
以下のような例があります。
- 二つVariableのお互いに影響する条件設定
package terraform
import future.keywords.if
import future.keywords.in
import input as tfplan
variable1 := tfplan.variables.variable1.value
variable2 := tfplan.variables.variable2.value
vars_pattern_is_valid if {
variable1 == "A"
variable2 > 10
} else if {
variable2 == 10
}
violation[msg] if {
not vars_pattern_is_valid
msg := "variable1が'A'の場合、variable2は10以上でなければなりません。それ以外、variable2は10でなければなりません。"
}
$ opa eval --format pretty --data policy.rego --input input.json "data.terraform.violation"
{
"variable1が'A'の場合、variable2は10以上でなければなりません。それ以外、variable2は10でなければなりません。": true
}
その他には、目で確認していたロジックもRegoで書くことができます。
- AWS Network FirewallのSubnetとRouteTable設定が正しいかどうか
- AWS WAFのRuleは規定の必須Ruleを実装しているかどうか
自動化フローの一環
JSON Parserとして使うというのは酷いかもしれませんが、企業固有のポリシーの実施に加えて、OPAをJSON Parserとして活用することは有効です。
ただし、現時点でOPAはデバッグやエラーメッセージ以外の目的での出力使用はまだ限定的ですので、CIフローの中で特定のコンプライアンスにおけるポリシーとルールを評価し、真偽値(True/False)の結果のみを利用することをお勧めします。
以下のような例があります。
package terraform
import future.keywords.if
import future.keywords.in
plan_has_destroy[resource_type] {
some resource_changes in input.resource_changes
"delete" in resource_changes.change.actions
resource_type := resource_changes.type
}
warn[msg] if {
some resource_type in plan_has_destroy
msg := sprintf(
"Please be cautious: the resource '%s' is scheduled for destruction. Verify if this action is intentional.",
[resource_type],
)
}
リソースが削除される予定である場合、このポリシーは適切な警告メッセージを生成し、ユーザーに対してその操作が意図的であるかどうかを再確認するよう促します。
$ opa eval --format pretty --data policy.rego --input input.json "data.terraform.warn"
{
"Please be cautious: the resource 'aws_db_instance' is scheduled for destruction. Verify if this action is intentional.": true,
"Please be cautious: the resource 'aws_ecr_lifecycle_policy' is scheduled for destruction. Verify if this action is intentional.": true,
"Please be cautious: the resource 'aws_elasticache_cluster' is scheduled for destruction. Verify if this action is intentional.": true
}
セキュリティとコンプライアンス
ポリシーに基づいた自動化により、セキュリティコンプライアンスの一貫性を保つための検証です。
RegoとOPAで定義しても良いが、定義と更新管理が非常に手間がかかることもあります。tfsec
とtflint
を使う前提で、組織固有のポリシーとして改めて定義する場合に使っても良いと思います。
Regula Terraform RuleSetsが一例になります。
まとめ
OPAとRego言語の統合は、Terraformによるクラウドインフラの管理を効率的にする大きな潜在能力を持っています。しかし、Regoの開発には相応の労力が必要であり、これがTerraform環境におけるOPAの導入の障壁の一つとなっています。この記事を通じて、TerraformとOPAを統合することの重要性と、そのためのベストプラクティスを理解いただけたことでしょう。現在、TerraformとOPAの実際の連携事例はまだ少ないですが、この分野の将来的な発展が非常に期待されています。