I love Terraform. It makes managing complex infrastructure and keeping track of what changes are happening infinitely easier........except if you are changing AWS IAM policies.

UPDATE: Someone has made an excellent tool that takes care of this for you!
terraform-landscape

Currently, if you change a single field in policy, you are returned two unformatted, uncolored strings of json. Leaving it up to you to somehow figure out the difference.

For example:
policy_diff-1

Can you spot the difference? ....because I sure can't.

If I am feeling lazy, I will assume this diff contains only the change I want. However, I quickly noticed that policies are one of the most often changed resources by someone manually. When I would apply these updates without carefully reviewing them, random coworkers would show up at my desk with questions like: "Do you know why I just lost access to view our AWS Lambdas?". Doh!

So to be a more resonsible Terraformer, I started formatting and diffing the polices myself in vim. But after doing that a couple times, I knew I needed quicker way. So I whipped up a quick bash script to help with this problem. Heres how to use it.

First copy the policy diff from the Terraform plan, and save it in a file called policy.txt. It should look like this.

"{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"RDSAccess\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"rds:ListTagsForResource\",\n        \"rds:Describe*\",\n        \"ec2:DescribeVpcs\",\n        \"ec2:DescribeSecurityGroups\",\n        \"ec2:DescribeAvailabilityZones\",\n        \"ec2:DescribeAccountAttributes\"\n      ],\n      \"Resource\": \"*\"\n    },\n    {\n      \"Sid\": \"CloudwatchPutMetricDataAccess\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"xray:PutTraceSegments\",\n        \"xray:PutTelemetryRecords\",\n        \"cloudwatch:PutMetricData\"\n      ],\n      \"Resource\": \"*\"\n    },\n    {\n      \"Sid\": \"MonitoringAccess\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"sns:List*\",\n        \"sns:Get*\",\n        \"logs:TestMetricFilter\",\n        \"logs:GetLogEvents\",\n        \"logs:Get*\",\n        \"logs:FilterLogEvents\",\n        \"logs:DescribeLogStreams\",\n        \"logs:Describe*\",\n        \"cloudwatch:List*\",\n        \"cloudwatch:GetMetricStatistics\",\n        \"cloudwatch:Get*\",\n        \"cloudwatch:Describe*\",\n        \"autoscaling:Describe*\"\n      ],\n      \"Resource\": \"*\"\n    },\n    {\n      \"Sid\": \"EcrAccess\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"ecr:GetDownloadUrlForLayer\",\n        \"ecr:GetAuthorizationToken\",\n        \"ecr:BatchGetImage\",\n        \"ecr:BatchCheckLayerAvailability\"\n      ],\n      \"Resource\": \"*\"\n    },\n    {\n      \"Sid\": \"RekognitionAccess\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"rekognition:*\",\n      \"Resource\": \"*\"\n    },\n    {\n      \"Sid\": \"LambdaInvoke\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"lambda:InvokeFunction\",\n        \"lambda:GetFunction\"\n      ],\n      \"Resource\": \"*\"\n    }\n  ]\n}" => "{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"RDSAccess\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"rds:ListTagsForResource\",\n        \"rds:Describe*\",\n        \"ec2:DescribeSecurityGroups\",\n        \"ec2:DescribeAvailabilityZones\",\n        \"ec2:DescribeAccountAttributes\"\n      ],\n      \"Resource\": \"*\"\n    },\n    {\n      \"Sid\": \"CloudwatchPutMetricDataAccess\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"xray:PutTraceSegments\",\n        \"xray:PutTelemetryRecords\",\n        \"cloudwatch:PutMetricData\"\n      ],\n      \"Resource\": \"*\"\n    },\n    {\n      \"Sid\": \"MonitoringAccess\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"sns:List*\",\n        \"sns:Get*\",\n        \"logs:TestMetricFilter\",\n        \"logs:GetLogEvents\",\n        \"logs:Get*\",\n        \"logs:FilterLogEvents\",\n        \"logs:DescribeLogStreams\",\n        \"logs:Describe*\",\n        \"cloudwatch:List*\",\n        \"cloudwatch:GetMetricStatistics\",\n        \"cloudwatch:Get*\",\n        \"cloudwatch:Describe*\",\n        \"autoscaling:Describe*\"\n      ],\n      \"Resource\": \"*\"\n    },\n    {\n      \"Sid\": \"EcrAccess\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"ecr:GetDownloadUrlForLayer\",\n        \"ecr:GetAuthorizationToken\",\n        \"ecr:BatchGetImage\",\n        \"ecr:BatchCheckLayerAvailability\"\n      ],\n      \"Resource\": \"*\"\n    },\n    {\n      \"Sid\": \"RekognitionAccess\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"rekognition:*\",\n      \"Resource\": \"*\"\n    },\n    {\n      \"Sid\": \"LambdaInvoke\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"lambda:InvokeFunction\",\n        \"lambda:GetFunction\"\n      ],\n      \"Resource\": \"*\"\n    }\n  ]\n}"

Next install color-diff
Note: This assumes you are using OSX

brew install color-diff

Now you can use this little script to print out a nice colorized diff of what changed:

cat policy.txt | sed s/=\>/\\n/ | split -l 1;
cat xaa | sed 's/\s\+$//' | sed s/\"// | sed 's/\"$//' | sed 's/\\\n//g' | sed 's/\\\"/\"/g' | python -m json.tool > initial.json;
cat xab | sed 's/\s\+$//' | sed s/\"// | sed 's/\"$//' | sed 's/\\\n//g' | sed 's/\\\"/\"/g' | python -m json.tool > new.json;
diff initial.json new.json | colordiff

For the above example you will see:
cropped_diff

We now see that this terraform apply is going to remove ec2:DescribeVpcs from the policy. Much nicer! And now I can apply in peace.