Add encrypted off-cluster etcd backups

This commit is contained in:
2026-09-04 09:45:16 +02:00
parent e01c1c11d9
commit 7f6073eeb8
3 changed files with 327 additions and 0 deletions
@@ -0,0 +1,36 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: naviya-etcd-backups-offsite
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
mountOptions:
- hard
- nfsvers=4.2
- timeo=600
- retrans=2
- nosuid
- nodev
- noexec
nfs:
server: 172.16.2.252
path: /Volume1/Backup/naviya-kubernetes/etcd
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: naviya-etcd-backups-offsite
namespace: kube-system
spec:
accessModes:
- ReadWriteMany
storageClassName: ""
volumeName: naviya-etcd-backups-offsite
resources:
requests:
storage: 10Gi
+285
View File
@@ -0,0 +1,285 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: naviya-etcd-backup
namespace: kube-system
spec:
schedule: "17 3 * * *"
timeZone: Etc/UTC
concurrencyPolicy: Forbid
startingDeadlineSeconds: 3600
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 1
activeDeadlineSeconds: 1800
ttlSecondsAfterFinished: 86400
template:
metadata:
labels:
app.kubernetes.io/name: naviya-etcd-backup
spec:
automountServiceAccountToken: false
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
restartPolicy: Never
nodeSelector:
node-role.kubernetes.io/control-plane: ""
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
securityContext:
seccompProfile:
type: RuntimeDefault
initContainers:
- name: snapshot
image: registry.k8s.io/etcd:3.6.8-0
imagePullPolicy: IfNotPresent
command:
- etcdctl
args:
- --endpoints=https://127.0.0.1:2379
- --cacert=/etcd-certs/ca.crt
- --cert=/etcd-certs/healthcheck-client.crt
- --key=/etcd-certs/healthcheck-client.key
- snapshot
- save
- /work/snapshot.db
securityContext:
runAsUser: 0
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: etcd-certs
mountPath: /etcd-certs
readOnly: true
- name: work
mountPath: /work
- name: validate
image: registry.k8s.io/etcd:3.6.8-0
imagePullPolicy: IfNotPresent
command:
- etcdutl
args:
- --write-out=table
- snapshot
- status
- /work/snapshot.db
securityContext:
runAsUser: 0
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: work
mountPath: /work
readOnly: true
containers:
- name: encrypt
image: docker.io/alpine/openssl@sha256:61cec9c1f221755bf995f1f309211b222164599e2d1943b666f878ef644d3a0e
imagePullPolicy: IfNotPresent
command: ["sh", "-ec"]
args:
- |
umask 077
stamp="$(date -u +%Y%m%dT%H%M%SZ)"
name="naviya-etcd-${stamp}-${NODE_NAME}.db.enc"
target="/offsite/daily/${name}"
tmp="${target}.tmp.$$"
checksum="${target}.sha256"
checksum_tmp="${checksum}.tmp.$$"
verification="/work/verification.db"
cleanup() {
rm -f \
"$tmp" \
"$checksum_tmp" \
"$verification"
}
trap cleanup EXIT
openssl enc \
-aes-256-cbc \
-salt \
-pbkdf2 \
-iter 600000 \
-md sha256 \
-in /work/snapshot.db \
-out "$tmp" \
-pass file:/key/passphrase
test -s "$tmp"
openssl enc \
-d \
-aes-256-cbc \
-pbkdf2 \
-iter 600000 \
-md sha256 \
-in "$tmp" \
-out "$verification" \
-pass file:/key/passphrase
snapshot_sha="$(sha256sum /work/snapshot.db |
awk '{print $1}')"
verification_sha="$(sha256sum "$verification" |
awk '{print $1}')"
test "$snapshot_sha" = "$verification_sha"
rm -f "$verification"
echo "Chiffrement et déchiffrement de contrôle : OK"
mv "$tmp" "$target"
encrypted_sha="$(sha256sum "$target" |
awk '{print $1}')"
printf '%s %s\n' \
"$encrypted_sha" \
"$name" \
> "$checksum_tmp"
mv "$checksum_tmp" "$checksum"
(
cd /offsite/daily
sha256sum -c "$(basename "$checksum")"
)
echo "Snapshot etcd quotidien validé : $target"
copy_tmp=""
copy_checksum_tmp=""
cleanup() {
rm -f \
"$tmp" \
"$checksum_tmp" \
"$verification"
test -z "$copy_tmp" ||
rm -f "$copy_tmp"
test -z "$copy_checksum_tmp" ||
rm -f "$copy_checksum_tmp"
}
copy_archive() {
tier="$1"
tier_dir="/offsite/${tier}"
tier_target="${tier_dir}/${name}"
copy_tmp="${tier_target}.tmp.$$"
tier_checksum="${tier_target}.sha256"
copy_checksum_tmp="${tier_checksum}.tmp.$$"
cp "$target" "$copy_tmp"
copy_sha="$(sha256sum "$copy_tmp" |
awk '{print $1}')"
test "$copy_sha" = "$encrypted_sha"
mv "$copy_tmp" "$tier_target"
printf '%s %s\n' \
"$encrypted_sha" \
"$name" \
> "$copy_checksum_tmp"
mv \
"$copy_checksum_tmp" \
"$tier_checksum"
(
cd "$tier_dir"
sha256sum -c "$(basename "$tier_checksum")"
)
copy_tmp=""
copy_checksum_tmp=""
echo "Copie ${tier} validée : $tier_target"
}
if test "$(date -u +%u)" = "7"; then
copy_archive weekly
fi
if test "$(date -u +%d)" = "01"; then
copy_archive monthly
fi
find /offsite/daily \
-type f \
\( -name '*.db.enc' -o -name '*.db.enc.sha256' \) \
-mtime +14 \
-delete
find /offsite/weekly \
-type f \
\( -name '*.db.enc' -o -name '*.db.enc.sha256' \) \
-mtime +56 \
-delete
find /offsite/monthly \
-type f \
\( -name '*.db.enc' -o -name '*.db.enc.sha256' \) \
-mtime +370 \
-delete
find /offsite \
-type f \
-name '*.tmp.*' \
-mtime +1 \
-delete
trap - EXIT
echo "Sauvegarde etcd chiffrée terminée avec succès"
env:
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
resources:
requests:
cpu: 25m
memory: 32Mi
limits:
cpu: 500m
memory: 256Mi
securityContext:
runAsUser: 0
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: work
mountPath: /work
- name: offsite
mountPath: /offsite
- name: encryption-key
mountPath: /key
readOnly: true
volumes:
- name: work
emptyDir: {}
- name: etcd-certs
hostPath:
path: /etc/kubernetes/pki/etcd
type: Directory
- name: offsite
persistentVolumeClaim:
claimName: naviya-etcd-backups-offsite
- name: encryption-key
secret:
secretName: naviya-etcd-backup-encryption
defaultMode: 0400
@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- 10-storage.yaml
- 20-cronjob.yaml