# PostSync hook Job: creates a MinIO IAM user + policy scoped to only the # `paperless` bucket (least-privilege — reuses root creds nowhere else in the # cluster), then writes the generated access/secret key into a Secret in the # `paperless` namespace for the nightly backup CronJob to consume. # # Idempotent: re-running never rotates existing credentials — if # paperless-minio-creds already exists in ns paperless, the script reuses the # access key it already wrote and just re-asserts the policy/user exist. apiVersion: v1 kind: ServiceAccount metadata: name: minio-paperless-provisioner namespace: storage --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: minio-paperless-provisioner rules: - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list", "create", "update", "patch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: minio-paperless-provisioner namespace: paperless subjects: - kind: ServiceAccount name: minio-paperless-provisioner namespace: storage roleRef: kind: ClusterRole name: minio-paperless-provisioner apiGroup: rbac.authorization.k8s.io --- apiVersion: batch/v1 kind: Job metadata: name: minio-provision-paperless namespace: storage annotations: argocd.argoproj.io/hook: PostSync argocd.argoproj.io/hook-delete-policy: BeforeHookCreation spec: ttlSecondsAfterFinished: 600 backoffLimit: 3 template: spec: serviceAccountName: minio-paperless-provisioner restartPolicy: Never initContainers: - name: kubectl-copy image: bitnami/kubectl:latest command: ["sh", "-c", "cp $(which kubectl) /shared/kubectl"] volumeMounts: - name: shared mountPath: /shared containers: - name: provision image: minio/mc:latest volumeMounts: - name: shared mountPath: /shared - name: minio-creds mountPath: /minio-creds readOnly: true command: - /bin/sh - -c - | set -e export PATH="/shared:$PATH" # minio-creds ships as a shell-sourceable config.env blob # (`export MINIO_ROOT_USER=... / MINIO_ROOT_PASSWORD=...`), not # discrete keys — source it directly rather than re-parsing. . /minio-creds/config.env mc alias set m http://minio-cluster-hl.storage.svc.cluster.local:9000 \ "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" echo "Checking for existing paperless-minio-creds secret..." if kubectl -n paperless get secret paperless-minio-creds >/dev/null 2>&1; then ACCESS_KEY=$(kubectl -n paperless get secret paperless-minio-creds -o jsonpath='{.data.ACCESS_KEY}' | base64 -d) SECRET_KEY=$(kubectl -n paperless get secret paperless-minio-creds -o jsonpath='{.data.SECRET_KEY}' | base64 -d) echo " reusing existing credentials" else ACCESS_KEY="paperless" SECRET_KEY=$(head -c 32 /dev/urandom | base64 | tr -d '/+=' | head -c 40) echo " generated new credentials" fi echo "Ensuring MinIO user 'paperless' exists..." if ! mc admin user info m "$ACCESS_KEY" >/dev/null 2>&1; then mc admin user add m "$ACCESS_KEY" "$SECRET_KEY" fi echo "Writing scoped policy (paperless bucket only)..." printf '%s' '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:ListBucket"],"Resource":["arn:aws:s3:::paperless"]},{"Effect":"Allow","Action":["s3:GetObject","s3:PutObject","s3:DeleteObject"],"Resource":["arn:aws:s3:::paperless/*"]}]}' > /tmp/paperless-rw-policy.json mc admin policy create m paperless-rw /tmp/paperless-rw-policy.json || \ mc admin policy update m paperless-rw /tmp/paperless-rw-policy.json mc admin policy attach m paperless-rw --user "$ACCESS_KEY" echo "Writing paperless-minio-creds secret (ns paperless)..." kubectl -n paperless create secret generic paperless-minio-creds \ --from-literal=ACCESS_KEY="$ACCESS_KEY" \ --from-literal=SECRET_KEY="$SECRET_KEY" \ --from-literal=BUCKET=paperless \ --from-literal=ENDPOINT=http://minio-cluster-hl.storage.svc.cluster.local:9000 \ --dry-run=client -o yaml | kubectl apply -f - echo "Done." volumes: - name: shared emptyDir: {} - name: minio-creds secret: secretName: minio-creds