From 863bcd4883e262725df42569570f734e0d4dea8f Mon Sep 17 00:00:00 2001 From: Daniel Sy Date: Mon, 8 Jun 2026 13:12:09 +0200 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E2=9C=A8=20add=20secrets-backup?= =?UTF-8?q?=20CronJob=20as=20ArgoCD=20Application?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backs up critical K8s secrets (argocd, cert-manager, external-secrets) to OBS. Uses template variables for environment-specific values. Ref: IPCEICIS-9317 --- template/stacks/core/secrets-backup.yaml | 23 ++++ .../manifests/secrets-backup-cronjob.yaml | 124 ++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 template/stacks/core/secrets-backup.yaml create mode 100644 template/stacks/core/secrets-backup/manifests/secrets-backup-cronjob.yaml diff --git a/template/stacks/core/secrets-backup.yaml b/template/stacks/core/secrets-backup.yaml new file mode 100644 index 0000000..08947ae --- /dev/null +++ b/template/stacks/core/secrets-backup.yaml @@ -0,0 +1,23 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: secrets-backup + namespace: argocd + labels: + env: dev +spec: + project: default + syncPolicy: + automated: + selfHeal: true + syncOptions: + - CreateNamespace=true + retry: + limit: -1 + destination: + name: in-cluster + namespace: gitea + sources: + - repoURL: https://{{{ .Env.CLIENT_REPO_DOMAIN }}}/{{{ .Env.CLIENT_REPO_ORG_NAME }}} + targetRevision: HEAD + path: "{{{ .Env.CLIENT_REPO_ID }}}/{{{ .Env.DOMAIN }}}/stacks/core/secrets-backup/manifests" diff --git a/template/stacks/core/secrets-backup/manifests/secrets-backup-cronjob.yaml b/template/stacks/core/secrets-backup/manifests/secrets-backup-cronjob.yaml new file mode 100644 index 0000000..aa3b1bd --- /dev/null +++ b/template/stacks/core/secrets-backup/manifests/secrets-backup-cronjob.yaml @@ -0,0 +1,124 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: secrets-backup + namespace: gitea +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: secrets-backup-reader +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list"] + - apiGroups: [""] + resources: ["namespaces"] + verbs: ["get", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: secrets-backup-reader +subjects: + - kind: ServiceAccount + name: secrets-backup + namespace: gitea +roleRef: + kind: ClusterRole + name: secrets-backup-reader + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: v1 +kind: Secret +metadata: + name: secrets-backup-config + namespace: gitea +type: Opaque +stringData: + encryption-passphrase: "{{{ .Env.BACKUP_ENCRYPTION_KEY }}}" +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: secrets-backup + namespace: gitea +spec: + schedule: "30 3 * * *" + concurrencyPolicy: "Forbid" + successfulJobsHistoryLimit: 5 + failedJobsHistoryLimit: 5 + startingDeadlineSeconds: 600 # 10 minutes + jobTemplate: + spec: + activeDeadlineSeconds: 900 + backoffLimit: 2 + ttlSecondsAfterFinished: 259200 + template: + spec: + serviceAccountName: secrets-backup + containers: + - name: secrets-backup + image: alpine/k8s:1.28.0 + imagePullPolicy: IfNotPresent + env: + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: forgejo-cloud-credentials + key: access-key + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: forgejo-cloud-credentials + key: secret-key + - name: ENCRYPTION_PASSPHRASE + valueFrom: + secretKeyRef: + name: secrets-backup-config + key: encryption-passphrase + - name: BACKUP_BUCKET + value: "{{{ .Env.BACKUP_BUCKET }}}" + - name: OBS_ENDPOINT + value: "{{{ .Env.OBS_ENDPOINT }}}" + command: + - /bin/sh + - -c + - | + set -euo pipefail + + TIMESTAMP=$(date +%Y%m%d-%H%M%S) + BACKUP_DIR="/tmp/secrets-backup-${TIMESTAMP}" + NAMESPACES="argocd cert-manager external-secrets" + + mkdir -p "${BACKUP_DIR}" + + echo "=== Exporting secrets from critical namespaces ===" + for NS in ${NAMESPACES}; do + echo "Exporting namespace: ${NS}" + kubectl get secrets -n "${NS}" \ + -o json \ + --field-selector type!=kubernetes.io/service-account-token \ + > "${BACKUP_DIR}/${NS}-secrets.json" + done + + echo "=== Encrypting backup with AES-256-CBC ===" + ARCHIVE="${BACKUP_DIR}/secrets-backup-${TIMESTAMP}.tar.gz" + tar -czf "${ARCHIVE}" -C "${BACKUP_DIR}" \ + $(ls "${BACKUP_DIR}"/*.json 2>/dev/null | xargs -n1 basename) + + ENCRYPTED="${BACKUP_DIR}/secrets-backup-${TIMESTAMP}.tar.gz.enc" + openssl enc -aes-256-cbc -salt -pbkdf2 -iter 100000 \ + -in "${ARCHIVE}" \ + -out "${ENCRYPTED}" \ + -pass env:ENCRYPTION_PASSPHRASE + + echo "=== Uploading to OBS ===" + aws s3 cp "${ENCRYPTED}" \ + "s3://${BACKUP_BUCKET}/secrets-backup/${TIMESTAMP}/secrets-backup.tar.gz.enc" \ + --endpoint-url "https://${OBS_ENDPOINT}" + + echo "=== Cleanup ===" + rm -rf "${BACKUP_DIR}" + echo "Backup completed: ${TIMESTAMP}" + restartPolicy: OnFailure