Extract change of sensitive resource in Terraform

In terraform 0.14+, it won't be possible to see the changes in sensitives types of arguments while using the plan and apply commands. What you can do is export the result of the plan in a json format:

$ terraform plan -out=tfplan
$ terraform show -json tfplan > tfplan.json
$ jq '.' tfplan.json > tfplan-hr.json

Then use jq to extract the sensitive values you are looking for. In this exemple we will take a kubernetes_secret ressource:

$ jq '.resource_changes[] | select(.address == "module.adress.kubernetes_secret.my-secret") | .change["before"] | {data}' tfplan-hr.json

This command will allow you to recover the state of the data argument before the wanted change by targeting the before key located in the ressource_changes bloc of the appropriate ressource. It becomes then easy to do the same for the after key, which correspond to the result of the change wanted:

$ jq '.resource_changes[] | select(.address == "module.adress.kubernetes_secret.my-secret") | .change["after"] | {data}' tfplan-hr.json

Both commands should look something similar (depending on your secret data):

{
  "data": {
    "my-super-secret.json": "bar"
  }
}

Now let's make a script that will check the differences only for the kubernetes_secrets ressource type and return them. The final result will be at the end of the post, right now i will just explain how i did things.

First we will need a to find all the secrets that will be changed during this terraform plan run, for this we will still use jq as follows (we will also have to use sed to remove all quotes generated by jq:

$ jq '.resource_changes[] | select(.address | contains("kubernetes_secret")) | .address' tfplan-hr.json | sed 's/\\"//g'

The result should be as follow for as many secret you will be modifying:

module.module_one.kubernetes_secret.secret_one
module.module_two.kubernetes_secret.secret_two
kubernetes_secret.another_secret
...

Once this is done, we will use this command as the base of a for loop:

#!/bin/bash 

terraform init
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
# nice to have in case of debug
jq '.' tfplan.json > tfplan-hr.json 

for resource in $(jq '.resource_changes[] | select(.address | contains("kubernetes_secret")) | .address' tfplan-hr.json | sed 's/\\"//g'); do
done

In this loop, we will cycle through every secrets present in the tfplan-hr.json file that we created before using the resource variable. It is now time to use the previous commands that will show the before and after keys of the selected ressource.

We will have to modify the jq command in order to select the address of the ressource we want to target by using the --arg flag. This will allow us a variable inside the jq expression we used to look for the kubernetes_secret resources by specifying a pair of key/values that will be used inside this expression. The result will be stored in two variables BEFORE and AFTER :

#!/bin/bash 

terraform init
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
# nice to have in case of debug
jq '.' tfplan.json > tfplan-hr.json 

for resource in $(jq '.resource_changes[] | select(.address | contains("kubernetes_secret")) | .address' tfplan-hr.json | sed 's/\\"//g'); do
  BEFORE=$(jq --arg secret_address $resource '.resource_changes[] | select(.address == $secret_address) | .change["before"] | {data}' tfplan-hr.json)
  AFTER=$(jq --arg secret_address $resource '.resource_changes[] | select(.address == $secret_address) | .change["after"] | {data}' tfplan-hr.json)
done

This will then allow us to add an conditional if bloc to output if there are changes or not. For the condition, we will compare the content of both variables in order to start our bloc, then if the content is the same, output a string to let the user know, otherwise let the user know the difference between the two data blocs:

#!/bin/bash 

terraform init
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
# nice to have in case of debug
jq '.' tfplan.json > tfplan-hr.json 

for resource in $(jq '.resource_changes[] | select(.address | contains("kubernetes_secret")) | .address' tfplan-hr.json | sed 's/\\"//g'); do
  BEFORE=$(jq --arg secret_address $resource '.resource_changes[] | select(.address == $secret_address) | .change["before"] | {data}' tfplan-hr.json)
  AFTER=$(jq --arg secret_address $resource '.resource_changes[] | select(.address == $secret_address) | .change["after"] | {data}' tfplan-hr.json)
if [ "$BEFORE" == "$AFTER" ]; then
    echo "No changes on Data argument for resource $resource"
  else
    echo "####################################################################"
    echo "     Changes detected for secret data in ressource "
    echo "     $resource"
    echo "####################################################################"
    diff <(echo "$BEFORE") <(echo "$AFTER")
  fi
done

This should send you the following output:

No changes on Data argument for resource module.module_one.kubernetes_secret.secret_one
No changes on Data argument for resource module.module_two.kubernetes_secret.secret_two
No changes on Data argument for resource kubernetes_secret.another_secret
####################################################################
     Changes detected for secret data in ressource 
     kubernetes_secret.another_secret
####################################################################
3c3
<     "secret.json": "foo"
---
>     "secret.json": "bar"

We will now wrap things up by removing the files we generated in the script:

#!/bin/bash 

terraform init
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
# nice to have in case of debug
jq '.' tfplan.json > tfplan-hr.json 

for resource in $(jq '.resource_changes[] | select(.address | contains("kubernetes_secret")) | .address' tfplan-hr.json | sed 's/\\"//g'); do
  BEFORE=$(jq --arg secret_address $resource '.resource_changes[] | select(.address == $secret_address) | .change["before"] | {data}' tfplan-hr.json)
  AFTER=$(jq --arg secret_address $resource '.resource_changes[] | select(.address == $secret_address) | .change["after"] | {data}' tfplan-hr.json)
if [ "$BEFORE" == "$AFTER" ]; then
    echo "No changes on Data argument for resource $resource"
  else
    echo "####################################################################"
    echo "     Changes detected for secret data in ressource "
    echo "     $resource"
    echo "####################################################################"
    diff <(echo "$BEFORE") <(echo "$AFTER")
  fi
done

rm tfplan
rm tfplan.json
rm tfplan-hr.json

You should be good to go 🙂