Consider this as a throw away PGP approach for Terraform. It is meant to be used for CI/CD pipeline while creating, encrypting and outputting AWS IAM user login profile password in terminal. You are going to decrypt outputted password in your local environment to get the actual password for console login. That's the whole aim here. The reason why this should be considered "throw away" is that, a passphrase is not used. Hence reason you should change decrypted password as soon as you obtain it.


We are going to use a Dockerised solution. I personally don't like installing one-off third party tools on my device, signing up to certain online accounts so on to achieve same outcome. This includes Keybase, gpg command etc. Not saying they are bad, but I find it unnecessary.


You could even create a GitHub repository for this and allow all the engineers to use it when they are being onboarded. It is up to you. Here is the simple walkthrough of this example.


  1. Create public, private and base64 encoded PGP keys in your local environment.

  2. Copy base64 encoded PGP key into Terraform manifests where it is needed to encrypt certain output. e.g. aws_iam_user_login_profile.pgp_key

  3. Decrypt encrypted terraform apply output.

  4. Login to AWS profile and change the password.

Setup


Create .env out of .env.dist file and update marked variables using your own data. Run $ make build command to build Docker image.


Create public, private and base64 PGP keys


Run $ make keys command which does everything. You won't do anything with public and private keys directly but the PGP key is the one that you will use in Terraform resources where pgp_key field is required. Having said that, you can still use public and private keys for any other work using optional steps below.


Encrypt something (optional)


This is not specific to this post so consider it as an informative step.



Decrypt something (optional)


This is not specific to this post so consider it as an informative step.



Decrypt encrypted output from Terraform


This is explicitly related to our post.



Files


We are not focusing on the the most beautiful solution here. Instead we are being pragmatic. If you want, you can combine all bash scripts into one and running parameterised command. You can do all sorts of thing to improve it.


├── .env.dist
├── .gitignore
├── Dockerfile
├── Makefile
├── decrypt.sh
├── decrypt_base64.sh
├── encrypt.sh
├── keys.sh
├── pgp.cfg
└── tmp
└── .gitkeep

.env.dist


PGP_DIR="/root/.gnupg"
PGP_CFG="/pgp.cfg"
PUB_KEY="/tmp/public.key"
PRV_KEY="/tmp/private.key"
B64_KEY="/tmp/base64.key"
PLAIN_INPUT="/tmp/plain_input.txt"
ENCRYPTED_INPUT="/tmp/encrypted_input.txt"
DECRYPTED_INPUT="/tmp/decrypted_input.txt"

# Updated with your own data
NAME="Your name"
EMAIL="your email"
COMMENT="Key purpose"

.gitignore


