From 35d41ca8db4e902ce4dfd189c3f9a9f1bcce7c29 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Thu, 25 Nov 2021 07:34:46 -0500 Subject: [PATCH] feat: use age instead of gpg for sops (#153) * feat: use age instead of gpg Signed-off-by: Devin Buhl * fix: update comment in config env Signed-off-by: Devin Buhl * fix: set some debug output in configure.sh Signed-off-by: Devin Buhl * fix: use /Users/devin instead of full path Signed-off-by: Devin Buhl * fix: check if BOOTSTRAP_AGE_PUBLIC_KEY starts with age Signed-off-by: Devin Buhl --- .config.sample.env | 9 ++-- .envrc | 3 +- .gitignore | 1 + README.md | 104 +++++++++++++----------------------- cluster/base/apps.yaml | 2 +- cluster/base/core.yaml | 2 +- configure.sh | 22 ++++---- tmpl/.sops.yaml | 9 ++-- tmpl/cluster/gotk-sync.yaml | 2 +- 9 files changed, 62 insertions(+), 92 deletions(-) diff --git a/.config.sample.env b/.config.sample.env index 6c855c1d..062a0ef8 100644 --- a/.config.sample.env +++ b/.config.sample.env @@ -19,12 +19,9 @@ export BOOTSTRAP_METALLB_LB_RANGE="" # The load balancer IP for traefik, choose from one of the available IPs above # e.g. 192.168.1.220 export BOOTSTRAP_METALLB_TRAEFIK_ADDR="" -# Your personal pgp key -# e.g. 772154FFF783DE317KLCA0EC77149AC618D75581 -export BOOTSTRAP_PERSONAL_KEY_FP="" -# The flux pgp key -# e.g. AB675CE4CC64251G3S9AE1DAA88ARRTY2C009E2D -export BOOTSTRAP_FLUX_KEY_FP="" +# Age Public Key - string should start with age +# e.g. age15uzrw396e67z9wdzsxzdk7ka0g2gr3l460e0slaea563zll3hdfqwqxdta +export BOOTSTRAP_AGE_PUBLIC_KEY="" # # Ansible related variables diff --git a/.envrc b/.envrc index 0b7a8512..e0a5845d 100644 --- a/.envrc +++ b/.envrc @@ -1,4 +1,5 @@ #shellcheck disable=SC2148,SC2155 export KUBECONFIG=$(expand_path ./provision/kubeconfig) export ANSIBLE_CONFIG=$(expand_path ./ansible.cfg) -export GPG_TTY=$(tty) +export ANSIBLE_HOST_KEY_CHECKING="False" +export SOPS_AGE_KEY_FILE=$(expand_path ~/.config/sops/age/keys.txt) diff --git a/.gitignore b/.gitignore index a197cafc..98807c01 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ kubeconfig # vscode-sops .decrypted~*.yaml .config.env +*.agekey # Ansible xanmanning.k3s* # Terraform diff --git a/README.md b/README.md index 5a25bf24..a353c470 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Highly opinionated template for deploying a single [k3s](https://k3s.io) cluster with [Ansible](https://www.ansible.com) and [Terraform](https://www.terraform.io) backed by [Flux](https://toolkit.fluxcd.io/) and [SOPS](https://toolkit.fluxcd.io/guides/mozilla-sops/). -The purpose here is to showcase how you can deploy an entire Kubernetes cluster and show it off to the world using the [GitOps](https://www.weave.works/blog/what-is-gitops-really) tool [Flux](https://toolkit.fluxcd.io/). When completed, your Git repository will be driving the state of your Kubernetes cluster. In addition with the help of the [Ansible](https://github.com/ansible-collections/community.sops), [Terraform](https://github.com/carlpett/terraform-provider-sops) and [Flux](https://toolkit.fluxcd.io/guides/mozilla-sops/) SOPS integrations you'll be able to commit GPG encrypted secrets to your public repo. +The purpose here is to showcase how you can deploy an entire Kubernetes cluster and show it off to the world using the [GitOps](https://www.weave.works/blog/what-is-gitops-really) tool [Flux](https://toolkit.fluxcd.io/). When completed, your Git repository will be driving the state of your Kubernetes cluster. In addition with the help of the [Ansible](https://github.com/ansible-collections/community.sops), [Terraform](https://github.com/carlpett/terraform-provider-sops) and [Flux](https://toolkit.fluxcd.io/guides/mozilla-sops/) SOPS integrations you'll be able to commit Age encrypted secrets to your public repo. ## Overview @@ -50,28 +50,27 @@ For provisioning the following tools will be used: #### Required -| Tool | Purpose | -|--------------------------------------------------------------------|---------------------------------------------------------------------| -| [ansible](https://www.ansible.com) | Preparing Ubuntu for Kubernetes and installing k3s | -| [direnv](https://github.com/direnv/direnv) | Exports env vars based on present working directory | -| [flux](https://toolkit.fluxcd.io/) | Operator that manages your k8s cluster based on your Git repository | -| [gnupg](https://gnupg.org/) | Encrypts and signs your data | -| [go-task](https://github.com/go-task/task) | A task runner / simpler Make alternative written in Go | -| [ipcalc](http://jodies.de/ipcalc) | Used to verify settings in the configure script | -| [jq](https://stedolan.github.io/jq/) | Used to verify settings in the configure script | -| [kubectl](https://kubernetes.io/docs/tasks/tools/) | Allows you to run commands against Kubernetes clusters | -| [pinentry](https://gnupg.org/related_software/pinentry/index.html) | Allows GnuPG to read passphrases and PIN numbers | -| [sops](https://github.com/mozilla/sops) | Encrypts k8s secrets with GnuPG | -| [terraform](https://www.terraform.io) | Prepare a Cloudflare domain to be used with the cluster | +| Tool | Purpose | +|----------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------| +| [ansible](https://www.ansible.com) | Preparing Ubuntu for Kubernetes and installing k3s | +| [direnv](https://github.com/direnv/direnv) | Exports env vars based on present working directory | +| [flux](https://toolkit.fluxcd.io/) | Operator that manages your k8s cluster based on your Git repository | +| [age](https://github.com/FiloSottile/age) | A simple, modern and secure encryption tool (and Go library) with small explicit keys, no config options, and UNIX-style composability. | +| [go-task](https://github.com/go-task/task) | A task runner / simpler Make alternative written in Go | +| [ipcalc](http://jodies.de/ipcalc) | Used to verify settings in the configure script | +| [jq](https://stedolan.github.io/jq/) | Used to verify settings in the configure script | +| [kubectl](https://kubernetes.io/docs/tasks/tools/) | Allows you to run commands against Kubernetes clusters | +| [sops](https://github.com/mozilla/sops) | Encrypts k8s secrets with Age | +| [terraform](https://www.terraform.io) | Prepare a Cloudflare domain to be used with the cluster | #### Optional -| Tool | Purpose | -|--------------------------------------------------------------------|---------------------------------------------------------------------| -| [helm](https://helm.sh/) | Manage Kubernetes applications | -| [kustomize](https://kustomize.io/) | Template-free way to customize application configuration | -| [pre-commit](https://github.com/pre-commit/pre-commit) | Runs checks pre `git commit` | -| [prettier](https://github.com/prettier/prettier) | Prettier is an opinionated code formatter. | +| Tool | Purpose | +|--------------------------------------------------------|----------------------------------------------------------| +| [helm](https://helm.sh/) | Manage Kubernetes applications | +| [kustomize](https://kustomize.io/) | Template-free way to customize application configuration | +| [pre-commit](https://github.com/pre-commit/pre-commit) | Runs checks pre `git commit` | +| [prettier](https://github.com/prettier/prettier) | Prettier is an opinionated code formatter. | ### :warning:  pre-commit @@ -118,56 +117,31 @@ Clone the repo to you local workstation and `cd` into it. :round_pushpin: **All of the below commands** are run on your **local** workstation, **not** on any of your cluster nodes. -### :closed_lock_with_key:  Setting up GnuPG keys +### :closed_lock_with_key:  Setting up Age -:round_pushpin: Here we will create a personal and a Flux GPG key. Using SOPS with GnuPG allows us to encrypt and decrypt secrets. +:round_pushpin: Here we will create a Age Private and Public key. Using SOPS with Age allows us to encrypt and decrypt secrets. -1. Create a Personal GPG Key, password protected, and export the fingerprint. It's **strongly encouraged** to back up this key somewhere safe so you don't lose it. +1. Create a Age Private / Public Key ```sh -export GPG_TTY=$(tty) -export PERSONAL_KEY_NAME="First name Last name (location) " - -gpg --batch --full-generate-key < -# sub rsa4096 2021-03-11 [E] +age-keygen -o age.agekey ``` -2. Create a Flux GPG Key and export the fingerprint +2. Set up the directory for the Age key and move the Age file to it ```sh -export GPG_TTY=$(tty) -export FLUX_KEY_NAME="Cluster name (Flux) " - -gpg --batch --full-generate-key < -# sub rsa4096 2021-03-11 [E] +mkdir -p ~/.config/sops/age +mv age.agekey ~/.config/sops/age/keys.txt ``` -3. You will need the Fingerprints in the configuration section below. For example, in the above steps you will need `772154FFF783DE317KLCA0EC77149AC618D75581` and `AB675CE4CC64251G3S9AE1DAA88ARRTY2C009E2D` +3. Export the `SOPS_AGE_KEY_FILE` variable in your `bashrc`, `zshrc` or `config.fish` and source it, e.g. + +```sh +export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt +source ~/.bashrc +``` + +4. Fill out the Age public key in the `.config.env` under `BOOTSTRAP_AGE_PUBLIC_KEY`, **note** the public key should start with `age`... ### :cloud:  Global Cloudflare API Key @@ -248,14 +222,12 @@ flux --kubeconfig=./provision/kubeconfig check --pre kubectl --kubeconfig=./provision/kubeconfig create namespace flux-system --dry-run=client -o yaml | kubectl --kubeconfig=./provision/kubeconfig apply -f - ``` -3. Add the Flux GPG key in-order for Flux to decrypt SOPS secrets +3. Add the Age key in-order for Flux to decrypt SOPS secrets ```sh -source .config.env -gpg --export-secret-keys --armor "${BOOTSTRAP_FLUX_KEY_FP}" | -kubectl --kubeconfig=./provision/kubeconfig create secret generic sops-gpg \ - --namespace=flux-system \ - --from-file=sops.asc=/dev/stdin +cat ~/.config/sops/age/keys.txt | + kubectl -n default create secret generic sops-age \ + --from-file=age.agekey=/dev/stdin ``` :round_pushpin: Variables defined in `./cluster/base/cluster-secrets.sops.yaml` and `./cluster/base/cluster-settings.sops.yaml` will be usable anywhere in your YAML manifests under `./cluster` diff --git a/cluster/base/apps.yaml b/cluster/base/apps.yaml index 3006180b..cd9fef18 100644 --- a/cluster/base/apps.yaml +++ b/cluster/base/apps.yaml @@ -16,7 +16,7 @@ spec: decryption: provider: sops secretRef: - name: sops-gpg + name: sops-age postBuild: substitute: {} substituteFrom: diff --git a/cluster/base/core.yaml b/cluster/base/core.yaml index 09aecd16..41938faa 100644 --- a/cluster/base/core.yaml +++ b/cluster/base/core.yaml @@ -16,7 +16,7 @@ spec: decryption: provider: sops secretRef: - name: sops-gpg + name: sops-age postBuild: substitute: {} substituteFrom: diff --git a/configure.sh b/configure.sh index 2e92a7f7..ed9858cf 100755 --- a/configure.sh +++ b/configure.sh @@ -31,7 +31,7 @@ main() { verify_ansible_hosts verify_metallb verify_kubevip - verify_gpg + verify_age verify_git_repository verify_cloudflare success @@ -117,22 +117,22 @@ _has_valid_ip() { fi } -verify_gpg() { - _has_envar "BOOTSTRAP_PERSONAL_KEY_FP" - _has_envar "BOOTSTRAP_FLUX_KEY_FP" +verify_age() { + _has_envar "BOOTSTRAP_AGE_PUBLIC_KEY" + _has_envar "SOPS_AGE_KEY_FILE" - if ! gpg --list-keys "${BOOTSTRAP_PERSONAL_KEY_FP}" >/dev/null 2>&1; then - _log "ERROR" "Invalid Personal GPG FP ${BOOTSTRAP_PERSONAL_KEY_FP}" + if [[ ! "$BOOTSTRAP_AGE_PUBLIC_KEY" =~ ^age.* ]]; then + _log "ERROR" "BOOTSTRAP_AGE_PUBLIC_KEY does not start with age" exit 1 else - _log "INFO" "Found Personal GPG Fingerprint '${BOOTSTRAP_PERSONAL_KEY_FP}'" + _log "INFO" "Age public key is in the correct format" fi - if ! gpg --list-keys "${BOOTSTRAP_FLUX_KEY_FP}" >/dev/null 2>&1; then - _log "ERROR" "Invalid Flux GPG FP '${BOOTSTRAP_FLUX_KEY_FP}'" + if [[ ! -f ~/.config/sops/age/keys.txt ]]; then + _log "ERROR" "Unable to find Age file keys.txt in ~/.config/sops/age" exit 1 else - _log "INFO" "Found Flux GPG Fingerprint '${BOOTSTRAP_FLUX_KEY_FP}'" + _log "INFO" "Found Age public key '${BOOTSTRAP_AGE_PUBLIC_KEY}'" fi } @@ -141,7 +141,7 @@ verify_binaries() { _has_binary "envsubst" _has_binary "flux" _has_binary "git" - _has_binary "gpg" + _has_binary "age" _has_binary "helm" _has_binary "ipcalc" _has_binary "jq" diff --git a/tmpl/.sops.yaml b/tmpl/.sops.yaml index d7f4d9bb..d956a3f9 100644 --- a/tmpl/.sops.yaml +++ b/tmpl/.sops.yaml @@ -2,10 +2,9 @@ creation_rules: - path_regex: provision/.*\.sops\.ya?ml unencrypted_regex: "^(kind)$" - pgp: >- - ${BOOTSTRAP_PERSONAL_KEY_FP} + age: >- + ${BOOTSTRAP_AGE_PUBLIC_KEY} - path_regex: cluster/.*\.ya?ml encrypted_regex: "^(data|stringData)$" - pgp: >- - ${BOOTSTRAP_FLUX_KEY_FP}, - ${BOOTSTRAP_PERSONAL_KEY_FP} + age: >- + ${BOOTSTRAP_AGE_PUBLIC_KEY} diff --git a/tmpl/cluster/gotk-sync.yaml b/tmpl/cluster/gotk-sync.yaml index 9a693271..19f90679 100644 --- a/tmpl/cluster/gotk-sync.yaml +++ b/tmpl/cluster/gotk-sync.yaml @@ -25,4 +25,4 @@ spec: decryption: provider: sops secretRef: - name: sops-gpg + name: sops-age