Skip to content

Instantly share code, notes, and snippets.

@sboardwell
Last active March 31, 2024 13:49
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save sboardwell/bf5f5a17f6a7dde42a92ac46ee61d72f to your computer and use it in GitHub Desktop.
Save sboardwell/bf5f5a17f6a7dde42a92ac46ee61d72f to your computer and use it in GitHub Desktop.
Encryption by design - tech talk @ techpunk

Encryption by Design - Demo Gist

Encryption should used wherever possible (and plausible).

The demo shows us how we can use SOPS to tick most of the boxes.

  • checkout this gist
  • run ./setup.sh
  • follow the instructions
apiVersion: v1
kind: Secret
metadata:
name: dbmigrator
labels:
app: dbmigrator
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
release: "{{ .Release.Name }}"
heritage: "{{ .Release.Service }}"
type: Opaque
data:
db_host: {{ .Values.db_host | b64enc | quote }}
db_user: {{ .Values.db_user | b64enc | quote }}
db_pass: {{ .Values.db_pass | b64enc | quote }}
releases:
- name: dbmigrator
labels:
job: dbmigrator
chart: ./dbmigrator
values: []
# DB host, username and password encrypted with helm-secrets(mozilla/sops)
secrets:
- "./enc.{{ env "MY_ENV" }}.yaml"
terraform {
required_version = ">= 1.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
sops = {
source = "carlpett/sops"
version = "0.7.1"
}
}
}
provider "aws" {
region = "us-east-1"
}
provider "sops" {}
variable "kms_key_id" {
type = string
default = ""
}
data "sops_file" "demo" {
source_file = "../enc.01-simple.dev.yaml"
}
resource "aws_secretsmanager_secret" "secrets" {
for_each = var.kms_key_id != "" ? nonsensitive(data.sops_file.demo.data) : {}
name = "Secrets/as/Code/${each.key}"
description = "Secrets as Code using sops: ${each.key}"
recovery_window_in_days = 7
kms_key_id = var.kms_key_id
}
resource "aws_secretsmanager_secret_version" "secrets" {
for_each = var.kms_key_id != "" ? nonsensitive(data.sops_file.demo.data) : {}
secret_id = aws_secretsmanager_secret.secrets[each.key].id
secret_string = sensitive(data.sops_file.demo.data[each.key])
}
output "secret-hidden" {
# Access the password variable that is under db via the terraform map of data
value = data.sops_file.demo.data["apikey"]
sensitive = true
}
output "secret-unencrypted" {
# Access the password variable that is under db via the terraform map of data
value = nonsensitive(data.sops_file.demo.data["apikey"])
}
terraform {
required_version = ">= 1.0.0"
required_providers {
sops = {
source = "carlpett/sops"
version = "0.7.1"
}
}
}
provider "sops" {}
variable "kms_key_id" {
type = string
default = ""
}
data "sops_file" "demo" {
source_file = "../enc.01-simple.dev.yaml"
}
output "secret-hidden" {
# Access the password variable that is under db via the terraform map of data
value = data.sops_file.demo.data["apikey"]
sensitive = true
}
output "secret-unencrypted" {
# Access the password variable that is under db via the terraform map of data
value = nonsensitive(data.sops_file.demo.data["apikey"])
}
#!/usr/bin/env bash
set -euo pipefail
SCRIPT="${0##*/}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
EXAMPLES_DIR="${SCRIPT_DIR}/examples"
DEV_AGE_KEY="age-dev-key.txt"
PRD_AGE_KEY="age-prod-key.txt"
ALL_AGE_KEY="age-all-keys.txt"
SECRET_SIMPLE_HELM_DEV="\
db_host: mysecret_db_host_dev
db_user: mysecret_db_user_dev
db_pass: mysecret_db_pass_dev"
SECRET_SIMPLE_HELM_PROD="\
db_host: mysecret_db_host_prd
db_user: mysecret_db_user_prd
db_pass: mysecret_db_pass_prd"
SECRET_SIMPLE_DEV="\
password: mysecretpassword_DEV
apikey: mysecretapikey_DEV
this_is_unencrypted: this will be unencrypted_DEV"
SECRET_SIMPLE_PRD="\
password: mysecretpassword_PRD
apikey: mysecretapikey_PRD
this_is_unencrypted: this will be unencrypted_PRD"
SECRET_SIMPLE="\
password: mysecretpassword
apikey: mysecretapikey
this_is_unencrypted: this will be unencrypted"
SECRET_UNENCRYPTED="$SECRET_SIMPLE
this_is_plaintext: this will also be unencrypted"
SECRET_ENCRYPTED="$SECRET_SIMPLE
just_encryptme: this will be encrypted but nothing else"
## Set up example data
rm -rf "${EXAMPLES_DIR}/helm"
rm -f "${EXAMPLES_DIR}/terraform/*" "${EXAMPLES_DIR}/terraform-aws/*"
rm -f "${EXAMPLES_DIR}/"secrets* "${EXAMPLES_DIR}/"enc* "${EXAMPLES_DIR}/"*.tf "${EXAMPLES_DIR}/"*.txt "${EXAMPLES_DIR}/"ex*
mkdir -p "${EXAMPLES_DIR}/terraform"
mkdir -p "${EXAMPLES_DIR}/terraform-aws"
mkdir -p "${EXAMPLES_DIR}/helm"
cd "${EXAMPLES_DIR}"
echo "# >>> Create the identity keys..."
[ -f "${DEV_AGE_KEY}" ] || age-keygen -o "${DEV_AGE_KEY}"
[ -f "${PRD_AGE_KEY}" ] || age-keygen -o "${PRD_AGE_KEY}"
[ -f "${ALL_AGE_KEY}" ] || cat "${DEV_AGE_KEY}" "${PRD_AGE_KEY}" > "${ALL_AGE_KEY}"
# make them secure-ish (better still, add to a vault of sorts)
chmod 600 "${DEV_AGE_KEY}"
chmod 600 "${PRD_AGE_KEY}"
chmod 600 "${ALL_AGE_KEY}"
echo "# >>> Create an .env file"
cat <<EOF > .env
EXAMPLES_DIR="${EXAMPLES_DIR}"
DEV_AGE_KEY="${DEV_AGE_KEY}"
DEV_DEC_KEY=\$(cat "${DEV_AGE_KEY}")
DEV_ENC_KEY=$(age-keygen -y "${DEV_AGE_KEY}")
PRD_AGE_KEY="${PRD_AGE_KEY}"
PRD_DEC_KEY=\$(cat "${PRD_AGE_KEY}")
PRD_ENC_KEY=$(age-keygen -y "${PRD_AGE_KEY}")
ALL_AGE_KEY="${ALL_AGE_KEY}"
ALL_DEC_KEY=\$(cat "${ALL_AGE_KEY}")
ALL_ENC_KEY=$(age-keygen -y "${DEV_AGE_KEY}"),$(age-keygen -y "${PRD_AGE_KEY}")
MY_HELM_CHART=helm/dbmigrator
echoI() { echo "$(tput setaf 4)$(tput setab 7)\$@$(tput sgr 0)"; }
EOF
source .env
echo "# >>> Create an .sops.yaml config file"
cat <<EOF > .sops.yaml
creation_rules:
- path_regex: k8s\..*\.yaml$
encrypted_regex: ^(data|stringData)$
age: ${ALL_ENC_KEY}
- path_regex: \.dev\.yaml$
age: ${DEV_ENC_KEY}
- path_regex: \.prd\.yaml$
age: ${PRD_ENC_KEY}
- path_regex: \.common\.yaml$
age: ${ALL_ENC_KEY}
- path_regex: \.unencrypted\.yaml$
unencrypted_regex: (_unencrypted|_plaintext)$
age: ${ALL_ENC_KEY}
- path_regex: \.encrypted\.yaml$
encrypted_suffix: _encryptme
age: ${ALL_ENC_KEY}
- path_regex: \.nokey\.yaml$
EOF
echo "# >>> Create secret yamls..."
echo "$SECRET_SIMPLE_DEV" > secrets.01-simple.dev.yaml
echo "$SECRET_SIMPLE_PRD" > secrets.02-simple.prd.yaml
echo "$SECRET_SIMPLE" > secrets.03-simple.common.yaml
echo "$SECRET_SIMPLE" > secrets.04-simple.nokey.yaml
echo "$SECRET_UNENCRYPTED" > secrets.05-custom.unencrypted.yaml
echo "$SECRET_ENCRYPTED" > secrets.06-custom.encrypted.yaml
kubectl create secret generic my-secrets \
-n my-namespace \
--from-literal=username=admin \
--from-literal=password=mysecretpassword2022 \
-oyaml --dry-run=client > secrets.07-k8s.dev.yaml
echo "$SECRET_SIMPLE_HELM_DEV" > helm/secrets.dev.yaml
echo "$SECRET_SIMPLE_HELM_PROD" > helm/secrets.prd.yaml
ls -1 secrets*.yaml
echo "# >>> Add terraform example"
cp ../_terraform-main.tf terraform/main.tf
cp ../_terraform-main-aws.tf terraform-aws/main.tf
echo "# >>> Add helm secret example"
helm create $MY_HELM_CHART
cp ../_helm-example-secret.yaml $MY_HELM_CHART/templates/secrets.yaml
cp ../_helm-helmfile.yaml $MY_HELM_CHART/../helmfile.yaml
echo 'db_host: ""' >> $MY_HELM_CHART/values.yaml
echo 'db_user: ""' >> $MY_HELM_CHART/values.yaml
echo 'db_pass: ""' >> $MY_HELM_CHART/values.yaml
echo "# >>> Create example scripts..."
cat <<'EOF' > ex01-encryption-config.sh
#!/usr/bin/env bash
set -euo pipefail
cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
. .env
echoI "# >>> SOPS Config..."
yq .sops.yaml
for f in $(find . -type f -name "secrets\.*\.yaml"); do
echo
echoI "# >>> Processing ${f}..."
set -x
sops --verbose -e "${f}" > "${f//secrets/enc}"
set +x
echo "# >>> Before..."
yq '.' "${f}"
echo "# >>> After..."
yq 'del(.sops)' "${f//secrets/enc}"
echo "# >>> SOPS file ENCRYPTION config..."
grep -E "(sops:|*encrypted_regex:|*encrypted_suffix:)" "${f//secrets/enc}" | yq -
echo "# >>> SOPS file AGE config..."
yq '.sops.age' "${f//secrets/enc}"
done
EOF
cat <<'EOF' > ex02-encryption-overrides.sh
#!/usr/bin/env bash
set -euo pipefail
cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
. .env
echoI "# >>> Overriding - encrypting PROD yaml with DEV key..."
set -x
SOPS_AGE_RECIPIENTS="${DEV_ENC_KEY}" \
sops -e secrets.02-simple.prd.yaml > enc.overridden.simple.prd.yaml
set +x
echoI "# >>> SOPS Config..."
yq .sops.yaml
echoI "# >>> Encrypted prod file using DEV key..."
yq '.sops.age' enc.overridden.simple.prd.yaml
EOF
cat <<'EOF' > ex03-helm.sh
#!/usr/bin/env bash
set -euo pipefail
cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
. .env
MY_SECRET_DEV_FILE='helm/enc.dev.yaml'
echoI "# >>> Helm - see https://github.com/jkroepke/helm-secrets/wiki/Usage"
helm secrets --version &> /dev/null || { echo "Please install the helm secrets plugin"; exit 1; }
echo
echo "# >>> Helm - looking at the value..."
set -x
grep -r db_ ./$MY_HELM_CHART
set +x
echo
echoI "# >>> Helm template - without the secrets file..."
set -x
helm template ./$MY_HELM_CHART \
| grep -B 2 db_
set +x
echo
echoI "# >>> Helm template - attempting to use wrong key..."
set -x
helm template ./$MY_HELM_CHART \
-f "secrets+age-import://${PRD_AGE_KEY}?${MY_SECRET_DEV_FILE}" \
|| echo "ERROR: Command failed"
set +x
echo
echoI "# >>> Helm template - using correct key..."
set -x
helm template ./$MY_HELM_CHART \
-f "secrets+age-import://${DEV_AGE_KEY}?${MY_SECRET_DEV_FILE}" \
| grep -B 2 db_
set +x
EOF
cat <<'EOF' > ex04-helmfile.sh
#!/usr/bin/env bash
set -euo pipefail
cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
. .env
export SOPS_AGE_KEY_FILE=../age-all-keys.txt
echoI "# >>> helmfile - dev environment"
set -x
MY_ENV=dev helmfile --selector job=dbmigrator -f helm/helmfile.yaml template | grep db_
set +x
echoI "# >>> helmfile - prod environment"
set -x
MY_ENV=prd helmfile --selector job=dbmigrator -f helm/helmfile.yaml template | grep db_
set +x
EOF
cat <<'EOF' > ex05-terraform.sh
#!/usr/bin/env bash
set -euo pipefail
cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
. .env
echoI "# >>> Terraform... (SOPS_AGE_KEY_FILE=...)"
echo
echo "# >>> Terraform - init..."
set -x
terraform -chdir=terraform init
set +x
echo
echoI "# >>> Terraform - using dev key file..."
set -x
SOPS_AGE_KEY_FILE=../$DEV_AGE_KEY terraform -chdir=terraform refresh
set +x
echo
echoI "# >>> Terraform - using common key file..."
set -x
SOPS_AGE_KEY_FILE=../$ALL_AGE_KEY terraform -chdir=terraform refresh
set +x
echo
echoI "# >>> Terraform - using INCORRECT prod key..."
set -x
SOPS_AGE_KEY_FILE=../$PRD_AGE_KEY terraform -chdir=terraform refresh \
|| echo "ERROR: Command failed"
set +x
EOF
cat <<'EOF' > ex06-git.sh
#!/usr/bin/env bash
set -euo pipefail
cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
. .env
TEMP_DIR=$(mktemp -d)
cd $TEMP_DIR
echoI "# >>> Git setup..."
echo
set -x
git init
echo "my_key: 1234" > mysecrets.yaml
export SOPS_AGE_RECIPIENTS="${DEV_ENC_KEY}"
export SOPS_AGE_KEY_FILE="${EXAMPLES_DIR}/${DEV_AGE_KEY}"
sops -e -i mysecrets.yaml
git add mysecrets.yaml
git commit -m "Init secrets yaml" mysecrets.yaml
echo "my_key: 5678" > mysecrets.yaml
sops -e -i mysecrets.yaml
set +x
echo
echoI "# >>> Git WITHOUT sopsdiffer..."
echo
set -x
git --no-pager diff
set +x
echo
echoI "# >>> Git WITH sopsdiffer..."
echo
set -x
git config diff.sopsdiffer.textconv "sops -d"
cat .git/config
set +x
echo "*.yaml diff=sopsdiffer" > .gitattributes
echo
set -x
cat .gitattributes
set +x
echo
git --no-pager diff
set +x
cd -
EOF
# create instructions
cat <<'EOF' >> "${EXAMPLES_DIR}/instructions.txt"
#========================
# Example commands:
#========================
EOF
for f in $(ls ex*.sh); do
chmod +x $f
echo "./examples/${f}" >> "${EXAMPLES_DIR}/instructions.txt"
done
cat <<'EOF' >> "${EXAMPLES_DIR}/instructions.txt"
#========================
# Advanced commands:
#========================
# checkout
cd examples/
SOPS_AGE_KEY_FILE=age-dev-key.txt sops -d enc.01-simple.dev.yaml
export SOPS_AGE_KEY_FILE=age-dev-key.txt
# enter demo env (to set variables)
sops --config /dev/null -d ~/.secrets/demo.yaml | yq 'keys|.[]' -
sops exec-env ~/.secrets/demo.yaml zsh
# check
sops -r --add-kms $DEMO_AWS_KEY_ARN --add-pgp $DEMO_GPG_PUB enc.01-simple.dev.yaml
vim enc.01-simple.dev.yaml
# change in-place
sops -r -i --add-kms $DEMO_AWS_KEY_ARN --add-pgp $DEMO_GPG_PUB enc.01-simple.dev.yaml
vim enc.01-simple.dev.yaml
# running with remote keys
unset SOPS_AGE_KEY_FILE
sops -d enc.01-simple.dev.yaml
# terraform
cd terraform-aws
# look at the main.tf
vim main.tf
diff ../terraform/main.tf main.tf
# check before
aws secretsmanager list-secrets | grep "as/Code"
# set the following environment variables
export TF_VAR_kms_key_id="$DEMO_AWS_KEY_ID"
# create
terraform init
terraform plan -out tfplan
terraform apply -auto-approve tfplan
# check after
aws secretsmanager list-secrets | grep "as/Code"
# look, Ma! - no secrets!
terraform state show \
aws_secretsmanager_secret.secrets\[\"password\"\]
# destroy
terraform destroy -auto-approve
unset TF_VAR_kms_key_id
# Additional clean up
# - destroying only schedules keys for deletion
# - trying to create the same keys will fail because the key still exists
# - we need to delete them without recovery
sops -d ../enc.01-simple.dev.yaml 2> /dev/null | yq 'keys|.[]' | xargs -I{} echo aws secretsmanager delete-secret --secret-id "Secrets/as/Code/{}" --force-delete-without-recovery
sops -d ../enc.01-simple.dev.yaml 2> /dev/null | yq 'keys|.[]' | xargs -I{} aws secretsmanager delete-secret --secret-id "Secrets/as/Code/{}" --force-delete-without-recovery
EOF
cat "${EXAMPLES_DIR}/instructions.txt"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment