RustFS Credential Runbook¶
RustFS uses two credential classes:
- Root/app credentials are only for the TrueNAS RustFS app bootstrap and console administration.
- Workload credentials are RustFS-console-created access keys used by Kubernetes clients through External Secrets Operator.
Do not point Kubernetes workloads at the TrueNAS app/root credential.
1Password Item¶
The shared item is rustfs in vault homelab-prod.
Required fields:
| Field | Purpose |
|---|---|
root-access-key |
TrueNAS RustFS app/root access key. Do not use for Kubernetes clients. |
root-secret-key |
TrueNAS RustFS app/root secret key. Do not use for Kubernetes clients. |
rustfs-workload-access-key |
RustFS console-created workload access key for Kubernetes S3 clients. |
rustfs-workload-secret-key |
RustFS console-created workload secret key for Kubernetes S3 clients. |
kopia_password |
Kopia repository encryption password. |
endpoint |
RustFS S3 endpoint, currently http://192.168.10.133:30292. |
S3_ENDPOINT |
RustFS S3 endpoint, currently http://192.168.10.133:30292. |
Workload key IAM policy¶
The single workload key (named homelab-workload in the RustFS console) is
configured with a broad allow policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:*"],
"Resource": ["arn:aws:s3:::*"]
}
]
}
Scope is intentionally broad (homelab single-tenant). Per-bucket IAM scoping
would duplicate Kubernetes RBAC's namespace separation without adding meaningful
protection in a single-operator homelab — a compromised cluster would mean a
compromised key either way. Broad policy also means new buckets work immediately
without a forgotten-IAM failure mode when adding a new logging/metrics/backup
destination. The workload key is kept distinct from root-access-key so the
cluster cannot invoke RustFS admin operations (bucket create/delete, user
mgmt — those use root via console).
When to tighten: - If multiple operators/people gain cluster access and the homelab becomes shared. - If a specific app misbehaves with the bucket — scope ITS key, not the shared one. - If running an audit/compliance exercise that requires least-privilege docs.
Captured in mink:
rustfs-workload-key-policy-full-s3-on-all-buckets-homelab-single-tenant-decision.md
Update 1Password¶
Use these commands when rotating the RustFS workload key. Replace the placeholder values manually.
op item edit rustfs \
--vault homelab-prod \
'rustfs-workload-access-key[text]=PASTE_RUSTFS_WORKLOAD_ACCESS_KEY_HERE' \
'rustfs-workload-secret-key[concealed]=PASTE_RUSTFS_WORKLOAD_SECRET_KEY_HERE'
Optional root/app fields, if the 1Password item does not already have them:
op item edit rustfs \
--vault homelab-prod \
'root-access-key[text]=PASTE_RUSTFS_ROOT_ACCESS_KEY_HERE' \
'root-secret-key[concealed]=PASTE_RUSTFS_ROOT_SECRET_KEY_HERE'
Verify field presence without revealing concealed values:
op item get rustfs \
--vault homelab-prod \
--fields rustfs-workload-access-key,rustfs-workload-secret-key,kopia_password,endpoint,S3_ENDPOINT
ESO Consumers¶
These GitOps-managed ExternalSecrets read rustfs-workload-access-key and rustfs-workload-secret-key:
| ExternalSecret | Kubernetes Secret |
|---|---|
cloudnative-pg/cnpg-s3-credentials |
cnpg-s3-credentials |
kopia-ui/kopia-ui-secrets |
kopia-ui-secret (only consumes kopia_password, not the workload key) |
loki-stack/loki-s3-credentials |
loki-s3-credentials |
monitoring/tempo-s3-credentials |
tempo-s3-credentials |
posthog/posthog-secrets |
posthog-secrets |
rustfs-lifecycle/rustfs-admin-credentials |
rustfs-admin-credentials |
kopiur/kopiur-rustfs (ClusterExternalSecret → every namespace labeled kopiur.home-operations.com/repo: cluster-kopia) |
kopiur-rustfs |
Per-PVC backup credentials are delivered by the single kopiur-rustfs ClusterExternalSecret (infrastructure/controllers/kopiur/externalsecret.yaml), which fans the repo credentials into every namespace labeled kopiur.home-operations.com/repo: cluster-kopia.
Force ESO refresh after changing 1Password:
TS="$(date +%s)"
kubectl annotate externalsecret -n cloudnative-pg cnpg-s3-credentials force-sync="$TS" --overwrite
kubectl annotate externalsecret -n kopia-ui kopia-ui-secrets force-sync="$TS" --overwrite
kubectl annotate externalsecret -n loki-stack loki-s3-credentials force-sync="$TS" --overwrite
kubectl annotate externalsecret -n monitoring tempo-s3-credentials force-sync="$TS" --overwrite
kubectl annotate externalsecret -n posthog posthog-secrets force-sync="$TS" --overwrite
kubectl annotate externalsecret -n rustfs-lifecycle rustfs-admin-credentials force-sync="$TS" --overwrite
# Also refresh the kopiur repo-credential fanout (one ClusterExternalSecret
# feeds the per-namespace kopiur-rustfs Secret into every backed-up namespace):
kubectl annotate clusterexternalsecret kopiur-rustfs force-sync="$TS" --overwrite
Restart consumers that load S3 credentials from environment variables:
# CNPG re-reads cnpg-s3-credentials automatically via the operator's
# Secret-watcher — no restart needed for postgres clusters.
kubectl rollout restart deploy/kopia-ui -n kopia-ui
kubectl rollout restart statefulset/loki-backend statefulset/loki-write -n loki-stack
kubectl rollout restart deploy/loki-read -n loki-stack
kubectl rollout restart statefulset/tempo -n monitoring
kubectl rollout restart deploy/db deploy/feature-flags deploy/plugins deploy/web deploy/worker \
deploy/ingestion-general deploy/ingestion-sessionreplay \
deploy/recording-api deploy/replay-capture \
deploy/temporal-django-worker deploy/property-defs-rs \
-n posthog
kopiur mover Jobs read the namespace kopiur-rustfs Secret at Job creation time, so the next scheduled (or manually triggered) Snapshot picks up rotated credentials automatically — no operator restart needed.
The RustFS lifecycle Job is spawned by its CronJob — the next scheduled run
uses the refreshed Secret.