/tmp/*
!/tmp/.gitignore
.env

Dockerfile


FROM alpine:3.17.0

COPY .env .env
COPY pgp.cfg pgp.cfg
COPY keys.sh keys.sh
COPY encrypt.sh encrypt.sh
COPY decrypt.sh decrypt.sh
COPY decrypt_base64.sh decrypt_base64.sh

RUN apk update && \
apk fetch gnupg && \
apk add gnupg && \
chmod +x keys.sh && \
chmod +x encrypt.sh && \
chmod +x decrypt.sh && \
chmod +x decrypt_base64.sh

Makefile


.PHONY: help
help: ## Display available commands
@awk 'BEGIN {FS = ":.*?## "} /^[0-9a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

.PHONY: build
build: ## Build docker image
@DOCKER_BUILDKIT=0 docker build --no-cache --tag inanzzz/pgp:latest .

.PHONY: keys
keys: ## Generate public, private and base64 encoded public key.
docker run --rm --env ENV_FILE=".env" --volume `pwd`/tmp:/tmp:rw --name inanzzz-pgp inanzzz/pgp:latest sh -c ./keys.sh

.PHONY: encrypt
encrypt: ## Encrypt plain text.
docker run --rm --env ENV_FILE=".env" --volume `pwd`/tmp:/tmp:rw --name inanzzz-pgp inanzzz/pgp:latest sh -c ./encrypt.sh

.PHONY: decrypt
decrypt: ## Decrypt encrypt text
docker run --rm --env ENV_FILE=".env" --volume `pwd`/tmp:/tmp:rw --name inanzzz-pgp inanzzz/pgp:latest sh -c ./decrypt.sh

.PHONY: decrypt-base64
decrypt-base64: ## Decrypt base64 encrypted text
docker run --rm --env ENV_FILE=".env" --volume `pwd`/tmp:/tmp:rw --name inanzzz-pgp inanzzz/pgp:latest sh -c ./decrypt_base64.sh

decrypt.sh


#!/bin/sh

CYAN="\033[0;36m"
CLEAR="\033[0m"

printf "${CYAN}> DECRYPT INPUT -----------------------------------------------------------------------------${CLEAR}\n"

printf "${CYAN}Export all variables from dot env file into environment${CLEAR}\n"
set -o allexport
source ${ENV_FILE}
set +o allexport
echo "done"

printf "${CYAN}Create pgp directory and get into it${CLEAR}\n"
gpg2 --list-keys
cd ${PGP_DIR}
echo "done"

printf "${CYAN}Restore public and private key${CLEAR}\n"
gpg2 --import-options import-restore --import ${PUB_KEY}
gpg2 --import-options import-restore --import ${PRV_KEY}
echo "done"

printf "${CYAN}Decrypt input${CLEAR}\n"
gpg2 --decrypt ${ENCRYPTED_INPUT} > ${DECRYPTED_INPUT}
echo "done"

printf "${CYAN}> FINISHED ----------------------------------------------------------------------------------${CLEAR}\n"

decrypt_base64.sh


#!/bin/sh

CYAN="\033[0;36m"
CLEAR="\033[0m"

printf "${CYAN}> DECRYPT INPUT -----------------------------------------------------------------------------${CLEAR}\n"

printf "${CYAN}Export all variables from dot env file into environment${CLEAR}\n"
set -o allexport
source ${ENV_FILE}
set +o allexport
echo "done"

printf "${CYAN}Create pgp directory and get into it${CLEAR}\n"
gpg2 --list-keys
cd ${PGP_DIR}
echo "done"

printf "${CYAN}Restore public and private key${CLEAR}\n"
gpg2 --import-options import-restore --import ${PUB_KEY}
gpg2 --import-options import-restore --import ${PRV_KEY}
echo "done"

printf "${CYAN}Decrypt input${CLEAR}\n"
cat ${ENCRYPTED_INPUT} | base64 -d | gpg2 --decrypt > ${DECRYPTED_INPUT}
echo "done"

printf "${CYAN}> FINISHED ----------------------------------------------------------------------------------${CLEAR}\n"

encrypt.sh


#!/bin/sh

CYAN="\033[0;36m"
CLEAR="\033[0m"

printf "${CYAN}> ENCRYPT INPUT -----------------------------------------------------------------------------${CLEAR}\n"

printf "${CYAN}Export all variables from dot env file into environment${CLEAR}\n"
set -o allexport
source ${ENV_FILE}
set +o allexport
echo "done"

printf "${CYAN}Create pgp directory and get into it${CLEAR}\n"
gpg2 --list-keys
cd ${PGP_DIR}
echo "done"

printf "${CYAN}Restore public and private key${CLEAR}\n"
gpg2 --import-options import-restore --import ${PUB_KEY}
gpg2 --import-options import-restore --import ${PRV_KEY}
echo "done"

printf "${CYAN}Auto trust and verify keys${CLEAR}\n"
echo -e "5\ny\n" | gpg2 --no-tty --command-fd 0 --expert --edit-key ${EMAIL} trust
echo "done"

printf "${CYAN}Encrypt input${CLEAR}\n"
gpg2 --batch --yes --output ${ENCRYPTED_INPUT} --encrypt --sign --armor -r ${EMAIL} ${PLAIN_INPUT}
echo "done"

printf "${CYAN}> FINISHED ----------------------------------------------------------------------------------${CLEAR}\n"

keys.sh


#!/bin/sh

CYAN="\033[0;36m"
CLEAR="\033[0m"

printf "${CYAN}> GENERATE KEYS -----------------------------------------------------------------------------${CLEAR}\n"

printf "${CYAN}Export all variables from dot env file into environment${CLEAR}\n"
set -o allexport
source ${ENV_FILE}
set +o allexport
echo "done"

printf "${CYAN}Prepare configuration file${CLEAR}\n"
sed -i -e 's/${NAME}/'"${NAME}"'/g' -e 's/${COMMENT}/'"${COMMENT}"'/g' -e 's/${EMAIL}/'"${EMAIL}"'/g' ${PGP_CFG}
echo "done"

printf "${CYAN}Create pgp directory and get into it${CLEAR}\n"
gpg2 --list-keys
cd ${PGP_DIR}
echo "done"

printf "${CYAN}Generate keys${CLEAR}\n"
gpg2 --verbose --batch --gen-key ${PGP_CFG}
echo "done"

printf "${CYAN}Backup public and private keys${CLEAR}\n"
gpg2 --yes --output ${PUB_KEY} --armor --export-options export-backup --export ${EMAIL}
gpg2 --yes --output ${PRV_KEY} --armor --export-options export-backup --export-secret-keys ${EMAIL}
echo "done"

printf "${CYAN}Export base64 encoded public key${CLEAR}\n"
gpg2 --export ${EMAIL} | base64 -w 0 > ${B64_KEY}
echo "done"

printf "${CYAN}> FINISHED ----------------------------------------------------------------------------------${CLEAR}\n"

pgp.cfg


Key-Type: RSA
Key-Length: 2048
Subkey-Type: RSA
Subkey-Length: 2048
Name-Real: ${NAME}
Name-Email: ${EMAIL}
Name-Comment: ${COMMENT}
Expire-Date: 0
%no-protection