rahulkiit / opaterraform

Open Policy Agent with Terraform version 0.12.5

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

opaterraform

Open Policy Agent with Terraform version 0.12.5

While trying to follow the terraform example from OPA(Open Policy Agent) documentation (https://www.openpolicyagent.org/docs/v0.10.7/terraform/) faced lot's of issue because of terraform version upgrade.

Step 1 : Create and save a Terraform plan

create main.tf file and generate the terraform plan output file

terraform plan --out -no-color tfplan.output

Step 2 : Convert the Terraform plan into JSON (This Json format is different from one generated by tfplan)

terraform show -json tfplan.output | jq ".resource_changes | map ({(.type|tostring) : .}) | add" > tfplan.json

terraform show -json tfplan.output | jq ".resource_changes | map ({(.address|tostring) : .}) | add" > tfplan.json

tfplan.json

{
  "aws_autoscaling_group": {
    "address": "aws_autoscaling_group.my_asg",
    "mode": "managed",
    "type": "aws_autoscaling_group",
    "name": "my_asg",
    "provider_name": "aws",
    "change": {
      "actions": [
        "create"
      ],
      "before": null,
      "after": {
        "availability_zones": [
          "us-west-1a"
        ],
        "desired_capacity": 4,
        "enabled_metrics": null,
        "force_delete": true,
        "health_check_grace_period": 300,
        "health_check_type": "ELB",
        "initial_lifecycle_hook": [],
        "launch_configuration": "my_web_config",
        "launch_template": [],
        "max_size": 5,
        "metrics_granularity": "1Minute",
        "min_elb_capacity": null,
        "min_size": 1,
        "mixed_instances_policy": [],
        "name": "my_asg",
        "name_prefix": null,
        "placement_group": null,
        "protect_from_scale_in": false,
        "suspended_processes": null,
        "tag": [],
        "tags": null,
        "termination_policies": null,
        "timeouts": null,
        "wait_for_capacity_timeout": "10m",
        "wait_for_elb_capacity": null
      },
      "after_unknown": {
        "arn": true,
        "availability_zones": [
          false
        ],
        "default_cooldown": true,
        "id": true,
        "initial_lifecycle_hook": [],
        "launch_template": [],
        "load_balancers": true,
        "mixed_instances_policy": [],
        "service_linked_role_arn": true,
        "tag": [],
        "target_group_arns": true,
        "vpc_zone_identifier": true
      }
    }
  },
  "aws_instance": {
    "address": "aws_instance.web",
    "mode": "managed",
    "type": "aws_instance",
    "name": "web",
    "provider_name": "aws",
    "change": {
      "actions": [
        "create"
      ],
      "before": null,
      "after": {
        "ami": "ami-09b4b74c",
        "credit_specification": [],
        "disable_api_termination": null,
        "ebs_optimized": null,
        "get_password_data": false,
        "iam_instance_profile": null,
        "instance_initiated_shutdown_behavior": null,
        "instance_type": "t2.micro",
        "monitoring": null,
        "source_dest_check": true,
        "tags": null,
        "timeouts": null,
        "user_data": null,
        "user_data_base64": null
      },
      "after_unknown": {
        "arn": true,
        "associate_public_ip_address": true,
        "availability_zone": true,
        "cpu_core_count": true,
        "cpu_threads_per_core": true,
        "credit_specification": [],
        "ebs_block_device": true,
        "ephemeral_block_device": true,
        "host_id": true,
        "id": true,
        "instance_state": true,
        "ipv6_address_count": true,
        "ipv6_addresses": true,
        "key_name": true,
        "network_interface": true,
        "network_interface_id": true,
        "password_data": true,
        "placement_group": true,
        "primary_network_interface_id": true,
        "private_dns": true,
        "private_ip": true,
        "public_dns": true,
        "public_ip": true,
        "root_block_device": true,
        "security_groups": true,
        "subnet_id": true,
        "tenancy": true,
        "volume_tags": true,
        "vpc_security_group_ids": true
      }
    }
  },
  "aws_launch_configuration": {
    "address": "aws_launch_configuration.my_web_config",
    "mode": "managed",
    "type": "aws_launch_configuration",
    "name": "my_web_config",
    "provider_name": "aws",
    "change": {
      "actions": [
        "create"
      ],
      "before": null,
      "after": {
        "associate_public_ip_address": false,
        "enable_monitoring": true,
        "ephemeral_block_device": [],
        "iam_instance_profile": null,
        "image_id": "ami-09b4b74c",
        "instance_type": "t2.micro",
        "name": "my_web_config",
        "name_prefix": null,
        "placement_tenancy": null,
        "security_groups": null,
        "spot_price": null,
        "user_data": null,
        "user_data_base64": null,
        "vpc_classic_link_id": null,
        "vpc_classic_link_security_groups": null
      },
      "after_unknown": {
        "ebs_block_device": true,
        "ebs_optimized": true,
        "ephemeral_block_device": [],
        "id": true,
        "key_name": true,
        "root_block_device": true
      }
    }
  }
}

Step 3 : Write the OPA policy to check the plan

Modified the existing policy file to match as per the updated json format. I tested my update policy file here (https://play.openpolicyagent.org/)

terraform.rego

package terraform.analysis

import input as tfplan

########################
# Parameters for Policy
########################

# acceptable score for automated authorization
blast_radius = 10

# weights assigned for each operation on each resource-type
weights = {
    "aws_autoscaling_group": {"delete": 100, "create": 10, "modify": 1},
    "aws_instance": {"delete": 10, "create": 1, "modify": 1}
}

# Consider exactly these resource types in calculations
resource_types = {"aws_autoscaling_group", "aws_instance", "aws_iam", "aws_launch_configuration"}

#########
# Policy
#########

# Authorization holds if score for the plan is acceptable and no changes are made to IAM
default authz = false
authz {
    score < blast_radius
    not touches_iam
}

# Compute the score for a Terraform plan as the weighted sum of deletions, creations, modifications
score = s {
    all := [ x |
            crud := weights[resource_type];
            del := crud["delete"] * num_deletes[resource_type];
            new := crud["create"] * num_creates[resource_type];
            mod := crud["modify"] * num_modifies[resource_type];
            x := del + new + mod
    ]
    s := sum(all)
}

# Whether there is any change to IAM
touches_iam {
    all := instance_names["aws_iam"]
    count(all) > 0
}

####################
# Terraform Library
####################

# list of all resources of a given type
instance_names[resource_type] = all {
    resource_types[resource_type]
    all := [name |
        tfplan[name] = _
        startswith(name, resource_type)
    ]
}

# number of deletions of resources of a given type
num_deletes[resource_type] = num {
    resource_types[resource_type]
    all := instance_names[resource_type]
    deletions := [name | name := all[_]; obj := tfplan[name]; obj["change"]["actions"][_] == "delete"]
    num := count(deletions)
}

# number of creations of resources of a given type
num_creates[resource_type] = num {
    resource_types[resource_type]
    all := instance_names[resource_type]
    creates := [name | all[_] = name; obj := tfplan[name]; obj["change"]["actions"][_] == "create"]
    num := count(creates)
}

# number of modifications to resources of a given type
num_modifies[resource_type] = num {
    resource_types[resource_type]
    all := instance_names[resource_type]
    modifies := [name | name := all[_]; obj := tfplan[name]; obj["change"]["actions"][_] == "update"]
    num := count(modifies)
}

Step 4 : Evaluate the OPA policy on the Terraform plan

Opa Policy evaluation :

opa eval --data terraform.rego --input tfplan.json "data.terraform.analysis.authz"

Output :

{
  "result": [
    {
      "expressions": [
        {
          "value": false,
          "text": "data.terraform.analysis.authz",
          "location": {
            "row": 1,
            "col": 1
          }
        }
      ]
    }
  ]
}

Opa Policy evaluation :

opa eval --data terraform.rego --input tfplan.json "data.terraform.analysis.score"

Output :

{
  "result": [
    {
      "expressions": [
        {
          "value": 11,
          "text": "data.terraform.analysis.score",
          "location": {
            "row": 1,
            "col": 1
          }
        }
      ]
    }
  ]
}

About

Open Policy Agent with Terraform version 0.12.5


Languages

Language:HCL 100.0%