From a2d3f1e83047ab9cfe77014b4a2a08b35d5eeb67 Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Fri, 11 Aug 2023 23:12:22 -0500 Subject: [PATCH 01/39] Minimal Shell Operator example. --- hooks/hook.zsh | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100755 hooks/hook.zsh diff --git a/hooks/hook.zsh b/hooks/hook.zsh new file mode 100755 index 0000000..05559fc --- /dev/null +++ b/hooks/hook.zsh @@ -0,0 +1,34 @@ +#!/usr/bin/env zsh + +function quotedoc { + typeset heredoc spaces=65536 leading='^( +)([^[:space:]])' IFS='' dedented + typeset -a lines + while read -r line; do + lines+=("$line") + if [[ "$line" =~ $leading && "${#match[1]}" -lt "$spaces" ]]; then + spaces="${#match[1]}" + fi + done + read -r -d '' dedented < <(printf "%s\n" "${lines[@]}" | sed -E 's/^ {'$spaces'}//') + eval "$({ + print "cat < Date: Sat, 12 Aug 2023 04:20:51 +0000 Subject: [PATCH 02/39] Create a `Dockerfile`. --- Dockerfile | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..54d1c30 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM ghcr.io/flant/shell-operator:latest +RUN < Date: Sun, 13 Aug 2023 04:38:46 +0000 Subject: [PATCH 03/39] Quote `jq` path argument. Zsh was unhappy with this copypasta Bash. --- hooks/hook.zsh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/hook.zsh b/hooks/hook.zsh index 05559fc..2b165df 100755 --- a/hooks/hook.zsh +++ b/hooks/hook.zsh @@ -28,7 +28,7 @@ function { executeHookOnEvent: [ "Added" ] EOF else - config_map_name=$(jq -r .[0].object.metadata.name $BINDING_CONTEXT_PATH) + config_map_name=$(jq -r '.[0].object.metadata.name' $BINDING_CONTEXT_PATH) print -- "ConfigMap '${config_map_name}' added" fi } "$@" From 2b0ce9c947b4d8e55860b11eb7fc3aee3e13b965 Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Sun, 13 Aug 2023 00:03:13 -0500 Subject: [PATCH 04/39] Working Step Operator. We now have a working Step Operator from the Quick Start. --- config-map.yaml | 7 +++++++ step-renewer.yaml | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 config-map.yaml create mode 100644 step-renewer.yaml diff --git a/config-map.yaml b/config-map.yaml new file mode 100644 index 0000000..165bb99 --- /dev/null +++ b/config-map.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + namespace: step-renewer + name: example +data: + hello: world diff --git a/step-renewer.yaml b/step-renewer.yaml new file mode 100644 index 0000000..9bc3747 --- /dev/null +++ b/step-renewer.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: step-renewer +--- +apiVersion: v1 +kind: Pod +metadata: + namespace: step-renewer + name: step-renewer +spec: + containers: + - name: step-renewer + image: flatheadmill/step-renewer:latest + imagePullPolicy: Always + serviceAccountName: step-renewer From 841f84b056f14af95eac9f50a50c112e7b1133d1 Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Sun, 13 Aug 2023 01:15:25 -0500 Subject: [PATCH 05/39] Attempt to match by label. --- hooks/hook.zsh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hooks/hook.zsh b/hooks/hook.zsh index 2b165df..411b8ee 100755 --- a/hooks/hook.zsh +++ b/hooks/hook.zsh @@ -25,6 +25,9 @@ function { kubernetes: - apiVersion: v1 kind: ConfigMap + labelSelector: + matchLabels: + step-renewer.prettyrobots.com: enabled executeHookOnEvent: [ "Added" ] EOF else From 5332aeeee96dae25288add130c55aa8b5429a16f Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Sun, 13 Aug 2023 06:35:07 +0000 Subject: [PATCH 06/39] Let's see what happens every minute. --- hooks/hook.zsh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hooks/hook.zsh b/hooks/hook.zsh index 411b8ee..0bcd02c 100755 --- a/hooks/hook.zsh +++ b/hooks/hook.zsh @@ -22,6 +22,8 @@ function { if [[ ${1:-} = '--config' ]]; then quotedoc <<' EOF' configVersion: v1 + schedule: + - crontab: "* * * * *" kubernetes: - apiVersion: v1 kind: ConfigMap @@ -31,6 +33,7 @@ function { executeHookOnEvent: [ "Added" ] EOF else + cat "$BINDING_CONTEXT_PATH" config_map_name=$(jq -r '.[0].object.metadata.name' $BINDING_CONTEXT_PATH) print -- "ConfigMap '${config_map_name}' added" fi From 73e47851aa9653bf17eb5f476eec539020acdcc1 Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Sun, 13 Aug 2023 01:36:01 -0500 Subject: [PATCH 07/39] Add our label. --- config-map.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config-map.yaml b/config-map.yaml index 165bb99..d658100 100644 --- a/config-map.yaml +++ b/config-map.yaml @@ -3,5 +3,7 @@ kind: ConfigMap metadata: namespace: step-renewer name: example + labels: + step-renewer.prettyrobots.com: enabled data: hello: world From 3085fbded6c92363cbf6bdba80ed11207c6a1ef5 Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Sun, 13 Aug 2023 01:37:27 -0500 Subject: [PATCH 08/39] Let's switch to secrets. Now that we filter by label, we can work with secrets. --- config-map.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config-map.yaml b/config-map.yaml index d658100..5491afa 100644 --- a/config-map.yaml +++ b/config-map.yaml @@ -6,4 +6,5 @@ metadata: labels: step-renewer.prettyrobots.com: enabled data: - hello: world + tls.crt: crt + tls.key: key From 95a2b9880d8536e4f0afdb2f8c052399f763eb09 Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Sun, 13 Aug 2023 01:39:11 -0500 Subject: [PATCH 09/39] Rename our dummy certificate. --- config-map.yaml => certificate.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename config-map.yaml => certificate.yaml (90%) diff --git a/config-map.yaml b/certificate.yaml similarity index 90% rename from config-map.yaml rename to certificate.yaml index 5491afa..f4a5dad 100644 --- a/config-map.yaml +++ b/certificate.yaml @@ -1,5 +1,5 @@ apiVersion: v1 -kind: ConfigMap +kind: Secret metadata: namespace: step-renewer name: example From 17e91cecdb72582862459e5b3bbf3ba29984da40 Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Sun, 13 Aug 2023 06:40:18 +0000 Subject: [PATCH 10/39] Use secrets in our hook. --- hooks/hook.zsh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hooks/hook.zsh b/hooks/hook.zsh index 0bcd02c..6a03f90 100755 --- a/hooks/hook.zsh +++ b/hooks/hook.zsh @@ -26,7 +26,7 @@ function { - crontab: "* * * * *" kubernetes: - apiVersion: v1 - kind: ConfigMap + kind: Secret labelSelector: matchLabels: step-renewer.prettyrobots.com: enabled @@ -34,7 +34,7 @@ function { EOF else cat "$BINDING_CONTEXT_PATH" - config_map_name=$(jq -r '.[0].object.metadata.name' $BINDING_CONTEXT_PATH) - print -- "ConfigMap '${config_map_name}' added" + secret_name=$(jq -r '.[0].object.metadata.name' $BINDING_CONTEXT_PATH) + print -- "Secret '${config_map_name}' added" fi } "$@" From c980d02e17bc7d4bd2be94a163ae8fdb60457a93 Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Sun, 13 Aug 2023 02:11:32 -0500 Subject: [PATCH 11/39] Fix secret definition. --- certificate.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/certificate.yaml b/certificate.yaml index f4a5dad..adeb7d1 100644 --- a/certificate.yaml +++ b/certificate.yaml @@ -6,5 +6,4 @@ metadata: labels: step-renewer.prettyrobots.com: enabled data: - tls.crt: crt - tls.key: key + tls.crt: Y3J0Cg== From 047ecc05600d4e271c4937a3e708e720ce1b4d3f Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Sun, 13 Aug 2023 02:31:40 -0500 Subject: [PATCH 12/39] Remove `certificate.yaml`. We're going to have a real certificate now. Not a serious one, but I don't want to trigger any secret scanners. --- .gitignore | 1 + certificate.yaml | 9 --------- 2 files changed, 1 insertion(+), 9 deletions(-) create mode 100644 .gitignore delete mode 100644 certificate.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b132f29 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/certificate.yaml diff --git a/certificate.yaml b/certificate.yaml deleted file mode 100644 index adeb7d1..0000000 --- a/certificate.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - namespace: step-renewer - name: example - labels: - step-renewer.prettyrobots.com: enabled -data: - tls.crt: Y3J0Cg== From dd4089b4630d5860864656637421e2ad82a8634a Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Sun, 13 Aug 2023 13:01:54 -0500 Subject: [PATCH 13/39] Working renewal. Renewal within Kubernetes cluster now works. Going to reoganize to preserve the debugging functions as commands according to the `README` I've begun to write. --- .gitignore | 1 + README.md | 73 +++++++++++++++++++++++++++++++++++++ hooks/hook.zsh | 98 ++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 README.md diff --git a/.gitignore b/.gitignore index b132f29..dd98b28 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /certificate.yaml +/snapshot.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..a8c3f95 --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +`step-renewer` is a Kubernetes operator that renews Kubernetes `Secret` resources that contain Step CA issued certificates using `step ca renew` and therefore x5c certificate renewal. `step-renewer` will renew the certificates and update the Kubernetes `Secret` with the renewed certificate. + +`step-renewer` is a simple and minimal way to renew Step CA certificates in Kubernetes. It does not require providing a JWT provisioner password. It simply follows the same basic renewal instructions that you would use to renew certificates on Linux with `systemd`. It deploys as a single Pod. + +The drawback is that there is no automatic issuance of certificates. You need to build and upload a birth certificate but it will then be maintained in perpetuity. This is exactly what I need for a NATS or OpenSearch deployment. They each have a single TLS certificate, the deployments are long lived, and I'm not deploying NATS nor OpenSearch by the thousands. + +**Help**: If anyone can see any other advantages or drawbacks of this method over the `cert-manager` and `autocert` and other methods, I'd appreciate your feedback, and I've asked for guidance in the Step CA discussions. + +## Install + +You can create your own Kubernetes manifests to deploy. In our examples, as in all the Shell Operator examples from the Shell Operator documentation, we're going to use `kubectl` commands to create the namespace and service account. + +``` +kubectl create namespace step-renewer +kubectl create clusterrole +kubectl create serviceaccount +kubectl create clusterrolebinding +``` + +You can now create a Kubernetes manifest for the `step-renewer` Pod. You will need to set the following environment variables. + +``` +apiVersion: v1 +kind: Pod +``` + +Step Operator documentation usually shows deployment using Pods instead of Deployments. If this causes a problem I'll come back and update this documentation, if it doesn't I'll come back and let you know that it doesn't really cause a problem. + +Now when you create a certificate you need to label it with `step-renewer.prettybobots.com: enabled`. The `step-renewer` will check it according to it's Step Operator `schedule`. + +## Configuration + +The full set of configuration environment variables are as follows. + +| Name | Value | +| ---------------------------------- | ------------------------------------------------------------ | +| `STEP_RENEWSER_STEP_CA_URL` | The URL of the Step CA. | +| `STEP_RENEWER_STEP_CA_FINGERPRINT` | The fingerprint of the Step CA root certificate. | +| `STEP_RENEWER_DEBUG` | The amount of time remaining before certificate expiration, at which point a renewal should be attempted. The certificate renewal will not be performed if the time to expiration is greater than the value. Can be expressed as a percentage of the certificate validity duration. See `step ca renew --help` for more details. | +| `STEP_RENEWER_DEBUG` | (Optional) Print `ca certificate inspect` for each certificate on each scheduled invocation. | +| `STEP_RENEWER_UNSAFE_LOGGING` | (Optional) **Do not** set this environment variable for production. If set it will print the `_BINDING_CONTEXT` to standard output for use in debugging the application locally. Only use on development clusters with temporary, placeholder certificates. | + +To configure you build a container that overwrites the default configuration can mount a different configuration to `/hooks/config.yaml`. The default configuration is to run once every 15 minutes. + +## Hacking + +The crux of the operator is implemented in `hooks/common.zsh` which has the crux of the application. The entry point is `hooks/hook`. You can debug the application locally with a test cluster using the programs in `debug/`. + +The program `debug/renew /` tests renewal against a specific secret in your cluster. It is invoked with the the same environment variables used to deploy a pod, plus a `namespace/secret` argument indicating the certificate you want to renew. + +``` +STEP_RENEWER_EXPIRES_IN='0%' \ + STEP_RENEWER_STEP_CA_URL=https://ca.prettyrobots.com \ + STEP_RENEWER_FINGERPRINT=6fedeaa92e08e59967b8cb4ead5427b2c51a6ccb45cfe4f504d5af1a3392c16c \ + debug/rewnew step-renewer/example +``` + +The program `debug/binding_context ` will run through an example of a binding context. You can capture an example of a binding context by running the `step-renewer` pod with the environment variable `STEP_RENEWER_UNSAFE_LOGGING=1`. **Do not** set this environment variable in a production cluster. Once running, each invocation of the `step-renewer` will print the `BINDING_CONTEXT` to standard output. + +Because Shell Operator logs standard output wrapped in JSON, you will need to extract the lines from the JSON. You can get plain standard output with the following command. + +``` +kubectl -n step-renewer logs step-renewer | jq -r 'select(.output == "stdout") | .msg' +``` + +The above command simply extracts the standard output from the hook from the JSON formatted logging messages. You will have to copy and paste an example of the binding context into a JSON file. In the example of invocation below we've copied and pasted a binding context example into `binding_context.json`. + +``` +STEP_RENEWER_EXPIRES_IN='0%' \ + STEP_RENEWER_STEP_CA_URL=https://ca.prettyrobots.com \ + STEP_RENEWER_FINGERPRINT=6fedeaa92e08e59967b8cb4ead5427b2c51a6ccb45cfe4f504d5af1a3392c16c \ + debug/binding_context ./binding_context.json +``` diff --git a/hooks/hook.zsh b/hooks/hook.zsh index 6a03f90..8e4a78b 100755 --- a/hooks/hook.zsh +++ b/hooks/hook.zsh @@ -1,5 +1,11 @@ #!/usr/bin/env zsh +function abend { + printf -- "$@" 1>&2 + print -u 2 + exit 1 +} + function quotedoc { typeset heredoc spaces=65536 leading='^( +)([^[:space:]])' IFS='' dedented typeset -a lines @@ -12,12 +18,59 @@ function quotedoc { read -r -d '' dedented < <(printf "%s\n" "${lines[@]}" | sed -E 's/^ {'$spaces'}//') eval "$({ print "cat < "$tmp/temp.key" + base64 -d <<< "$crt" > "$tmp/temp.crt" + [[ -n "$STEP_RENEWER_DEBUG" ]] && step certificate inspect "$tmp/temp.crt" + expires=$(step certificate inspect --format json "$tmp/temp.crt" | jq -r '.validity.end') + if step certificate needs-renewal --expires-in "$STEP_RENEWER_EXPIRES_IN" "$tmp/temp.crt" 2>/dev/null; then + print -- "secret=$namespace/$name expires=$expires status=renewing" + step certificate fingerprint "$tmp/temp.crt" + step ca renew --force "$tmp/temp.crt" "$tmp/temp.key" 2>/dev/null + expires=$(step certificate inspect --format json "$tmp/temp.crt" | jq -r '.validity.end') + print -- "secret=$namespace/$name expires=$expires status=renewed" + expires=$(step certificate inspect --format json "$tmp/temp.crt" | jq -r '.validity.end') + quotedoc <<' EOF' > "$tmp/patch.yaml" + data: + tls.crt: $(base64 < "$tmp/temp.crt") + EOF + kubectl -n $namespace patch secret $name --patch-file "$tmp/patch.yaml" > /dev/null + else + print -- "secret=$namespace/$name expires=$expires status=okay" + fi + } always { + rm -rf "$tmp" + } +} + +function process_binding_context { + typeset process_binding=${1:-} + shift + step ca bootstrap --force \ + --ca-url "$STEP_RENEWER_STEP_CA_URL" \ + --fingerprint "$STEP_RENEWER_FINGERPRINT" > /dev/null 2>&1 || \ + abend 'unable to bootstrap step' + eval "set -- $( + jq -r '[ + .[0].snapshots.kubernetes[] | + (.object.metadata.name, .object.metadata.namespace, .object.data["tls.crt"], .object.data["tls.key"]) + ] | @sh' < "$process_binding" + )" + while (( $# )); do + maybe_renew_certificate "$@" + shift 4 + done +} + +function main { typeset config_map_name if [[ ${1:-} = '--config' ]]; then quotedoc <<' EOF' @@ -30,11 +83,42 @@ function { labelSelector: matchLabels: step-renewer.prettyrobots.com: enabled - executeHookOnEvent: [ "Added" ] + executeHookOnEvent: [] EOF else - cat "$BINDING_CONTEXT_PATH" - secret_name=$(jq -r '.[0].object.metadata.name' $BINDING_CONTEXT_PATH) - print -- "Secret '${config_map_name}' added" + [[ -n "$STEP_RENEWER_UNSAFE_LOGGING" ]] && { + cat "$BINDING_CONTEXT_PATH" + print + } + process_binding_context "$BINDING_CONTEXT_PATH" fi -} "$@" +} + +function debug_binding_context { + typeset tmp=$(mktemp -d) + { + STEPPATH="$tmp/step" process_binding_context './snapshot.json' + } always { + rm -rf "$tmp" + } +} + +# Used for debugging. +function debug_renewal { + typeset namespace=${1:-} name=${2:-} + typeset tmp=$(mktemp -d) + { + eval "set -- $( + kubectl -n "$namespace" get secret "$name" -o json | jq -r '[ .data["tls.crt"], .data["tls.key"] ] | @sh' + )" + STEPPATH="$tmp/step" step ca bootstrap --force \ + --ca-url "$STEP_RENEWER_STEP_CA_URL" \ + --fingerprint "$STEP_RENEWER_FINGERPRINT" > /dev/null 2>&1 || \ + abend 'unable to bootstrap step' + STEPPATH="$tmp/step" maybe_renew_certificate $name $namespace "$@" + } always { + rm -rf "$tmp" + } +} + +debug_renewal "$@" From 09d5d9785872e5dc8635ad1332850c125397c3af Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Wed, 20 Sep 2023 10:27:49 -0500 Subject: [PATCH 14/39] Reorganize for debugging. Create a common library with different executables for debugging. --- README.md | 4 +- debug/binding_context | 12 ++++++ debug/renew | 20 +++++++++ hooks/hook | 27 ++++++++++++ hooks/hook.zsh => lib/step-renewer.zsh | 60 ++------------------------ step-renewer.yaml | 37 ++++++++++++++++ 6 files changed, 101 insertions(+), 59 deletions(-) create mode 100644 debug/binding_context create mode 100755 debug/renew create mode 100755 hooks/hook rename hooks/hook.zsh => lib/step-renewer.zsh (59%) diff --git a/README.md b/README.md index a8c3f95..c271195 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,9 @@ The full set of configuration environment variables are as follows. | Name | Value | | ---------------------------------- | ------------------------------------------------------------ | -| `STEP_RENEWSER_STEP_CA_URL` | The URL of the Step CA. | +| `STEP_RENEWER_STEP_CA_URL` | The URL of the Step CA. | | `STEP_RENEWER_STEP_CA_FINGERPRINT` | The fingerprint of the Step CA root certificate. | -| `STEP_RENEWER_DEBUG` | The amount of time remaining before certificate expiration, at which point a renewal should be attempted. The certificate renewal will not be performed if the time to expiration is greater than the value. Can be expressed as a percentage of the certificate validity duration. See `step ca renew --help` for more details. | +| `STEP_RENEWER_EXPIRES_IN` | The amount of time remaining before certificate expiration, at which point a renewal should be attempted. The certificate renewal will not be performed if the time to expiration is greater than the value. Can be expressed as a percentage of the certificate validity duration. See `step ca renew --help` for more details. | | `STEP_RENEWER_DEBUG` | (Optional) Print `ca certificate inspect` for each certificate on each scheduled invocation. | | `STEP_RENEWER_UNSAFE_LOGGING` | (Optional) **Do not** set this environment variable for production. If set it will print the `_BINDING_CONTEXT` to standard output for use in debugging the application locally. Only use on development clusters with temporary, placeholder certificates. | diff --git a/debug/binding_context b/debug/binding_context new file mode 100644 index 0000000..0769ba7 --- /dev/null +++ b/debug/binding_context @@ -0,0 +1,12 @@ +#!/usr/bin/env zsh + +source "${0:a:h}/../lib/step-renewer.zsh" + +function { + typeset tmp=$(mktemp -d) + { + STEPPATH="$tmp/step" process_binding_context './snapshot.json' + } always { + rm -rf "$tmp" + } +} "$@" diff --git a/debug/renew b/debug/renew new file mode 100755 index 0000000..7966d75 --- /dev/null +++ b/debug/renew @@ -0,0 +1,20 @@ +#!/usr/bin/env zsh + +source "${0:a:h}/../lib/step-renewer.zsh" + +function { + typeset namespace=${1:-} name=${2:-} + typeset tmp=$(mktemp -d) + { + eval "set -- $( + kubectl -n "$namespace" get secret "$name" -o json | jq -r '[ .data["tls.crt"], .data["tls.key"] ] | @sh' + )" + STEPPATH="$tmp/step" step ca bootstrap --force \ + --ca-url "$STEP_RENEWER_STEP_CA_URL" \ + --fingerprint "$STEP_RENEWER_STEP_CA_FINGERPRINT" > /dev/null 2>&1 || \ + abend 'unable to bootstrap step' + STEPPATH="$tmp/step" maybe_renew_certificate $name $namespace "$@" + } always { + rm -rf "$tmp" + } +} "$@" diff --git a/hooks/hook b/hooks/hook new file mode 100755 index 0000000..a67edd5 --- /dev/null +++ b/hooks/hook @@ -0,0 +1,27 @@ +#!/usr/bin/env zsh + +source "${0:a:h}/../lib/step-renewer.zsh" + +function { + typeset config_map_name + if [[ ${1:-} = '--config' ]]; then + quotedoc <<' EOF' + configVersion: v1 + schedule: + - crontab: "* * * * *" + kubernetes: + - apiVersion: v1 + kind: Secret + labelSelector: + matchLabels: + flatheadmaill.github.com: step-renewer + executeHookOnEvent: [] + EOF + else + [[ -n "$STEP_RENEWER_UNSAFE_LOGGING" ]] && { + cat "$BINDING_CONTEXT_PATH" + print + } + process_binding_context "$BINDING_CONTEXT_PATH" + fi +} "$@" diff --git a/hooks/hook.zsh b/lib/step-renewer.zsh similarity index 59% rename from hooks/hook.zsh rename to lib/step-renewer.zsh index 8e4a78b..5ce8e4f 100755 --- a/hooks/hook.zsh +++ b/lib/step-renewer.zsh @@ -7,8 +7,7 @@ function abend { } function quotedoc { - typeset heredoc spaces=65536 leading='^( +)([^[:space:]])' IFS='' dedented - typeset -a lines + typeset lines=() spaces=65536 leading='^( +)([^[:space:]])' IFS='' dedented while read -r line; do lines+=("$line") if [[ "$line" =~ $leading && "${#match[1]}" -lt "$spaces" ]]; then @@ -19,7 +18,7 @@ function quotedoc { eval "$({ print "cat < /dev/null 2>&1 || \ + --fingerprint "$STEP_RENEWER_STEP_CA_FINGERPRINT" > /dev/null 2>&1 || \ abend 'unable to bootstrap step' eval "set -- $( jq -r '[ @@ -69,56 +68,3 @@ function process_binding_context { shift 4 done } - -function main { - typeset config_map_name - if [[ ${1:-} = '--config' ]]; then - quotedoc <<' EOF' - configVersion: v1 - schedule: - - crontab: "* * * * *" - kubernetes: - - apiVersion: v1 - kind: Secret - labelSelector: - matchLabels: - step-renewer.prettyrobots.com: enabled - executeHookOnEvent: [] - EOF - else - [[ -n "$STEP_RENEWER_UNSAFE_LOGGING" ]] && { - cat "$BINDING_CONTEXT_PATH" - print - } - process_binding_context "$BINDING_CONTEXT_PATH" - fi -} - -function debug_binding_context { - typeset tmp=$(mktemp -d) - { - STEPPATH="$tmp/step" process_binding_context './snapshot.json' - } always { - rm -rf "$tmp" - } -} - -# Used for debugging. -function debug_renewal { - typeset namespace=${1:-} name=${2:-} - typeset tmp=$(mktemp -d) - { - eval "set -- $( - kubectl -n "$namespace" get secret "$name" -o json | jq -r '[ .data["tls.crt"], .data["tls.key"] ] | @sh' - )" - STEPPATH="$tmp/step" step ca bootstrap --force \ - --ca-url "$STEP_RENEWER_STEP_CA_URL" \ - --fingerprint "$STEP_RENEWER_FINGERPRINT" > /dev/null 2>&1 || \ - abend 'unable to bootstrap step' - STEPPATH="$tmp/step" maybe_renew_certificate $name $namespace "$@" - } always { - rm -rf "$tmp" - } -} - -debug_renewal "$@" diff --git a/step-renewer.yaml b/step-renewer.yaml index 9bc3747..d21390f 100644 --- a/step-renewer.yaml +++ b/step-renewer.yaml @@ -13,4 +13,41 @@ spec: - name: step-renewer image: flatheadmill/step-renewer:latest imagePullPolicy: Always + env: + - name: STEP_RENEWER_STEP_CA_URL + value: https://ca.prettyrobots.net + - name: STEP_RENEWER_STEP_CA_FINGERPRINT + value: 6fedeaa92e08e59967b8cb4ead5427b2c51a6ccb45cfe4f504d5af1a3392c16c + - name: STEP_RENEWER_EXPIRES_IN + value: '80%' + - name: STEP_RENEWER_DEBUG + value: '1' serviceAccountName: step-renewer +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: step-renewer + namespace: step-renewer +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: step-renewer +rules: +- apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "watch", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: step-renewer +subjects: +- kind: ServiceAccount + name: step-renewer + namespace: step-renewer +roleRef: + kind: ClusterRole + name: step-renewer + apiGroup: rbac.authorization.k8s.io From 6782683aa15ea69610bcb9314bd480f6c80f979b Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Wed, 20 Sep 2023 20:46:30 -0500 Subject: [PATCH 15/39] Add builder version. --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 54d1c30..9b5e6df 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,4 @@ +# syntax=docker/dockerfile:1.3-labs FROM ghcr.io/flant/shell-operator:latest RUN < Date: Wed, 20 Sep 2023 20:53:28 -0500 Subject: [PATCH 16/39] Remove `lib` directory. Since it all needs to be in `/hooks/`. --- hooks/hook | 2 +- {lib => hooks}/step-renewer.zsh | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename {lib => hooks}/step-renewer.zsh (100%) mode change 100755 => 100644 diff --git a/hooks/hook b/hooks/hook index a67edd5..9002f8d 100755 --- a/hooks/hook +++ b/hooks/hook @@ -1,6 +1,6 @@ #!/usr/bin/env zsh -source "${0:a:h}/../lib/step-renewer.zsh" +source "${0:a:h}/step-renewer.zsh" function { typeset config_map_name diff --git a/lib/step-renewer.zsh b/hooks/step-renewer.zsh old mode 100755 new mode 100644 similarity index 100% rename from lib/step-renewer.zsh rename to hooks/step-renewer.zsh From d7de4e848b397caf280cdcf01597660d7bdaf968 Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Wed, 20 Sep 2023 21:10:06 -0500 Subject: [PATCH 17/39] Guess we're not getting snapshots, per se. --- hooks/step-renewer.zsh | 2 +- step-renewer.yaml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/hooks/step-renewer.zsh b/hooks/step-renewer.zsh index 5ce8e4f..9f6a7ed 100644 --- a/hooks/step-renewer.zsh +++ b/hooks/step-renewer.zsh @@ -59,7 +59,7 @@ function process_binding_context { abend 'unable to bootstrap step' eval "set -- $( jq -r '[ - .[0].snapshots.kubernetes[] | + .[0] | (.object.metadata.name, .object.metadata.namespace, .object.data["tls.crt"], .object.data["tls.key"]) ] | @sh' < "$process_binding" )" diff --git a/step-renewer.yaml b/step-renewer.yaml index d21390f..2bffcb1 100644 --- a/step-renewer.yaml +++ b/step-renewer.yaml @@ -22,6 +22,8 @@ spec: value: '80%' - name: STEP_RENEWER_DEBUG value: '1' + - name: STEP_RENEWER_UNSAFE_LOGGING + value: '1' serviceAccountName: step-renewer --- apiVersion: v1 From 169c554642d500f9e756b427b5f4a6d184f59ecc Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Wed, 20 Sep 2023 21:10:49 -0500 Subject: [PATCH 18/39] Install `step-cli`. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9b5e6df..a3a6aa9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,6 @@ FROM ghcr.io/flant/shell-operator:latest RUN < Date: Wed, 20 Sep 2023 21:14:49 -0500 Subject: [PATCH 19/39] Fix label matching. --- hooks/hook | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/hook b/hooks/hook index 9002f8d..a6c0a04 100755 --- a/hooks/hook +++ b/hooks/hook @@ -14,7 +14,7 @@ function { kind: Secret labelSelector: matchLabels: - flatheadmaill.github.com: step-renewer + flatheadmill.github.io: step-renewer executeHookOnEvent: [] EOF else From abaff1e09cc05c8719dc5306f8f7243d12c97ed9 Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Wed, 20 Sep 2023 21:21:24 -0500 Subject: [PATCH 20/39] Include secret snapshots. --- hooks/hook | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hooks/hook b/hooks/hook index a6c0a04..26d7485 100755 --- a/hooks/hook +++ b/hooks/hook @@ -9,8 +9,10 @@ function { configVersion: v1 schedule: - crontab: "* * * * *" + includeSnapshotsFrom: secrets kubernetes: - - apiVersion: v1 + - name: secrets + apiVersion: v1 kind: Secret labelSelector: matchLabels: From 418b0007873d376c65719dcf45706191bec7e801 Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Wed, 20 Sep 2023 21:23:00 -0500 Subject: [PATCH 21/39] Might have used a group last time. --- hooks/hook | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hooks/hook b/hooks/hook index 26d7485..a2c4f2e 100755 --- a/hooks/hook +++ b/hooks/hook @@ -9,9 +9,9 @@ function { configVersion: v1 schedule: - crontab: "* * * * *" - includeSnapshotsFrom: secrets + group: secrets kubernetes: - - name: secrets + - group: secrets apiVersion: v1 kind: Secret labelSelector: From 46f8fe4d4b32b95991ddc209c8efab7293269dcf Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Wed, 20 Sep 2023 21:33:52 -0500 Subject: [PATCH 22/39] Restore snapshot `jq`. --- hooks/step-renewer.zsh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/step-renewer.zsh b/hooks/step-renewer.zsh index 9f6a7ed..5ce8e4f 100644 --- a/hooks/step-renewer.zsh +++ b/hooks/step-renewer.zsh @@ -59,7 +59,7 @@ function process_binding_context { abend 'unable to bootstrap step' eval "set -- $( jq -r '[ - .[0] | + .[0].snapshots.kubernetes[] | (.object.metadata.name, .object.metadata.namespace, .object.data["tls.crt"], .object.data["tls.key"]) ] | @sh' < "$process_binding" )" From 270b504d3638e63be87b2f5340649b72754514fe Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Wed, 20 Sep 2023 21:37:33 -0500 Subject: [PATCH 23/39] Change to `ClusterRoleBinding`. --- step-renewer.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/step-renewer.yaml b/step-renewer.yaml index 2bffcb1..bb554b1 100644 --- a/step-renewer.yaml +++ b/step-renewer.yaml @@ -42,7 +42,7 @@ rules: verbs: ["get", "watch", "list"] --- apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding +kind: ClusterRoleBinding metadata: name: step-renewer subjects: From 557f80fbe3a566ded3ceafafc341453e551eb4f1 Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Wed, 20 Sep 2023 21:39:05 -0500 Subject: [PATCH 24/39] Setting a bad example with unsafe logging. --- step-renewer.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/step-renewer.yaml b/step-renewer.yaml index bb554b1..db5be70 100644 --- a/step-renewer.yaml +++ b/step-renewer.yaml @@ -22,8 +22,6 @@ spec: value: '80%' - name: STEP_RENEWER_DEBUG value: '1' - - name: STEP_RENEWER_UNSAFE_LOGGING - value: '1' serviceAccountName: step-renewer --- apiVersion: v1 From 77b68edb77a2de455b317611b454919243f198ce Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Wed, 20 Sep 2023 21:40:00 -0500 Subject: [PATCH 25/39] Rename group to `certificates`. --- hooks/hook | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hooks/hook b/hooks/hook index a2c4f2e..2cfd708 100755 --- a/hooks/hook +++ b/hooks/hook @@ -9,15 +9,15 @@ function { configVersion: v1 schedule: - crontab: "* * * * *" - group: secrets + group: certificates kubernetes: - - group: secrets - apiVersion: v1 + - apiVersion: v1 kind: Secret labelSelector: matchLabels: flatheadmill.github.io: step-renewer executeHookOnEvent: [] + group: certificates EOF else [[ -n "$STEP_RENEWER_UNSAFE_LOGGING" ]] && { From 4dcb96551ff7b1f0a78de3c4e5436d37e594f263 Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Thu, 21 Sep 2023 15:32:32 -0500 Subject: [PATCH 26/39] Emit configuration using `jo`. --- Dockerfile | 3 ++- debug/binding_context | 2 +- debug/config | 5 +++++ debug/renew | 2 +- hooks/config.zsh | 20 ++++++++++++++++++++ hooks/hook | 4 +++- 6 files changed, 32 insertions(+), 4 deletions(-) create mode 100755 debug/config create mode 100644 hooks/config.zsh diff --git a/Dockerfile b/Dockerfile index a3a6aa9..dd75051 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,8 @@ # syntax=docker/dockerfile:1.3-labs FROM ghcr.io/flant/shell-operator:latest RUN < Date: Thu, 21 Sep 2023 20:34:00 +0000 Subject: [PATCH 27/39] Remove test to see if `jo` is installed. --- Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index dd75051..5bd4590 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,6 @@ # syntax=docker/dockerfile:1.3-labs FROM ghcr.io/flant/shell-operator:latest RUN < Date: Fri, 12 Jan 2024 13:03:29 -0600 Subject: [PATCH 28/39] Sketch of how `HUP` might work. Can't remember if `step-renewer` is per namespace or cluster wide at the moment, but the gist of it is a shell program (or similar) with a shebang line. --- README.md | 66 ++++++++++++++++++++++++++++++++++++++++-- debug/renew | 6 ++-- hooks/hook | 18 ++---------- hooks/step-renewer.zsh | 17 +++++++---- step-renewer.yaml | 3 ++ 5 files changed, 83 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index c271195..34b307f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ `step-renewer` is a simple and minimal way to renew Step CA certificates in Kubernetes. It does not require providing a JWT provisioner password. It simply follows the same basic renewal instructions that you would use to renew certificates on Linux with `systemd`. It deploys as a single Pod. -The drawback is that there is no automatic issuance of certificates. You need to build and upload a birth certificate but it will then be maintained in perpetuity. This is exactly what I need for a NATS or OpenSearch deployment. They each have a single TLS certificate, the deployments are long lived, and I'm not deploying NATS nor OpenSearch by the thousands. +The drawback is that there is no automatic issuance of certificates. You need to build and upload a birth certificate but it will then be maintained in perpetuity. This is exactly what I need for a NATS, PostgreSQL or OpenSearch deployment in Kubernetes. They each have a single TLS certificate, the deployments are long lived, and I'm not deploying NATS nor OpenSearch by the thousands. **Help**: If anyone can see any other advantages or drawbacks of this method over the `cert-manager` and `autocert` and other methods, I'd appreciate your feedback, and I've asked for guidance in the Step CA discussions. @@ -21,7 +21,66 @@ You can now create a Kubernetes manifest for the `step-renewer` Pod. You will ne ``` apiVersion: v1 +kind: Namespace +metadata: + name: step-renewer +--- +apiVersion: v1 kind: Pod +metadata: + namespace: step-renewer + name: step-renewer +spec: + containers: + - name: step-renewer + image: flatheadmill/step-renewer:latest + imagePullPolicy: Always + env: + - name: STEP_RENEWER_STEP_CA_URL + value: https://ca.prettyrobots.net + - name: STEP_RENEWER_STEP_CA_FINGERPRINT + value: 6fedeaa92e08e59967b8cb4ead5427b2c51a6ccb45cfe4f504d5af1a3392c16c + - name: STEP_RENEWER_EXPIRES_IN + value: '80%' + - name: STEP_RENEWER_DEBUG + value: '1' + - name: STEP_RENEWER_HUP + value: | + #!/usr/bin/env zsh + case "$1" in + program/program ) + curl http://my-secure-service.step-renewer/reload-certs + ;; + esac + serviceAccountName: step-renewer +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: step-renewer + namespace: step-renewer +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: step-renewer +rules: +- apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "watch", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: step-renewer +subjects: +- kind: ServiceAccount + name: step-renewer + namespace: step-renewer +roleRef: + kind: ClusterRole + name: step-renewer + apiGroup: rbac.authorization.k8s.io ``` Step Operator documentation usually shows deployment using Pods instead of Deployments. If this causes a problem I'll come back and update this documentation, if it doesn't I'll come back and let you know that it doesn't really cause a problem. @@ -37,6 +96,7 @@ The full set of configuration environment variables are as follows. | `STEP_RENEWER_STEP_CA_URL` | The URL of the Step CA. | | `STEP_RENEWER_STEP_CA_FINGERPRINT` | The fingerprint of the Step CA root certificate. | | `STEP_RENEWER_EXPIRES_IN` | The amount of time remaining before certificate expiration, at which point a renewal should be attempted. The certificate renewal will not be performed if the time to expiration is greater than the value. Can be expressed as a percentage of the certificate validity duration. See `step ca renew --help` for more details. | +| `STEP_RENEWER_HUP` | A string containing an interpreted program that will be run after a secret has been updated. The program will be written to file with the executable bit set and executed to reload any services. It should be plain text and start with a shebang line. | | `STEP_RENEWER_DEBUG` | (Optional) Print `ca certificate inspect` for each certificate on each scheduled invocation. | | `STEP_RENEWER_UNSAFE_LOGGING` | (Optional) **Do not** set this environment variable for production. If set it will print the `_BINDING_CONTEXT` to standard output for use in debugging the application locally. Only use on development clusters with temporary, placeholder certificates. | @@ -44,9 +104,9 @@ To configure you build a container that overwrites the default configuration can ## Hacking -The crux of the operator is implemented in `hooks/common.zsh` which has the crux of the application. The entry point is `hooks/hook`. You can debug the application locally with a test cluster using the programs in `debug/`. +The crux of the operator is implemented in `hooks/step-renewer.zsh`. The entry point is `hooks/hook`. You can debug the application locally with a test cluster using the programs in `debug/`. -The program `debug/renew /` tests renewal against a specific secret in your cluster. It is invoked with the the same environment variables used to deploy a pod, plus a `namespace/secret` argument indicating the certificate you want to renew. +The program `debug/renew /` tests renewal against a specific secret in your cluster. Invoke it with the same environment variables used to deploy a pod, plus a `namespace/secret` argument indicating the certificate you want to renew. ``` STEP_RENEWER_EXPIRES_IN='0%' \ diff --git a/debug/renew b/debug/renew index 2d23259..0622b47 100755 --- a/debug/renew +++ b/debug/renew @@ -6,15 +6,15 @@ function { typeset namespace=${1:-} name=${2:-} typeset tmp=$(mktemp -d) { - eval "set -- $( + set -- "${(QA@)${(z)$( kubectl -n "$namespace" get secret "$name" -o json | jq -r '[ .data["tls.crt"], .data["tls.key"] ] | @sh' - )" + )}}" STEPPATH="$tmp/step" step ca bootstrap --force \ --ca-url "$STEP_RENEWER_STEP_CA_URL" \ --fingerprint "$STEP_RENEWER_STEP_CA_FINGERPRINT" > /dev/null 2>&1 || \ abend 'unable to bootstrap step' STEPPATH="$tmp/step" maybe_renew_certificate $name $namespace "$@" } always { - rm -rf "$tmp" + [[ -d "$tmp" ]] && rm -rf "$tmp" } } "$@" diff --git a/hooks/hook b/hooks/hook index 0189789..c45b3ff 100755 --- a/hooks/hook +++ b/hooks/hook @@ -7,25 +7,11 @@ function { typeset config_map_name if [[ ${1:-} = '--config' ]]; then config - false && quotedoc <<' EOF' - configVersion: v1 - schedule: - - crontab: "* * * * *" - group: certificates - kubernetes: - - apiVersion: v1 - kind: Secret - labelSelector: - matchLabels: - flatheadmill.github.io: step-renewer - executeHookOnEvent: [] - group: certificates - EOF else - [[ -n "$STEP_RENEWER_UNSAFE_LOGGING" ]] && { + if [[ $STEP_RENEWER_UNSAFE_LOGGING = 1 ]]; then cat "$BINDING_CONTEXT_PATH" print - } + fi process_binding_context "$BINDING_CONTEXT_PATH" fi } "$@" diff --git a/hooks/step-renewer.zsh b/hooks/step-renewer.zsh index 5ce8e4f..a6c2206 100644 --- a/hooks/step-renewer.zsh +++ b/hooks/step-renewer.zsh @@ -7,7 +7,7 @@ function abend { } function quotedoc { - typeset lines=() spaces=65536 leading='^( +)([^[:space:]])' IFS='' dedented + typeset lines=() spaces=65536 leading='^( +)([^[:space:]])' IFS='' dedented match=() while read -r line; do lines+=("$line") if [[ "$line" =~ $leading && "${#match[1]}" -lt "$spaces" ]]; then @@ -28,7 +28,7 @@ function maybe_renew_certificate { { base64 -d <<< "$key" > "$tmp/temp.key" base64 -d <<< "$crt" > "$tmp/temp.crt" - [[ -n "$STEP_RENEWER_DEBUG" ]] && step certificate inspect "$tmp/temp.crt" + [[ $STEP_RENEWER_DEBUG = 1 ]] && step certificate inspect "$tmp/temp.crt" expires=$(step certificate inspect --format json "$tmp/temp.crt" | jq -r '.validity.end') if step certificate needs-renewal --expires-in "$STEP_RENEWER_EXPIRES_IN" "$tmp/temp.crt" 2>/dev/null; then print -- "secret=$namespace/$name expires=$expires status=renewing" @@ -42,27 +42,34 @@ function maybe_renew_certificate { tls.crt: $(base64 < "$tmp/temp.crt") EOF kubectl -n $namespace patch secret $name --patch-file "$tmp/patch.yaml" > /dev/null + if [[ -n $STEP_RENEWER_HUP ]]; then + cat <<< "$STEP_RENEWER_HUP" > "$tmp/hup" + chmod 755 "$tmp/hup" + "$tmp/hup" "$namespace/$name" + fi else print -- "secret=$namespace/$name expires=$expires status=okay" fi } always { - rm -rf "$tmp" + [[ -d "$tmp" ]] && rm -rf "$tmp" } } function process_binding_context { typeset process_binding=${1:-} shift + [[ -n $STEP_RENEWER_STEP_CA_URL ]] || abend 'STEP_RENEWER_STEP_CA_URL is not set' + [[ -n $STEP_RENEWER_STEP_CA_FINGERPRINT ]] || abend 'STEP_RENEWER_STEP_CA_FINGERPRINT is not set' step ca bootstrap --force \ --ca-url "$STEP_RENEWER_STEP_CA_URL" \ --fingerprint "$STEP_RENEWER_STEP_CA_FINGERPRINT" > /dev/null 2>&1 || \ abend 'unable to bootstrap step' - eval "set -- $( + set -- "${(@QA){$(z)$( jq -r '[ .[0].snapshots.kubernetes[] | (.object.metadata.name, .object.metadata.namespace, .object.data["tls.crt"], .object.data["tls.key"]) ] | @sh' < "$process_binding" - )" + )}}" while (( $# )); do maybe_renew_certificate "$@" shift 4 diff --git a/step-renewer.yaml b/step-renewer.yaml index db5be70..9f90f0a 100644 --- a/step-renewer.yaml +++ b/step-renewer.yaml @@ -22,6 +22,9 @@ spec: value: '80%' - name: STEP_RENEWER_DEBUG value: '1' + - name: STEP_RENEWER_HUP + value: | + curl http:://program.program/reload-certs serviceAccountName: step-renewer --- apiVersion: v1 From d3a71b1a75c0e7a88460fb19fc3911a9db2cc69c Mon Sep 17 00:00:00 2001 From: Acre Operator Date: Sun, 21 Jan 2024 04:32:02 +0000 Subject: [PATCH 29/39] Fix base64 encoding, bad parameter substitution. --- hooks/step-renewer.zsh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hooks/step-renewer.zsh b/hooks/step-renewer.zsh index a6c2206..457f925 100644 --- a/hooks/step-renewer.zsh +++ b/hooks/step-renewer.zsh @@ -23,8 +23,8 @@ function quotedoc { } function maybe_renew_certificate { - typeset name=${1:-} namespace=${2:-} crt=${3:-} key=${4:-} - typeset tmp=$(mktemp -d) expires + typeset name=${1:-} namespace=${2:-} crt=${3:-} key=${4:-} tmp expires + tmp=$(mktemp -d) || abend 'cannot create temporary directory' { base64 -d <<< "$key" > "$tmp/temp.key" base64 -d <<< "$crt" > "$tmp/temp.crt" @@ -33,14 +33,14 @@ function maybe_renew_certificate { if step certificate needs-renewal --expires-in "$STEP_RENEWER_EXPIRES_IN" "$tmp/temp.crt" 2>/dev/null; then print -- "secret=$namespace/$name expires=$expires status=renewing" step certificate fingerprint "$tmp/temp.crt" - step ca renew --force "$tmp/temp.crt" "$tmp/temp.key" 2>/dev/null + step ca renew --force "$tmp/temp.crt" "$tmp/temp.key" || abend 'unable to renew `%s/%s`.' $namespace $name expires=$(step certificate inspect --format json "$tmp/temp.crt" | jq -r '.validity.end') print -- "secret=$namespace/$name expires=$expires status=renewed" - expires=$(step certificate inspect --format json "$tmp/temp.crt" | jq -r '.validity.end') quotedoc <<' EOF' > "$tmp/patch.yaml" data: - tls.crt: $(base64 < "$tmp/temp.crt") + tls.crt: $(base64 -w 0 < "$tmp/temp.crt") EOF + cat "$tmp/patch.yaml" kubectl -n $namespace patch secret $name --patch-file "$tmp/patch.yaml" > /dev/null if [[ -n $STEP_RENEWER_HUP ]]; then cat <<< "$STEP_RENEWER_HUP" > "$tmp/hup" @@ -64,7 +64,7 @@ function process_binding_context { --ca-url "$STEP_RENEWER_STEP_CA_URL" \ --fingerprint "$STEP_RENEWER_STEP_CA_FINGERPRINT" > /dev/null 2>&1 || \ abend 'unable to bootstrap step' - set -- "${(@QA){$(z)$( + set -- "${(@QA)${(z)$( jq -r '[ .[0].snapshots.kubernetes[] | (.object.metadata.name, .object.metadata.namespace, .object.data["tls.crt"], .object.data["tls.key"]) From 26ce93fe9ef14109e8e3d81d6fd3d2278b63bf86 Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Wed, 24 Apr 2024 20:47:40 +0000 Subject: [PATCH 30/39] Sketch of non `tls` type secrets with multiple certificates. --- hooks/step-renewer.zsh | 82 +++++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 29 deletions(-) diff --git a/hooks/step-renewer.zsh b/hooks/step-renewer.zsh index 457f925..5651f02 100644 --- a/hooks/step-renewer.zsh +++ b/hooks/step-renewer.zsh @@ -23,33 +23,30 @@ function quotedoc { } function maybe_renew_certificate { - typeset name=${1:-} namespace=${2:-} crt=${3:-} key=${4:-} tmp expires + typeset name=${1:-} namespace=${2:-} tmp expires pairs + shift 2 tmp=$(mktemp -d) || abend 'cannot create temporary directory' { - base64 -d <<< "$key" > "$tmp/temp.key" - base64 -d <<< "$crt" > "$tmp/temp.crt" - [[ $STEP_RENEWER_DEBUG = 1 ]] && step certificate inspect "$tmp/temp.crt" - expires=$(step certificate inspect --format json "$tmp/temp.crt" | jq -r '.validity.end') - if step certificate needs-renewal --expires-in "$STEP_RENEWER_EXPIRES_IN" "$tmp/temp.crt" 2>/dev/null; then - print -- "secret=$namespace/$name expires=$expires status=renewing" - step certificate fingerprint "$tmp/temp.crt" - step ca renew --force "$tmp/temp.crt" "$tmp/temp.key" || abend 'unable to renew `%s/%s`.' $namespace $name + while (( $# )); do + encoding=${1:-} crt_name=${2:-} crt=${3:-} key=${4:-} + shift 5 + base64 -d <<< "$key" > "$tmp/temp.key" + base64 -d <<< "$crt" > "$tmp/temp.crt" + [[ $STEP_RENEWER_DEBUG = 1 ]] && step certificate inspect "$tmp/temp.crt" expires=$(step certificate inspect --format json "$tmp/temp.crt" | jq -r '.validity.end') - print -- "secret=$namespace/$name expires=$expires status=renewed" - quotedoc <<' EOF' > "$tmp/patch.yaml" - data: - tls.crt: $(base64 -w 0 < "$tmp/temp.crt") - EOF - cat "$tmp/patch.yaml" - kubectl -n $namespace patch secret $name --patch-file "$tmp/patch.yaml" > /dev/null - if [[ -n $STEP_RENEWER_HUP ]]; then - cat <<< "$STEP_RENEWER_HUP" > "$tmp/hup" - chmod 755 "$tmp/hup" - "$tmp/hup" "$namespace/$name" + if step certificate needs-renewal --expires-in "$STEP_RENEWER_EXPIRES_IN" "$tmp/temp.crt" 2>/dev/null; then + print -- "secret=$namespace/$name expires=$expires status=renewing" + step certificate fingerprint "$tmp/temp.crt" + step ca renew --force "$tmp/temp.crt" "$tmp/temp.key" || abend 'unable to renew `%s/%s`.' $namespace $name + expires=$(step certificate inspect --format json "$tmp/temp.crt" | jq -r '.validity.end') + print -- "secret=$namespace/$name expires=$expires status=renewed" + jo data="$(jo $crt_name=@<(base64 -w 0 < $tmp/temp.crt))" > $tmp/patch.json + cat "$tmp/patch.json" + kubectl -n $namespace patch secret $name --patch-file "$tmp/patch.json" > /dev/null + else + print -- "secret=$namespace/$name expires=$expires status=okay" fi - else - print -- "secret=$namespace/$name expires=$expires status=okay" - fi + done } always { [[ -d "$tmp" ]] && rm -rf "$tmp" } @@ -65,13 +62,40 @@ function process_binding_context { --fingerprint "$STEP_RENEWER_STEP_CA_FINGERPRINT" > /dev/null 2>&1 || \ abend 'unable to bootstrap step' set -- "${(@QA)${(z)$( - jq -r '[ + jq -r ' + [ .[0].snapshots.kubernetes[] | - (.object.metadata.name, .object.metadata.namespace, .object.data["tls.crt"], .object.data["tls.key"]) - ] | @sh' < "$process_binding" + .object as $root | + [[ + if (.object.metadata.annotations | has("flatheadmill.github.io/pairs")) + then .object.metadata.annotations["flatheadmill.github.io/pairs"] + else "tls.crt/tls.key/pem" end | + split(":")[] | + split("/") | { + crt: (if (. | length) > 1 then .[0] else "" end), + key: (if (. | length) > 2 then .[1] else "" end), + type: (if (. | length) == 3 then .[2] else "pem" end) + } + ][] | ( + .type, + .crt, + (.crt as $crt | if (.crt != "" and $root.data | has($crt)) then $root.data[.crt] else "" end), + (.key as $key | if (.key != "" and $root.data | has($key)) then $root.data[.key] else "" end) + )] as $certficates | + ( + .object.metadata.name, + .object.metadata.namespace, + $certficates | length, + $certficates[] + ) + ] | flatten | @sh' < $process_binding )}}" - while (( $# )); do - maybe_renew_certificate "$@" - shift 4 + typeset name namespace count certificates=() + while $(( #@ )); do + name=${1:-} namespace=${2:-} count=${3:-} + shift 3 + certificates=( "${@[1,$count]}" ) + shift $count + maybe_renew_certificates $name $namespace "${(@)certificates}" done } From ddd684159a755e3ff073be3eb976cc7bccd9c6ae Mon Sep 17 00:00:00 2001 From: flatheadmill Date: Thu, 25 Apr 2024 08:41:47 -0500 Subject: [PATCH 31/39] Working non-TLS type secrets. When working with OpenSearch I need to create an api certificate, a certificate for a transport layer and client certificate for administration of the cluster. These are all kept in the same secret so they can be mounted in a directory next to each other. Further more, the keys need be in PKCS#8 format. Added an annotation for a colon separated list of key pairs in secret. The pairs are slash delimited. Shouldn't be the case that someone needs a slash or a colon in their key name so I'm not going to add escaping for the delimiters. The pair can contain a third part which is the key type either `pkcs8` or `pem`, but I'm not sure if `pem` is the right name for the default format. If no pair string is provided it assumed to be `"tls.crt/tls.key/pem"`. --- debug/binding_context | 4 +- debug/config | 2 +- debug/renew | 18 +----- hooks/hook | 8 +-- hooks/step-renewer.zsh | 135 +++++++++++++++++++---------------------- 5 files changed, 73 insertions(+), 94 deletions(-) diff --git a/debug/binding_context b/debug/binding_context index 174d7c3..668bc90 100644 --- a/debug/binding_context +++ b/debug/binding_context @@ -1,11 +1,11 @@ #!/usr/bin/env zsh -source "${0:a:h}/../hooks/step-renewer.zsh" +source ${0:a:h}/../hooks/step-renewer.zsh function { typeset tmp=$(mktemp -d) { - STEPPATH="$tmp/step" process_binding_context './snapshot.json' + STEPPATH=$tmp/step process_binding_context './snapshot.json' } always { rm -rf "$tmp" } diff --git a/debug/config b/debug/config index aa4e4f6..792f80f 100755 --- a/debug/config +++ b/debug/config @@ -1,5 +1,5 @@ #!/usr/bin/env zsh -source "${0:a:h}/../hooks/config.zsh" +source ${0:a:h}/../hooks/config.zsh config diff --git a/debug/renew b/debug/renew index 0622b47..e8c4f00 100755 --- a/debug/renew +++ b/debug/renew @@ -1,20 +1,8 @@ #!/usr/bin/env zsh -source "${0:a:h}/../hooks/step-renewer.zsh" +source ${0:a:h}/../hooks/step-renewer.zsh function { - typeset namespace=${1:-} name=${2:-} - typeset tmp=$(mktemp -d) - { - set -- "${(QA@)${(z)$( - kubectl -n "$namespace" get secret "$name" -o json | jq -r '[ .data["tls.crt"], .data["tls.key"] ] | @sh' - )}}" - STEPPATH="$tmp/step" step ca bootstrap --force \ - --ca-url "$STEP_RENEWER_STEP_CA_URL" \ - --fingerprint "$STEP_RENEWER_STEP_CA_FINGERPRINT" > /dev/null 2>&1 || \ - abend 'unable to bootstrap step' - STEPPATH="$tmp/step" maybe_renew_certificate $name $namespace "$@" - } always { - [[ -d "$tmp" ]] && rm -rf "$tmp" - } + typeset namespace=${1:-} + renew_certificates <(kubectl -n "$namespace" get secrets -o json | jq '.items') } "$@" diff --git a/hooks/hook b/hooks/hook index c45b3ff..124b54e 100755 --- a/hooks/hook +++ b/hooks/hook @@ -1,7 +1,7 @@ #!/usr/bin/env zsh -source "${0:a:h}/step-renewer.zsh" -source "${0:a:h}/config.zsh" +source ${0:a:h}/step-renewer.zsh +source ${0:a:h}/config.zsh function { typeset config_map_name @@ -9,9 +9,9 @@ function { config else if [[ $STEP_RENEWER_UNSAFE_LOGGING = 1 ]]; then - cat "$BINDING_CONTEXT_PATH" + cat $BINDING_CONTEXT_PATH print fi - process_binding_context "$BINDING_CONTEXT_PATH" + process_binding_context $BINDING_CONTEXT_PATH fi } "$@" diff --git a/hooks/step-renewer.zsh b/hooks/step-renewer.zsh index 5651f02..c25381f 100644 --- a/hooks/step-renewer.zsh +++ b/hooks/step-renewer.zsh @@ -6,69 +6,51 @@ function abend { exit 1 } -function quotedoc { - typeset lines=() spaces=65536 leading='^( +)([^[:space:]])' IFS='' dedented match=() - while read -r line; do - lines+=("$line") - if [[ "$line" =~ $leading && "${#match[1]}" -lt "$spaces" ]]; then - spaces="${#match[1]}" - fi - done - read -r -d '' dedented < <(printf "%s\n" "${lines[@]}" | sed -E 's/^ {'$spaces'}//') - eval "$({ - print "cat < "$tmp/temp.key" - base64 -d <<< "$crt" > "$tmp/temp.crt" - [[ $STEP_RENEWER_DEBUG = 1 ]] && step certificate inspect "$tmp/temp.crt" - expires=$(step certificate inspect --format json "$tmp/temp.crt" | jq -r '.validity.end') - if step certificate needs-renewal --expires-in "$STEP_RENEWER_EXPIRES_IN" "$tmp/temp.crt" 2>/dev/null; then - print -- "secret=$namespace/$name expires=$expires status=renewing" - step certificate fingerprint "$tmp/temp.crt" - step ca renew --force "$tmp/temp.crt" "$tmp/temp.key" || abend 'unable to renew `%s/%s`.' $namespace $name - expires=$(step certificate inspect --format json "$tmp/temp.crt" | jq -r '.validity.end') - print -- "secret=$namespace/$name expires=$expires status=renewed" - jo data="$(jo $crt_name=@<(base64 -w 0 < $tmp/temp.crt))" > $tmp/patch.json - cat "$tmp/patch.json" - kubectl -n $namespace patch secret $name --patch-file "$tmp/patch.json" > /dev/null - else - print -- "secret=$namespace/$name expires=$expires status=okay" + typeset tmp=${1:-} name=${2:-} namespace=${3:-} tmp expires + shift 3 + while (( $# )); do + encoding=${1:-} crt_name=${2:-} crt=${3:-} key=${4:-} + shift 4 + base64 -d <<< "$crt" > $tmp/temp.crt + [[ $STEP_RENEWER_DEBUG = 1 ]] && step certificate inspect $tmp/temp.crt + expires=$(step certificate inspect --format json $tmp/temp.crt | jq -r '.validity.end') + if step certificate needs-renewal --expires-in $STEP_RENEWER_EXPIRES_IN $tmp/temp.crt 2>/dev/null; then + print -- "secret=$namespace/$name expires=$expires status=renewing" + step certificate fingerprint $tmp/temp.crt + if ! step ca renew --force $tmp/temp.crt <(base64 -d <<< $key); then + printf 'unable to renew `%s/%s`.\n' $namespace $name + continue fi - done - } always { - [[ -d "$tmp" ]] && rm -rf "$tmp" - } + expires=$(step certificate inspect --format json $tmp/temp.crt | jq -r '.validity.end') + print -- "secret=$namespace/$name expires=$expires status=renewed" + kubectl -n $namespace patch secret $name --patch-file =( + jo data="$(jo $crt_name=%$tmp/temp.crt)" + ) > /dev/null + else + print -- "secret=$namespace/$name expires=$expires status=okay" + fi + done } -function process_binding_context { - typeset process_binding=${1:-} - shift +function renew_certificates { [[ -n $STEP_RENEWER_STEP_CA_URL ]] || abend 'STEP_RENEWER_STEP_CA_URL is not set' [[ -n $STEP_RENEWER_STEP_CA_FINGERPRINT ]] || abend 'STEP_RENEWER_STEP_CA_FINGERPRINT is not set' - step ca bootstrap --force \ - --ca-url "$STEP_RENEWER_STEP_CA_URL" \ - --fingerprint "$STEP_RENEWER_STEP_CA_FINGERPRINT" > /dev/null 2>&1 || \ - abend 'unable to bootstrap step' - set -- "${(@QA)${(z)$( - jq -r ' + typeset input=${1:-} tmp name count certificates=() + set -- "${(QA@)${(z)$(jq -r ' [ - .[0].snapshots.kubernetes[] | - .object as $root | + .[] | + select(.metadata.labels["flatheadmill.github.io"] == "step-renewer") | + . as $root | [[ - if (.object.metadata.annotations | has("flatheadmill.github.io/pairs")) - then .object.metadata.annotations["flatheadmill.github.io/pairs"] + if (.metadata.annotations | has("flatheadmill.github.io/step-renewer/pairs")) + then .metadata.annotations["flatheadmill.github.io/step-renewer/pairs"] else "tls.crt/tls.key/pem" end | split(":")[] | split("/") | { @@ -79,23 +61,32 @@ function process_binding_context { ][] | ( .type, .crt, - (.crt as $crt | if (.crt != "" and $root.data | has($crt)) then $root.data[.crt] else "" end), - (.key as $key | if (.key != "" and $root.data | has($key)) then $root.data[.key] else "" end) + (.crt as $crt | if ($root.data | has($crt)) then $root.data[.crt] else "" end), + (.key as $key | if ($root.data | has($key)) then $root.data[.key] else "" end) )] as $certficates | - ( - .object.metadata.name, - .object.metadata.namespace, - $certficates | length, - $certficates[] - ) - ] | flatten | @sh' < $process_binding - )}}" - typeset name namespace count certificates=() - while $(( #@ )); do - name=${1:-} namespace=${2:-} count=${3:-} - shift 3 - certificates=( "${@[1,$count]}" ) - shift $count - maybe_renew_certificates $name $namespace "${(@)certificates}" - done + (.metadata.name, .metadata.namespace, ($certficates | length), $certficates[]) + ] | @sh + ' < $input)}}" + tmp=$(mktemp -d) || abend 'cannot create temporary directory' + { + STEPPATH=$tmp/step step ca bootstrap --force \ + --ca-url "$STEP_RENEWER_STEP_CA_URL" \ + --fingerprint "$STEP_RENEWER_STEP_CA_FINGERPRINT" > /dev/null 2>&1 || \ + abend 'unable to bootstrap step' + while (( $# )); do + name=${1:-} namespace=${2:-} count=${3:-} + shift 3 + certificates=( "$@[1,$count]" ) + shift $count + STEPPATH=$tmp/step maybe_renew_certificate $tmp $name $namespace "${(@)certificates}" + done + } always { + [[ -d $tmp ]] && rm -rf $tmp + } +} + +function process_binding_context { + typeset process_binding=${1:-} + shift + renew_certificates <(jq '.[0].snasphots.kubernetes' <<< $process_binding) } From d9d17308bf4ee99494930ee7eb7ffc5734b17042 Mon Sep 17 00:00:00 2001 From: flatheadmill Date: Thu, 25 Apr 2024 21:02:14 -0500 Subject: [PATCH 32/39] Fix feed into `renew_certificates` function. --- debug/binding_context | 4 ++-- hooks/step-renewer.zsh | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/debug/binding_context b/debug/binding_context index 668bc90..3dd95e8 100644 --- a/debug/binding_context +++ b/debug/binding_context @@ -3,9 +3,9 @@ source ${0:a:h}/../hooks/step-renewer.zsh function { - typeset tmp=$(mktemp -d) + typeset snapshot=${1:-} tmp=$(mktemp -d) { - STEPPATH=$tmp/step process_binding_context './snapshot.json' + STEPPATH=$tmp/step process_binding_context $snapshot } always { rm -rf "$tmp" } diff --git a/hooks/step-renewer.zsh b/hooks/step-renewer.zsh index c25381f..f991b08 100644 --- a/hooks/step-renewer.zsh +++ b/hooks/step-renewer.zsh @@ -13,16 +13,24 @@ function abend { # directory make the code easier to read. function maybe_renew_certificate { - typeset tmp=${1:-} name=${2:-} namespace=${3:-} tmp expires + typeset tmp=${1:-} name=${2:-} namespace=${3:-} expires shift 3 while (( $# )); do encoding=${1:-} crt_name=${2:-} crt=${3:-} key=${4:-} shift 4 + print -- "secret=$namespace/$name certificate=$crt_name encoding=$encoding status=visiting" base64 -d <<< "$crt" > $tmp/temp.crt + print -- --- + cat $tmp/temp.crt + print -- --- + if ! expires=$(step certificate inspect --format json $tmp/temp.crt | jq -r '.validity.end'); then + printf 'unable to inspect certificate.' + continue + fi [[ $STEP_RENEWER_DEBUG = 1 ]] && step certificate inspect $tmp/temp.crt - expires=$(step certificate inspect --format json $tmp/temp.crt | jq -r '.validity.end') + print there if step certificate needs-renewal --expires-in $STEP_RENEWER_EXPIRES_IN $tmp/temp.crt 2>/dev/null; then - print -- "secret=$namespace/$name expires=$expires status=renewing" + print -- "secret=$namespace/$name certificate=$crt_name encoding=$encoding expires=$expires status=renewed" step certificate fingerprint $tmp/temp.crt if ! step ca renew --force $tmp/temp.crt <(base64 -d <<< $key); then printf 'unable to renew `%s/%s`.\n' $namespace $name @@ -34,7 +42,7 @@ function maybe_renew_certificate { jo data="$(jo $crt_name=%$tmp/temp.crt)" ) > /dev/null else - print -- "secret=$namespace/$name expires=$expires status=okay" + print -- "secret=$namespace/$name certificate=$crt_name encoding=$encoding expires=$expires status=okay" fi done } @@ -88,5 +96,6 @@ function renew_certificates { function process_binding_context { typeset process_binding=${1:-} shift - renew_certificates <(jq '.[0].snasphots.kubernetes' <<< $process_binding) + print $process_binding + renew_certificates <(jq '[ .[0].snapshots.kubernetes[] | .object ]' < $process_binding) } From 14efeb318c2dadcad4f92b1859e360ed2eb49cc6 Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Fri, 26 Apr 2024 13:05:16 +0000 Subject: [PATCH 33/39] Remove debug printing. --- hooks/step-renewer.zsh | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/hooks/step-renewer.zsh b/hooks/step-renewer.zsh index f991b08..1502140 100644 --- a/hooks/step-renewer.zsh +++ b/hooks/step-renewer.zsh @@ -18,29 +18,23 @@ function maybe_renew_certificate { while (( $# )); do encoding=${1:-} crt_name=${2:-} crt=${3:-} key=${4:-} shift 4 - print -- "secret=$namespace/$name certificate=$crt_name encoding=$encoding status=visiting" base64 -d <<< "$crt" > $tmp/temp.crt - print -- --- - cat $tmp/temp.crt - print -- --- + expires=$(step certificate inspect --format json $tmp/temp.crt | jq -r '.validity.end') if ! expires=$(step certificate inspect --format json $tmp/temp.crt | jq -r '.validity.end'); then - printf 'unable to inspect certificate.' + print -- "secret=$namespace/$name certificate=$crt_name encoding=$encoding status=invalid" continue + else + print -- "secret=$namespace/$name certificate=$crt_name encoding=$encoding expires=$expires status=visiting" fi [[ $STEP_RENEWER_DEBUG = 1 ]] && step certificate inspect $tmp/temp.crt - print there if step certificate needs-renewal --expires-in $STEP_RENEWER_EXPIRES_IN $tmp/temp.crt 2>/dev/null; then - print -- "secret=$namespace/$name certificate=$crt_name encoding=$encoding expires=$expires status=renewed" - step certificate fingerprint $tmp/temp.crt if ! step ca renew --force $tmp/temp.crt <(base64 -d <<< $key); then printf 'unable to renew `%s/%s`.\n' $namespace $name continue fi expires=$(step certificate inspect --format json $tmp/temp.crt | jq -r '.validity.end') - print -- "secret=$namespace/$name expires=$expires status=renewed" - kubectl -n $namespace patch secret $name --patch-file =( - jo data="$(jo $crt_name=%$tmp/temp.crt)" - ) > /dev/null + kubectl -n $namespace patch secret $name --patch-file =(jo data="$(jo $crt_name=%$tmp/temp.crt)") > /dev/null + print -- "secret=$namespace/$name certificate=$crt_name encoding=$encoding expires=$expires status=renewed" else print -- "secret=$namespace/$name certificate=$crt_name encoding=$encoding expires=$expires status=okay" fi @@ -96,6 +90,5 @@ function renew_certificates { function process_binding_context { typeset process_binding=${1:-} shift - print $process_binding renew_certificates <(jq '[ .[0].snapshots.kubernetes[] | .object ]' < $process_binding) } From f36c0404acece5a2554651f0872174aa9a975814 Mon Sep 17 00:00:00 2001 From: flatheadmill Date: Fri, 26 Apr 2024 09:42:41 -0500 Subject: [PATCH 34/39] Fix annotation. You are only allowed one slash, so we placed our qualifier in hostname part. --- hooks/step-renewer.zsh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hooks/step-renewer.zsh b/hooks/step-renewer.zsh index 1502140..6b06ec8 100644 --- a/hooks/step-renewer.zsh +++ b/hooks/step-renewer.zsh @@ -51,8 +51,8 @@ function renew_certificates { select(.metadata.labels["flatheadmill.github.io"] == "step-renewer") | . as $root | [[ - if (.metadata.annotations | has("flatheadmill.github.io/step-renewer/pairs")) - then .metadata.annotations["flatheadmill.github.io/step-renewer/pairs"] + if (.metadata.annotations | has("step-renewer.flatheadmill.github.io/pairs")) + then .metadata.annotations["step-renewer.flatheadmill.github.io/pairs"] else "tls.crt/tls.key/pem" end | split(":")[] | split("/") | { From a75118ed9f8efd81f61340c0e336e9400530e579 Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Fri, 26 Apr 2024 15:08:29 +0000 Subject: [PATCH 35/39] Adjust annotation again. --- hooks/step-renewer.zsh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hooks/step-renewer.zsh b/hooks/step-renewer.zsh index 6b06ec8..724ce4b 100644 --- a/hooks/step-renewer.zsh +++ b/hooks/step-renewer.zsh @@ -51,8 +51,8 @@ function renew_certificates { select(.metadata.labels["flatheadmill.github.io"] == "step-renewer") | . as $root | [[ - if (.metadata.annotations | has("step-renewer.flatheadmill.github.io/pairs")) - then .metadata.annotations["step-renewer.flatheadmill.github.io/pairs"] + if (.metadata.annotations | has("flatheadmill.github.io/step-renewer.pairs")) + then .metadata.annotations["flatheadmill.github.io/step-renewer.pairs"] else "tls.crt/tls.key/pem" end | split(":")[] | split("/") | { From c5cb4216df647d0d0aa42a7aab39745efea56a24 Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Fri, 26 Apr 2024 15:10:06 +0000 Subject: [PATCH 36/39] Adjust label. --- hooks/config.zsh | 2 +- hooks/step-renewer.zsh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hooks/config.zsh b/hooks/config.zsh index a730445..c9fdfee 100644 --- a/hooks/config.zsh +++ b/hooks/config.zsh @@ -11,7 +11,7 @@ function config { kind=Secret \ labelSelector="$( jo matchLabels="$( - jo 'flatheadmill.github.io=step-renewer' + jo 'flatheadmill.github.io/step-renewer=true' )" )" \ executeHookOnEvent='[]' \ diff --git a/hooks/step-renewer.zsh b/hooks/step-renewer.zsh index 724ce4b..d05b082 100644 --- a/hooks/step-renewer.zsh +++ b/hooks/step-renewer.zsh @@ -48,7 +48,7 @@ function renew_certificates { set -- "${(QA@)${(z)$(jq -r ' [ .[] | - select(.metadata.labels["flatheadmill.github.io"] == "step-renewer") | + select(.metadata.labels["flatheadmill.github.io/step-renewer"] == "true") | . as $root | [[ if (.metadata.annotations | has("flatheadmill.github.io/step-renewer.pairs")) From f4a2fa5b78050ce4bb05f743a932dbd08f7017ee Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Mon, 13 May 2024 19:46:30 +0000 Subject: [PATCH 37/39] Fix step renewer configuration error. --- hooks/config.zsh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/config.zsh b/hooks/config.zsh index c9fdfee..3b6ee60 100644 --- a/hooks/config.zsh +++ b/hooks/config.zsh @@ -11,7 +11,7 @@ function config { kind=Secret \ labelSelector="$( jo matchLabels="$( - jo 'flatheadmill.github.io/step-renewer=true' + jo -- -s 'flatheadmill.github.io/step-renewer=true' )" )" \ executeHookOnEvent='[]' \ From 9926289aeb956a181002f2f92630c05c6ff5675d Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Wed, 5 Mar 2025 23:04:52 -0600 Subject: [PATCH 38/39] Label presence only, tidy Zsh. --- Dockerfile | 5 +---- hooks/config.zsh | 2 +- hooks/hook | 4 ++-- hooks/step-renewer.zsh | 8 ++++---- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5bd4590..5000de6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,4 @@ # syntax=docker/dockerfile:1.3-labs FROM ghcr.io/flant/shell-operator:latest -RUN < /dev/null 2>&1 || \ + --fingerprint "$STEP_RENEWER_STEP_CA_FINGERPRINT" > /dev/null 2>&1 || abend 'unable to bootstrap step' while (( $# )); do name=${1:-} namespace=${2:-} count=${3:-} From 9d8c4a4993eb7747236ae5ec40d177a793674652 Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Fri, 7 Mar 2025 11:13:48 -0600 Subject: [PATCH 39/39] Fix label selector. --- hooks/step-renewer.zsh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hooks/step-renewer.zsh b/hooks/step-renewer.zsh index e794068..55975c1 100644 --- a/hooks/step-renewer.zsh +++ b/hooks/step-renewer.zsh @@ -21,10 +21,10 @@ function maybe_renew_certificate { base64 -d <<< "$crt" > $tmp/temp.crt expires=$(step certificate inspect --format json $tmp/temp.crt | jq -r '.validity.end') if ! expires=$(step certificate inspect --format json $tmp/temp.crt | jq -r '.validity.end'); then - print -- "secret=$namespace/$name certificate=$crt_name encoding=$encoding status=invalid" + print -- "secret=$namespace/$name certificate=$crt_name encoding=$encoding message=invalid" continue else - print -- "secret=$namespace/$name certificate=$crt_name encoding=$encoding expires=$expires status=visiting" + print -- "secret=$namespace/$name certificate=$crt_name encoding=$encoding expires=$expires message=visiting" fi [[ $STEP_RENEWER_DEBUG = 1 ]] && step certificate inspect $tmp/temp.crt if step certificate needs-renewal --expires-in $STEP_RENEWER_EXPIRES_IN $tmp/temp.crt 2>/dev/null; then @@ -34,9 +34,9 @@ function maybe_renew_certificate { fi expires=$(step certificate inspect --format json $tmp/temp.crt | jq -r '.validity.end') kubectl -n $namespace patch secret $name --patch-file =(jo data="$(jo $crt_name=%$tmp/temp.crt)") > /dev/null - print -- "secret=$namespace/$name certificate=$crt_name encoding=$encoding expires=$expires status=renewed" + print -- "secret=$namespace/$name certificate=$crt_name encoding=$encoding expires=$expires message=renewed" else - print -- "secret=$namespace/$name certificate=$crt_name encoding=$encoding expires=$expires status=okay" + print -- "secret=$namespace/$name certificate=$crt_name encoding=$encoding expires=$expires message=okay" fi done } @@ -48,7 +48,7 @@ function renew_certificates { set -- "${(QA@)${(z)$(jq -r ' [ .[] | - select(.metadata.labels["flatheadmill.github.io/step-renewer"] == "true") | + select(.metadata.labels["flatheadmill.github.io/step-renewer"] == "") | . as $root | [[ if (.metadata.annotations | has("flatheadmill.github.io/step-renewer.pairs"))