Command line utility that validate a terraform plan file against certain rules based on input rules.
Before apply any terraform plan:
- make sure no aws security group is open to the public.
- make sure storage encryption and multi-az enabled on all aws rds instances.
- ...
pip install terraform-validator
validate-tfplan -p <path to .tfplan file> -r <path to your terraform rules .json file>
Command line exits with code 0 if all resources are valid. Exits with code 1 if validation failed and logs validation failure to stderr.
Rules file are in .json format. The out-most object is a array. Each element in the array defines rules for a single terraform resource type.
# definition of a single resource type and rules around it.
{
"resource": "aws_db_instance",
"rules": [
{
"name": "encryption should be enabled",
"expr": "R['storage_encrypted']=='true'"
}
]
}Within that, rules attribute is an array of rule objects.
Each rule objects have two attributes, name and expr.
name is used for identifying the purpose of this rule.
expr is used for validating the rule.
The rule expression is string of a python-like syntax expression that should be evaluated in the context of a resource of
current resource type and return True (means valid) or False (means invalid).
A special variable R that contains all attributes of the resource is provided to the expression when its being evaluated.
Attributes can be accessed by key from R, for example R['storage_encrypted'].
The key string can be an attribute name or a regular expression that matches one or more attribute name.
When the key is an attribute name, R[key] returns the value of that attribute.
When the key is a regular expression, R[key] returns a list of values of all attributes that
match the regular expression.
For example, by using command terraform show my.tfplan, you can check what attribute a resource have.
+ my_resource_type.my_resource
engine: "mysql"
allocated_storage: "10"
apply_immediately: "<computed>"
storage_encrypted: "true"
parameter.#: "1"
ingress.3133039999.cidr: "0.0.0.0/0"
ingress.3133039999.cidr: "127.0.0.1/0"
Then, you can imagine that R['engine'] is 'mysql', R['^ingress.[0-9]+.cidr$']] is ['0.0.0.0/0', '127.0.0.1/0']
and write validation logic accordingly.
You can set a strict requirement for an expression to return at least one value by using the any() function:
any(R['expression'])
and you can chain your expressions with boolean operators like or, and, not, etc. Any valid Python expression is accepted.
[
{
"resource": "aws_db_instance",
"rules": [
{
"name": "encryption should be enabled",
"expr": "R['storage_encrypted']=='true'"
},
{
"name": "multi-az should be enabled",
"expr": "R['multi_az']=='true'"
}
]
},
{
"resource": "aws_security_group_rule",
"rules":[
{
"name": "no ingress security group role should be open to the public",
"expr": "R['type'] != 'ingress' or (not any(R['^cidr_blocks.[0-9]+$']) or all([val != '0.0.0.0/0' for val in R['^cidr_blocks.[0-9]+$']]))"
}
]
},
{
"resource": "aws_db_security_group",
"rules":[
{
"name": "no ingress security group role should be open to the public",
"expr": "not any(R['^ingress.[0-9]+.cidr$']) or all([val != '0.0.0.0/0' for val in R['^ingress.[0-9]+.cidr$']])"
}
]
}
]- Add the ability to validate against specific resource name.
- Add nested module validation.
- Add Windows tfjson binary for Windows machines.
- Add access to dynamic name value pair attribute. For example:
parameter.2943476575.name: "cluster-enabled"
parameter.2943476575.value: "no"
Special thanks to tfjson for providing .tfplan file parsing ability.