From 29afcae52ee52a671ed28a8069a22a7d394a1685 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Sat, 17 Feb 2024 19:37:12 -0600 Subject: [PATCH] fetchcert: Deploy tool to get cert from k8s Secret The `fetchcert` tool is a short shell script that fetches an X.509 certificate and corresponding private key from a Kubernetes Secret, using the Kubernetes API. I originally wrote it for the Frigate server so it could fetch the _pyrocufflink.blue_ wildcard certificate, which is managed by _cert-manager_. Since then, I have adapted it to be more generic, so it will be useful to fetch the _loki.pyrocufflink.blue_ certificate for Grafana Loki. Although the script is rather simple, it does have several required configuration parameters. It needs to know the URL of the Kubernetes API server and have the certificate for the CA that signs the server certificate, as well as an authorization token. It also needs to know the namespace and name of the Secret from which it will fetch the certificate and private key. Finally, needs to know the paths to the files where the fetched data will be written. Generally, after certificates are updated, some action needs to be performed in order to make use of them. This typically involves restarting or reloading a daemon. Since the `fetchcert` tool runs in a container, it can't directly perform those actions, so it simply indicates via a special exit code that the certificate has been updated and some further action may be needed. The `/etc/fetchcert/postupdate.sh` script is executed by _systemd_ after `fetchcert` finishes. If the `EXIT_STATUS` environment variable (which is set by _systemd_ to the return code of the main service process) matches the expected code, the configured post-update actions will be executed. --- app/fetchcert/fetchcert.cue | 12 ++++++++ app/fetchcert/templates.cue | 39 +++++++++++++++++++++++++ env/prod/fetchcert.cue | 29 ++++++++++++++++++ templates/fetchcert/ca.crt | 1 + templates/fetchcert/fetchcert.container | 22 ++++++++++++++ templates/fetchcert/fetchcert.timer | 9 ++++++ templates/fetchcert/postupdate.sh | 8 +++++ templates/fetchcert/token | 1 + 8 files changed, 121 insertions(+) create mode 100644 app/fetchcert/fetchcert.cue create mode 100644 app/fetchcert/templates.cue create mode 100644 env/prod/fetchcert.cue create mode 100644 templates/fetchcert/ca.crt create mode 100644 templates/fetchcert/fetchcert.container create mode 100644 templates/fetchcert/fetchcert.timer create mode 100644 templates/fetchcert/postupdate.sh create mode 100644 templates/fetchcert/token diff --git a/app/fetchcert/fetchcert.cue b/app/fetchcert/fetchcert.cue new file mode 100644 index 0000000..4e29703 --- /dev/null +++ b/app/fetchcert/fetchcert.cue @@ -0,0 +1,12 @@ +package fetchcert + +#Fetchcert: { + ca_cert: string + kubernetes_url: string + token: string + namespace: string + secret: string + cert: string + key: string + postupdate?: string +} diff --git a/app/fetchcert/templates.cue b/app/fetchcert/templates.cue new file mode 100644 index 0000000..d14d236 --- /dev/null +++ b/app/fetchcert/templates.cue @@ -0,0 +1,39 @@ +package fetchcert + +import "du5t1n.me/cfg/base/schema/instructions" + +templates: [...instructions.#RenderInstruction] & [ + { + template: "fetchcert/ca.crt" + dest: "/etc/fetchcert/ca.crt" + }, + { + template: "fetchcert/token" + dest: "/etc/fetchcert/token" + }, + { + template: "fetchcert/postupdate.sh" + dest: "/etc/fetchcert/postupdate.sh" + mode: "u=rwx,go=rx" + }, + { + template: "fetchcert/fetchcert.container" + dest: "/etc/containers/systemd/fetchcert.container" + hooks: { + changed: [ + {run: "systemctl daemon-reload", immediate: true}, + ] + } + }, + { + template: "fetchcert/fetchcert.timer" + dest: "/etc/systemd/system/fetchcert.timer" + hooks: { + changed: [ + {run: "systemctl daemon-reload", immediate: true}, + {run: "systemctl enable fetchcert.timer"}, + {run: "systemctl start fetchcert.timer"}, + ] + } + }, +] diff --git a/env/prod/fetchcert.cue b/env/prod/fetchcert.cue new file mode 100644 index 0000000..4a7dee1 --- /dev/null +++ b/env/prod/fetchcert.cue @@ -0,0 +1,29 @@ +package prod + +import f "du5t1n.me/cfg/app/fetchcert" + +fetchcert: base: f.#Fetchcert & { + ca_cert: """ + -----BEGIN CERTIFICATE----- + MIIC/jCCAeagAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl + cm5ldGVzMB4XDTIyMDgwMTAyNTUzM1oXDTMyMDcyOTAyNTUzM1owFTETMBEGA1UE + AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMs6 + 2PUOzIClsAgPv1Mn9CTwzSFMntAn7OppwK5BQ4E5Vd1yMjz3p0uA1ZINv1ORorG0 + mLl95C7y+CWUGPx+stHKQr/40sLGyypbX+AfjoPzHiDbIcbZEff8X5RwKqzmT9V7 + Yt29KewADod0z+fqNYa62MJDaUunfwaV8kKFU/WJM8IKv2eJxAtWzvK3iHAFhx0j + Xo4TlyINL9V9UMKLf12w6CA3G41uZIBCN3G7aJEm++eGoMdrPZUXlbCpbSztO85/ + hbulVs+0hCIxWiI+mRmB5OoWlRYL4jA45oK/RtpEqSwZ95zlGNAChmH7rb0pTtNf + N0/C2wKAEL4POLx9kscCAwEAAaNZMFcwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB + /wQFMAMBAf8wHQYDVR0OBBYEFHYActCjEWdtsA+Ju25gxJh/vaLQMBUGA1UdEQQO + MAyCCmt1YmVybmV0ZXMwDQYJKoZIhvcNAQELBQADggEBAAfkYHecXUwyqvMSXmqr + ETqEzDCBini14s89VDhaDHOXBID9TKMVyeePdEYcPAJz3wo8fbx/+TL37K6hEuo+ + 7bUaamaumznsjg9L0Hth19GvuRKMXJlEpndRmE5K9hnaDLr94MLg9n1qGcEOt6tw + O6X5qqHf9AuuL39vt1+kSw6PeZZFZNMDZ8BdiTssw4btjQ2bsWu0wSiOSz/F8iRf + 2vN5An5dheroDsFs4dZ9gnJ69TmqV1YqQxfRWqCxzfNJbgVm6AoBPwhL1JRuAU4N + 3nCNoM9n2tLFDojT4un1778UVU91PtcBVdM9Nq+RC2jhXIyLBqsEK0ofOqFYqj3F + 0EQ= + -----END CERTIFICATE----- + """ + kubernetes_url: "https://kubernetes.pyrocufflink.blue:6443" + namespace: "dch-ca" +} diff --git a/templates/fetchcert/ca.crt b/templates/fetchcert/ca.crt new file mode 100644 index 0000000..168b239 --- /dev/null +++ b/templates/fetchcert/ca.crt @@ -0,0 +1 @@ +{{ fetchcert.ca_cert }} diff --git a/templates/fetchcert/fetchcert.container b/templates/fetchcert/fetchcert.container new file mode 100644 index 0000000..c9e541a --- /dev/null +++ b/templates/fetchcert/fetchcert.container @@ -0,0 +1,22 @@ +[Unit] +Description=Fetch HTTPS certificate from Kubernetes Secret API +Wants=network-online.target +After=network-online.target + +[Container] +Image=git.pyrocufflink.net/containerimages/fetchcert +Exec={{ fetchcert.namespace }} {{ fetchcert.secret }} /etc/fetchcert/certs/{{ fetchcert.key }} /etc/fetchcert/certs/{{ fetchcert.cert }} +ReadOnly=yes +ReadOnlyTmpfs=yes +Volume=/etc/fetchcert:/etc/fetchcert:ro +Volume=/etc/fetchcert/certs:/etc/fetchcert/certs:rw,z +Environment=KUBERNETES_URL={{ fetchcert.kubernetes_url }} +AddCapability=CAP_CHOWN +DropCapability=all +NoNewPrivileges=yes + +[Service] +Type=oneshot +SuccessExitStatus=20 +ExecStartPre=/bin/mkdir -p /etc/fetchcert/certs +ExecStopPost=-/etc/fetchcert/postupdate.sh diff --git a/templates/fetchcert/fetchcert.timer b/templates/fetchcert/fetchcert.timer new file mode 100644 index 0000000..489c582 --- /dev/null +++ b/templates/fetchcert/fetchcert.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Periodically fetch certificate from Kubernetes + +[Timer] +OnCalendar=*-*-* 0:0:0 +RandomizedDelaySec=8h + +[Install] +WantedBy=timers.target diff --git a/templates/fetchcert/postupdate.sh b/templates/fetchcert/postupdate.sh new file mode 100644 index 0000000..42b0e29 --- /dev/null +++ b/templates/fetchcert/postupdate.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +if [ ${EXIT_STATUS:-1} -ne 20 ]; then + exit 0 +fi +{% if fetchcert.postupdate %} +{{ fetchcert.postupdate }} +{% endif -%} diff --git a/templates/fetchcert/token b/templates/fetchcert/token new file mode 100644 index 0000000..fb5e66a --- /dev/null +++ b/templates/fetchcert/token @@ -0,0 +1 @@ +{{ fetchcert.token | decrypt }}