225 lines
6.8 KiB
Django/Jinja
225 lines
6.8 KiB
Django/Jinja
{% if tls_backup_enabled %}---
|
|
apiVersion: v1
|
|
kind: ServiceAccount
|
|
metadata:
|
|
name: tls-backup
|
|
namespace: {{ taskserv.namespace }}
|
|
---
|
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
kind: ClusterRole
|
|
metadata:
|
|
name: tls-backup-reader
|
|
rules:
|
|
- apiGroups: [""]
|
|
resources: ["secrets"]
|
|
verbs: ["get", "list"]
|
|
- apiGroups: ["cert-manager.io"]
|
|
resources: ["certificates", "clusterissuers"]
|
|
verbs: ["get", "list", "watch"]
|
|
---
|
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
kind: ClusterRoleBinding
|
|
metadata:
|
|
name: tls-backup-reader
|
|
roleRef:
|
|
apiGroup: rbac.authorization.k8s.io
|
|
kind: ClusterRole
|
|
name: tls-backup-reader
|
|
subjects:
|
|
- kind: ServiceAccount
|
|
name: tls-backup
|
|
namespace: {{ taskserv.namespace }}
|
|
---
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: restic-tls
|
|
namespace: {{ taskserv.namespace }}
|
|
type: Opaque
|
|
stringData:
|
|
RESTIC_REPOSITORY: "{{ restic_repository }}"
|
|
RESTIC_PASSWORD: "{{ restic_password }}"
|
|
AWS_ACCESS_KEY_ID: "{{ aws_access_key_id }}"
|
|
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_access_key }}"
|
|
AWS_DEFAULT_REGION: "{{ aws_default_region }}"
|
|
---
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: tls-backup-watcher
|
|
namespace: {{ taskserv.namespace }}
|
|
data:
|
|
watch.nu: |
|
|
#!/usr/bin/env nu
|
|
|
|
$env.PATH = ($env.PATH | append "/kubectl-bin")
|
|
|
|
const CERT_NS = "cert-manager"
|
|
|
|
def dump_all [] {
|
|
print "[tls-backup] dumping TLS secrets"
|
|
let r = (do { ^kubectl get secrets -A -l cert-manager.io/certificate-name -o yaml } | complete)
|
|
if $r.exit_code == 0 {
|
|
$r.stdout | save --force /dump/tls-secrets.yaml
|
|
} else {
|
|
print $"[tls-backup] warn: secrets dump failed: ($r.stderr | str trim)"
|
|
}
|
|
|
|
print "[tls-backup] dumping ACME keys"
|
|
let ri = (do { ^kubectl get clusterissuers -o json } | complete)
|
|
if $ri.exit_code == 0 {
|
|
$ri.stdout | from json | get -o items | default []
|
|
| each {|i| $i | get -o spec.acme.privateKeySecretRef.name | default ""}
|
|
| where {|k| $k | is-not-empty}
|
|
| each {|key|
|
|
let rk = (do { ^kubectl get secret -n $CERT_NS $key -o yaml } | complete)
|
|
if $rk.exit_code == 0 { $rk.stdout | save --force $"/dump/acme-key--($key).yaml" }
|
|
}
|
|
| ignore
|
|
}
|
|
|
|
"" | save --force /trigger/ready
|
|
print "[tls-backup] backup trigger written"
|
|
}
|
|
|
|
def process_event [line: string] {
|
|
let parts = ($line | split row ' ')
|
|
if ($parts | length) < 3 { return }
|
|
let evtype = ($parts | get 0? | default "")
|
|
let ns = ($parts | get 1? | default "")
|
|
let name = ($parts | get 2? | default "")
|
|
if $evtype != "MODIFIED" { return }
|
|
let fmt = 'jsonpath={.status.conditions[?(@.type=="Ready")].status}'
|
|
let r = (do { ^kubectl get certificate $name -n $ns -o $fmt } | complete)
|
|
if ($r.stdout | str trim) == "True" {
|
|
print $"[tls-backup] ($ns)/($name) Ready"
|
|
dump_all
|
|
}
|
|
}
|
|
|
|
print "[tls-backup] watcher started"
|
|
loop {
|
|
print "[tls-backup] watching certificates"
|
|
let watch_fmt = 'jsonpath={.type} {.object.metadata.namespace} {.object.metadata.name}{"\n"}'
|
|
try {
|
|
^kubectl get certificates -A -w --output-watch-events=true -o $watch_fmt
|
|
| lines
|
|
| where {|l| $l | str trim | is-not-empty}
|
|
| each {|line| process_event $line; null}
|
|
| ignore
|
|
} catch {|e|
|
|
print $"[tls-backup] watch ended: ($e.msg)"
|
|
}
|
|
sleep 10sec
|
|
}
|
|
---
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: tls-backup-watcher
|
|
namespace: {{ taskserv.namespace }}
|
|
spec:
|
|
replicas: 1
|
|
selector:
|
|
matchLabels:
|
|
app: tls-backup-watcher
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: tls-backup-watcher
|
|
spec:
|
|
serviceAccountName: tls-backup
|
|
initContainers:
|
|
- name: install-kubectl
|
|
image: alpine:3.21
|
|
command: ["/bin/sh", "-c"]
|
|
args:
|
|
- |
|
|
set -euo pipefail
|
|
apk add --no-cache curl
|
|
ARCH=$(uname -m)
|
|
case "$ARCH" in x86_64) ARCH=amd64 ;; aarch64|arm64) ARCH=arm64 ;; esac
|
|
curl -fsSL https://dl.k8s.io/release/{{ tls_backup_kubectl_version }}/bin/linux/${ARCH}/kubectl \
|
|
-o /kubectl-bin/kubectl
|
|
chmod +x /kubectl-bin/kubectl
|
|
echo "[tls-backup] kubectl installed (arch=${ARCH})"
|
|
volumeMounts:
|
|
- name: kubectl-bin
|
|
mountPath: /kubectl-bin
|
|
volumes:
|
|
- name: kubectl-bin
|
|
emptyDir: {}
|
|
- name: dump
|
|
emptyDir: {}
|
|
- name: trigger
|
|
emptyDir: {}
|
|
- name: scripts
|
|
configMap:
|
|
name: tls-backup-watcher
|
|
defaultMode: 0755
|
|
containers:
|
|
- name: watcher
|
|
image: {{ tls_backup_nushell_image }}
|
|
command: ["nu", "/scripts/watch.nu"]
|
|
volumeMounts:
|
|
- name: kubectl-bin
|
|
mountPath: /kubectl-bin
|
|
- name: dump
|
|
mountPath: /dump
|
|
- name: trigger
|
|
mountPath: /trigger
|
|
- name: scripts
|
|
mountPath: /scripts
|
|
- name: backup
|
|
image: restic/restic:0.17.3
|
|
env:
|
|
- name: RESTIC_REPOSITORY
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: restic-tls
|
|
key: RESTIC_REPOSITORY
|
|
- name: RESTIC_PASSWORD
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: restic-tls
|
|
key: RESTIC_PASSWORD
|
|
- name: AWS_ACCESS_KEY_ID
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: restic-tls
|
|
key: AWS_ACCESS_KEY_ID
|
|
- name: AWS_SECRET_ACCESS_KEY
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: restic-tls
|
|
key: AWS_SECRET_ACCESS_KEY
|
|
- name: AWS_DEFAULT_REGION
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: restic-tls
|
|
key: AWS_DEFAULT_REGION
|
|
volumeMounts:
|
|
- name: dump
|
|
mountPath: /dump
|
|
readOnly: true
|
|
- name: trigger
|
|
mountPath: /trigger
|
|
command: ["/bin/sh", "-c"]
|
|
args:
|
|
- |
|
|
set -euo pipefail
|
|
restic snapshots >/dev/null 2>&1 || restic init
|
|
echo "[tls-backup] backup container ready, watching for trigger"
|
|
while true; do
|
|
if [ -f /trigger/ready ]; then
|
|
echo "[tls-backup] trigger detected, running backup"
|
|
restic backup /dump \
|
|
--tag tls --tag workspace=libre-wuji \
|
|
--host libre-wuji-cluster
|
|
restic forget --keep-daily 14 --keep-weekly 8 --keep-monthly 12 --prune
|
|
rm -f /trigger/ready
|
|
echo "[tls-backup] backup complete"
|
|
fi
|
|
sleep 5
|
|
done
|
|
{% else %}# tls-backup: not enabled (set tls_backup.enabled = true in component config){% endif %}
|