662 lines
20 KiB
Markdown
662 lines
20 KiB
Markdown
# Credential Rotation Runbook
|
||
|
||
## Overview
|
||
|
||
Step-by-step rotation procedures for all credentials exposed in the
|
||
`homelab-optimized` public mirror (audited 2026-02-20). Work through each
|
||
section in priority order. After updating secrets in compose files, commit
|
||
and push — GitOps will redeploy automatically.
|
||
|
||
> **Note:** Almost all of these stem from the same root cause — secrets were
|
||
> hard-coded in compose files, then those files were committed to git, then
|
||
> `generate_service_docs.py` and wiki-upload scripts duplicated those secrets
|
||
> into documentation, creating 3–5× copies of every secret across the repo.
|
||
> See the "Going Forward" section for how to prevent this.
|
||
|
||
## Prerequisites
|
||
|
||
- [ ] SSH / Tailscale access to Atlantis, Calypso, Homelab VM, Seattle VM, matrix-ubuntu-vm
|
||
- [ ] Gitea admin access (`git.vish.gg`)
|
||
- [ ] Authentik admin access
|
||
- [ ] Google account access (Gmail app passwords)
|
||
- [ ] Cloudflare dashboard access
|
||
- [ ] OpenAI platform access
|
||
- [ ] Write access to this repository
|
||
|
||
## Metadata
|
||
|
||
- **Estimated Time**: 4–6 hours
|
||
- **Risk Level**: Medium (service restarts required for most items)
|
||
- **Requires Downtime**: Brief per-service restart only
|
||
- **Reversible**: Yes (old values can be restored if something breaks)
|
||
- **Last Updated**: 2026-02-20
|
||
|
||
---
|
||
|
||
## Priority 1 — Rotate Immediately (Externally Usable Tokens)
|
||
|
||
### 1. Gitea API Tokens
|
||
|
||
Two tokens hard-coded across scripts and docs.
|
||
|
||
#### 1a. Wiki/scripts token (`77e3ddaf...`)
|
||
|
||
**Files to update:**
|
||
- `scripts/cleanup-gitea-wiki.sh`
|
||
- `scripts/upload-all-docs-to-gitea-wiki.sh`
|
||
- `scripts/upload-to-gitea-wiki.sh`
|
||
- `scripts/create-clean-organized-wiki.sh`
|
||
- `scripts/upload-organized-wiki.sh`
|
||
- `docs/admin/DOCUMENTATION_MAINTENANCE_GUIDE.md`
|
||
|
||
```bash
|
||
# 1. Go to https://git.vish.gg/user/settings/applications
|
||
# 2. Revoke the token starting 77e3ddaf
|
||
# 3. Generate new token, name: homelab-wiki, scope: repo
|
||
# 4. Replace in all files:
|
||
NEW_TOKEN=REDACTED_TOKEN
|
||
for f in scripts/cleanup-gitea-wiki.sh \
|
||
scripts/upload-all-docs-to-gitea-wiki.sh \
|
||
scripts/upload-to-gitea-wiki.sh \
|
||
scripts/create-clean-organized-wiki.sh \
|
||
scripts/upload-organized-wiki.sh \
|
||
docs/admin/DOCUMENTATION_MAINTENANCE_GUIDE.md; do
|
||
sed -i "s/REDACTED_GITEA_TOKEN/$NEW_TOKEN/g" "$f"
|
||
done
|
||
```
|
||
|
||
#### 1b. Retro-site clone token (`52fa6ccb...`)
|
||
|
||
**File:** `Calypso/retro-site.yaml` and `hosts/synology/calypso/retro-site.yaml`
|
||
|
||
```bash
|
||
# 1. Go to https://git.vish.gg/user/settings/applications
|
||
# 2. Revoke the token starting 52fa6ccb
|
||
# 3. Generate new token, name: retro-site-deploy, scope: repo:read
|
||
# 4. Update the git clone URL in both compose files
|
||
# Consider switching to a deploy key for least-privilege access
|
||
```
|
||
|
||
---
|
||
|
||
### 2. Cloudflare API Token (`FGXlHM7doB8Z...`)
|
||
|
||
Appears in 13 files including active dynamic DNS updaters on multiple hosts.
|
||
|
||
**Files to update (active deployments):**
|
||
- `hosts/synology/atlantis/dynamicdnsupdater.yaml`
|
||
- `hosts/physical/guava/portainer_yaml/dynamic_dns.yaml`
|
||
- `hosts/physical/concord-nuc/dyndns_updater.yaml`
|
||
- Various Calypso/homelab-vm DDNS configs
|
||
|
||
**Files to sanitize (docs):**
|
||
- `docs/infrastructure/cloudflare-dns.md`
|
||
- `docs/infrastructure/npm-migration-jan2026.md`
|
||
- Any `docs/services/individual/ddns-*.md` files
|
||
|
||
```bash
|
||
# 1. Go to https://dash.cloudflare.com/profile/api-tokens
|
||
# 2. Find the token (FGXlHM7doB8Z...) and click Revoke
|
||
# 3. Create a new token: use "Edit zone DNS" template, scope to your zone only
|
||
# 4. Replace in all compose files above
|
||
# 5. Replace hardcoded value in docs with: YOUR_CLOUDFLARE_API_TOKEN
|
||
|
||
# Verify DDNS containers restart and can still update DNS:
|
||
docker logs cloudflare-ddns --tail 20
|
||
```
|
||
|
||
---
|
||
|
||
### 3. OpenAI API Key (`sk-proj-C_IYp6io...`)
|
||
|
||
**Files to update:**
|
||
- `hosts/vms/homelab-vm/hoarder.yaml`
|
||
- `docs/services/individual/web.md` (replace with placeholder)
|
||
|
||
```bash
|
||
# 1. Go to https://platform.openai.com/api-keys
|
||
# 2. Delete the exposed key
|
||
# 3. Create a new key, set a usage limit
|
||
# 4. Update OPENAI_API_KEY in hoarder.yaml
|
||
# 5. Replace value in docs with: YOUR_OPENAI_API_KEY
|
||
```
|
||
|
||
---
|
||
|
||
## Priority 2 — OAuth / SSO Secrets
|
||
|
||
### 4. Grafana ↔ Authentik OAuth Secret
|
||
|
||
**Files to update:**
|
||
- `hosts/vms/homelab-vm/monitoring.yaml`
|
||
- `hosts/synology/atlantis/grafana.yml`
|
||
- `docs/infrastructure/authentik-sso.md` (replace with placeholder)
|
||
- `docs/services/individual/grafana-oauth.md` (replace with placeholder)
|
||
|
||
```bash
|
||
# 1. Log into Authentik admin: https://auth.vish.gg/if/admin/
|
||
# 2. Applications → Providers → find Grafana OAuth2 provider
|
||
# 3. Edit → regenerate Client Secret → copy both Client ID and Secret
|
||
# 4. Update in both compose files:
|
||
# GF_AUTH_GENERIC_OAUTH_CLIENT_ID: NEW_ID
|
||
# GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: NEW_SECRET
|
||
# 5. Commit and push — both Grafana stacks restart automatically
|
||
|
||
# Verify SSO works after restart:
|
||
curl -I https://gf.vish.gg
|
||
```
|
||
|
||
---
|
||
|
||
### 5. Seafile ↔ Authentik OAuth Secret
|
||
|
||
**Files to update:**
|
||
- `hosts/synology/calypso/seafile-oauth-config.py`
|
||
- `docs/services/individual/seafile-oauth.md` (replace with placeholder)
|
||
|
||
```bash
|
||
# 1. Log into Authentik admin
|
||
# 2. Applications → Providers → find Seafile OAuth2 provider
|
||
# 3. Regenerate client secret
|
||
# 4. Update OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET in seafile-oauth-config.py
|
||
# 5. Re-run the config script on the Seafile server to apply
|
||
```
|
||
|
||
---
|
||
|
||
### 6. Authentik Secret Key (`RpRexcYo5HAz...`)
|
||
|
||
**Critical** — this key encrypts all Authentik data (tokens, sessions, stored credentials).
|
||
|
||
**File:** `hosts/synology/calypso/authentik/docker-compose.yaml`
|
||
|
||
```bash
|
||
# 1. Generate a new secret:
|
||
python3 -c "import secrets; print(secrets.token_urlsafe(50))"
|
||
|
||
# 2. Update AUTHENTIK_SECRET_KEY in docker-compose.yaml
|
||
# 3. Commit and push — Authentik will restart
|
||
# WARNING: All active Authentik sessions will be invalidated.
|
||
# Users will need to log back in. SSO-protected services
|
||
# may temporarily show login errors while Authentik restarts.
|
||
|
||
# Verify Authentik is healthy after restart:
|
||
docker logs authentik_server --tail 30
|
||
```
|
||
|
||
---
|
||
|
||
## Priority 3 — Application Secrets (Require Service Restart)
|
||
|
||
### 7. Gmail App Passwords
|
||
|
||
Five distinct app passwords were found across the repo. Revoke all of them
|
||
in Google Account → Security → App passwords, then create new per-service ones.
|
||
|
||
| Password | Used For | Active Files |
|
||
|----------|----------|-------------|
|
||
| (see Vaultwarden) | Mastodon, Joplin, Authentik SMTP | `matrix-ubuntu-vm/mastodon/.env.production.template`, `atlantis/joplin.yml`, `calypso/authentik/docker-compose.yaml` |
|
||
| (see Vaultwarden) | Vaultwarden SMTP | `atlantis/vaultwarden.yaml` |
|
||
| (see Vaultwarden) | Documenso SMTP | `atlantis/documenso/documenso.yaml` |
|
||
| (see Vaultwarden) | Reactive Resume v4 (archived) | `archive/reactive_resume_v4_archived/docker-compose.yml` |
|
||
| (see Vaultwarden) | Reactive Resume v5 (active) | `calypso/reactive_resume_v5/docker-compose.yml` |
|
||
|
||
**Best practice:** Create one app password per service, named clearly (e.g.,
|
||
`homelab-joplin`, `homelab-mastodon`). Update each file's `SMTP_PASS` /
|
||
`SMTP_PASSWORD` / `MAILER_AUTH_PASSWORD` / `smtp_password` field.
|
||
|
||
---
|
||
|
||
### 8. Matrix Synapse Secrets
|
||
|
||
Three secrets in `homeserver.yaml`, plus the TURN shared secret.
|
||
|
||
**File:** `hosts/synology/atlantis/matrix_synapse_docs/homeserver.yaml`
|
||
|
||
```bash
|
||
# Generate fresh values for each:
|
||
python3 -c "import secrets; print(secrets.token_urlsafe(48))"
|
||
|
||
# Fields to rotate:
|
||
# registration_shared_secret
|
||
# macaroon_secret_key
|
||
# form_secret
|
||
# turn_shared_secret
|
||
|
||
# After updating homeserver.yaml, restart Synapse:
|
||
docker restart synapse # or via Portainer
|
||
|
||
# Also update coturn config on the server directly:
|
||
ssh atlantis
|
||
nano /path/to/turnserver.conf
|
||
# Update: static-auth-secret=NEW_TURN_SECRET
|
||
systemctl restart coturn
|
||
|
||
# Update instructions.txt — replace old values with REDACTED
|
||
```
|
||
|
||
---
|
||
|
||
### 9. Mastodon `SECRET_KEY_BASE` + `OTP_SECRET`
|
||
|
||
**File:** `hosts/synology/atlantis/mastodon.yml`
|
||
**Also in:** `docs/services/individual/mastodon.md` (replace with placeholder)
|
||
|
||
```bash
|
||
# Generate new values:
|
||
openssl rand -hex 64 # for SECRET_KEY_BASE
|
||
openssl rand -hex 64 # for OTP_SECRET
|
||
|
||
# Update both in mastodon.yml
|
||
# Commit and push — GitOps restarts Mastodon
|
||
# WARNING: All active user sessions are invalidated. Users must log back in.
|
||
|
||
# Verify Mastodon web is accessible:
|
||
curl -I https://your-mastodon-domain/
|
||
docker logs mastodon_web --tail 20
|
||
```
|
||
|
||
---
|
||
|
||
### 10. Documenso Secrets (3 keys)
|
||
|
||
**Files:**
|
||
- `hosts/synology/atlantis/documenso/documenso.yaml`
|
||
- `hosts/synology/atlantis/documenso/Secrets.txt` (will be removed by sanitizer)
|
||
- `docs/services/individual/documenso.md` (replace with placeholder)
|
||
|
||
```bash
|
||
# Generate new values:
|
||
python3 -c "import secrets; print(secrets.token_urlsafe(32))" # NEXTAUTH_SECRET
|
||
python3 -c "import secrets; print(secrets.token_urlsafe(32))" # NEXT_PRIVATE_ENCRYPTION_KEY
|
||
python3 -c "import secrets; print(secrets.token_urlsafe(32))" # NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY
|
||
|
||
# Update all three in documenso.yaml
|
||
# NOTE: Rotating encryption keys will invalidate signed documents.
|
||
# Confirm this is acceptable before rotating.
|
||
```
|
||
|
||
---
|
||
|
||
### 11. Paperless-NGX API Token
|
||
|
||
**Files:**
|
||
- `hosts/synology/calypso/paperless/paperless-ai.yml`
|
||
- `hosts/synology/calypso/paperless/README.md` (replace with placeholder)
|
||
- `docs/services/paperless.md` (replace with placeholder)
|
||
|
||
```bash
|
||
# 1. Log into Paperless web UI
|
||
# 2. Admin → Auth Token → delete existing, generate new
|
||
# 3. Update PAPERLESS_API_TOKEN in paperless-ai.yml
|
||
# 4. Commit and push
|
||
```
|
||
|
||
---
|
||
|
||
### 12. Immich JWT Secret (Both NAS)
|
||
|
||
**Files:**
|
||
- `hosts/synology/atlantis/immich/stack.env` (will be removed by sanitizer)
|
||
- `hosts/synology/calypso/immich/stack.env` (will be removed by sanitizer)
|
||
|
||
Since these files are removed by the sanitizer, ensure they are in `.gitignore`
|
||
or managed via Portainer env variables going forward.
|
||
|
||
```bash
|
||
# Generate new secret:
|
||
openssl rand -base64 96
|
||
|
||
# Update JWT_SECRET in both stack.env files locally,
|
||
# then apply via Portainer (not committed to git).
|
||
# WARNING: All active Immich sessions invalidated.
|
||
```
|
||
|
||
---
|
||
|
||
### 13. Revolt/Stoatchat — LiveKit API Secret + VAPID Private Key
|
||
|
||
**Files:**
|
||
- `hosts/vms/seattle/stoatchat/livekit.yml`
|
||
- `hosts/vms/seattle/stoatchat/Revolt.overrides.toml`
|
||
- `hosts/vms/homelab-vm/stoatchat.yaml`
|
||
- `docs/services/stoatchat/Revolt.overrides.toml` (replace with placeholder)
|
||
- `hosts/vms/seattle/stoatchat/DEPLOYMENT_SUMMARY.md` (replace with placeholder)
|
||
|
||
```bash
|
||
# Generate new LiveKit API key/secret pair:
|
||
# Use the LiveKit CLI or generate random strings:
|
||
python3 -c "import secrets; print(secrets.token_urlsafe(24))" # API key
|
||
python3 -c "import secrets; print(secrets.token_urlsafe(32))" # API secret
|
||
|
||
# Generate new VAPID key pair:
|
||
npx web-push generate-vapid-keys
|
||
# or: python3 -c "from py_vapid import Vapid; v=Vapid(); v.generate_keys(); print(v.private_key)"
|
||
|
||
# Update in livekit.yml and Revolt.overrides.toml
|
||
# Restart LiveKit and Revolt services
|
||
```
|
||
|
||
---
|
||
|
||
### 14. Jitsi Internal Auth Passwords (6 passwords)
|
||
|
||
**File:** `hosts/synology/atlantis/jitsi/jitsi.yml`
|
||
**Also in:** `hosts/synology/atlantis/jitsi/.env` (will be removed by sanitizer)
|
||
|
||
```bash
|
||
# Generate new passwords for each variable:
|
||
for var in JICOFO_COMPONENT_SECRET JICOFO_AUTH_PASSWORD JVB_AUTH_PASSWORD \
|
||
JIGASI_XMPP_PASSWORD JIBRI_RECORDER_PASSWORD JIBRI_XMPP_PASSWORD; do
|
||
echo "$var=$(openssl rand -hex 10)"
|
||
done
|
||
|
||
# Update all 6 in jitsi.yml
|
||
# Restart the entire Jitsi stack — all components must use the same passwords
|
||
docker compose -f jitsi.yml down && docker compose -f jitsi.yml up -d
|
||
```
|
||
|
||
---
|
||
|
||
### 15. SNMP v3 Auth + Priv Passwords
|
||
|
||
Used for NAS monitoring — same credentials across 6 files.
|
||
|
||
**Files to update:**
|
||
- `hosts/synology/setillo/prometheus/snmp.yml`
|
||
- `hosts/synology/atlantis/grafana_prometheus/snmp.yml`
|
||
- `hosts/synology/atlantis/grafana_prometheus/snmp_mariushosting.yml`
|
||
- `hosts/synology/calypso/grafana_prometheus/snmp.yml`
|
||
- `hosts/vms/homelab-vm/monitoring.yaml`
|
||
|
||
```bash
|
||
# 1. Log into each Synology NAS DSM
|
||
# 2. Go to Control Panel → Terminal & SNMP → SNMP tab
|
||
# 3. Update SNMPv3 auth password and privacy password to new values
|
||
# 4. Update the same values in all 5 config files above
|
||
# 5. The archive file (deprecated-monitoring-stacks) can just be left for
|
||
# the sanitizer to redact.
|
||
```
|
||
|
||
---
|
||
|
||
### 16. Invidious `hmac_key`
|
||
|
||
**Files:**
|
||
- `hosts/physical/concord-nuc/invidious/invidious.yaml`
|
||
- `hosts/physical/concord-nuc/invidious/invidious_old/invidious.yaml`
|
||
- `hosts/synology/atlantis/invidious.yml`
|
||
|
||
```bash
|
||
# Generate new hmac_key:
|
||
python3 -c "import secrets; print(secrets.token_hex(16))"
|
||
|
||
# Update hmac_key in each active invidious.yaml
|
||
# Restart Invidious containers
|
||
```
|
||
|
||
---
|
||
|
||
### 17. Open WebUI Secret Keys
|
||
|
||
**Files:**
|
||
- `hosts/vms/contabo-vm/ollama/docker-compose.yml`
|
||
- `hosts/synology/atlantis/ollama/docker-compose.yml`
|
||
- `hosts/synology/atlantis/ollama/64_bit_key.txt` (will be removed by sanitizer)
|
||
|
||
```bash
|
||
# Generate new key:
|
||
openssl rand -hex 32
|
||
|
||
# Update WEBUI_SECRET_KEY in both compose files
|
||
# Restart Open WebUI containers — active sessions invalidated
|
||
```
|
||
|
||
---
|
||
|
||
### 18. Portainer Edge Key
|
||
|
||
**File:** `hosts/vms/homelab-vm/portainer_agent.yaml`
|
||
|
||
```bash
|
||
# 1. Log into Portainer at https://192.168.0.200:9443
|
||
# 2. Go to Settings → Edge Compute → Edge Agents
|
||
# 3. Find the homelab-vm agent and regenerate its edge key
|
||
# 4. Update EDGE_KEY in portainer_agent.yaml with the new base64 value
|
||
# 5. Restart the Portainer edge agent container
|
||
```
|
||
|
||
---
|
||
|
||
### 19. OpenProject Secret Key
|
||
|
||
**File:** `hosts/vms/homelab-vm/openproject.yml`
|
||
**Also in:** `docs/services/individual/openproject.md` (replace with placeholder)
|
||
|
||
```bash
|
||
openssl rand -hex 64
|
||
# Update OPENPROJECT_SECRET_KEY_BASE in openproject.yml
|
||
# Restart OpenProject — sessions invalidated
|
||
```
|
||
|
||
---
|
||
|
||
### 20. RomM Auth Secret Key
|
||
|
||
**File:** `hosts/vms/homelab-vm/romm/romm.yaml`
|
||
**Also:** `hosts/vms/homelab-vm/romm/secret_key.yaml` (will be removed by sanitizer)
|
||
|
||
```bash
|
||
openssl rand -hex 32
|
||
# Update ROMM_AUTH_SECRET_KEY in romm.yaml
|
||
# Restart RomM — sessions invalidated
|
||
```
|
||
|
||
---
|
||
|
||
### 21. Hoarder NEXTAUTH Secret
|
||
|
||
**File:** `hosts/vms/homelab-vm/hoarder.yaml`
|
||
**Also in:** `docs/services/individual/web.md` (replace with placeholder)
|
||
|
||
```bash
|
||
openssl rand -base64 36
|
||
# Update NEXTAUTH_SECRET in hoarder.yaml
|
||
# Restart Hoarder — sessions invalidated
|
||
```
|
||
|
||
---
|
||
|
||
## Priority 4 — Shared / Weak Passwords
|
||
|
||
### 22. `REDACTED_PASSWORD123!` — Used Across 5+ Services
|
||
|
||
This password is the same for all of the following. Change each to a
|
||
**unique** strong password:
|
||
|
||
| Service | File | Variable |
|
||
|---------|------|----------|
|
||
| NetBox | `hosts/synology/atlantis/netbox.yml` | `SUPERUSER_PASSWORD` |
|
||
| Paperless admin | `hosts/synology/calypso/paperless/docker-compose.yml` | `PAPERLESS_ADMIN_PASSWORD` |
|
||
| Seafile admin | `hosts/synology/calypso/seafile-server.yaml` | `INIT_SEAFILE_ADMIN_PASSWORD` |
|
||
| Seafile admin (new) | `hosts/synology/calypso/seafile-new.yaml` | `INIT_SEAFILE_ADMIN_PASSWORD` |
|
||
| PhotoPrism | `hosts/physical/anubis/photoprism.yml` | `PHOTOPRISM_ADMIN_PASSWORD` |
|
||
| Hemmelig | `hosts/vms/bulgaria-vm/hemmelig.yml` | `SECRET_JWT_SECRET` |
|
||
| Vaultwarden admin | `hosts/synology/atlantis/bitwarden/bitwarden_token.txt` | (source password) |
|
||
|
||
For each: generate `openssl rand -base64 18`, update in the compose file,
|
||
restart the container, then log in to verify.
|
||
|
||
---
|
||
|
||
### 23. `REDACTED_PASSWORD` — Used Across 3 Services
|
||
|
||
| Service | File | Variable |
|
||
|---------|------|----------|
|
||
| Gotify | `hosts/vms/homelab-vm/gotify.yml` | `GOTIFY_DEFAULTUSER_PASS` |
|
||
| Pi-hole | `hosts/synology/atlantis/pihole.yml` | `WEBPASSWORD` |
|
||
| Stirling PDF | `hosts/synology/atlantis/stirlingpdf.yml` | `SECURITY_INITIAL_LOGIN_PASSWORD` |
|
||
|
||
---
|
||
|
||
### 24. `mastodon_pass_2026` — Live PostgreSQL Password
|
||
|
||
**Files:**
|
||
- `hosts/vms/matrix-ubuntu-vm/mastodon/.env.production.template`
|
||
- `hosts/vms/matrix-ubuntu-vm/docs/SETUP.md`
|
||
|
||
```bash
|
||
# On the matrix-ubuntu-vm server:
|
||
ssh YOUR_WAN_IP
|
||
sudo -u postgres psql
|
||
ALTER USER mastodon WITH PASSWORD 'REDACTED_PASSWORD';
|
||
\q
|
||
|
||
# Update the password in .env.production.template and Mastodon's running config
|
||
# Restart Mastodon services
|
||
```
|
||
|
||
---
|
||
|
||
### 25. Watchtower API Token (`REDACTED_WATCHTOWER_TOKEN`)
|
||
|
||
| File |
|
||
|------|
|
||
| `hosts/synology/atlantis/watchtower.yml` |
|
||
| `hosts/synology/calypso/prometheus.yml` |
|
||
|
||
```bash
|
||
# Generate a proper random token:
|
||
openssl rand -hex 20
|
||
# Update WATCHTOWER_HTTP_API_TOKEN in both files
|
||
# Update any scripts that call the Watchtower API
|
||
```
|
||
|
||
---
|
||
|
||
### 26. `test:test` SSH Credentials on `YOUR_WAN_IP`
|
||
|
||
The matrix-ubuntu-vm CREDENTIALS.md shows a `test` user with password `test`.
|
||
|
||
```bash
|
||
# SSH to the server and remove or secure the test account:
|
||
ssh YOUR_WAN_IP
|
||
passwd test # change to a strong password
|
||
# or: userdel -r test # remove entirely if unused
|
||
```
|
||
|
||
---
|
||
|
||
## Priority 5 — Network Infrastructure
|
||
|
||
### 27. Management Switch Password Hashes
|
||
|
||
**File:** `mgmtswitch.conf` (will be removed from public mirror by sanitizer)
|
||
|
||
The SHA-512 hashes for `root`, `vish`, and `vkhemraj` switch accounts are
|
||
crackable offline. Rotate the switch passwords:
|
||
|
||
```bash
|
||
# SSH to the management switch
|
||
ssh admin@10.0.0.15
|
||
# Change passwords for all local accounts:
|
||
enable
|
||
configure terminal
|
||
username root secret NEW_PASSWORD
|
||
username vish secret NEW_PASSWORD
|
||
username vkhemraj secret NEW_PASSWORD
|
||
write memory
|
||
```
|
||
|
||
---
|
||
|
||
## Final Verification
|
||
|
||
After completing all rotations:
|
||
|
||
```bash
|
||
# 1. Commit and push all file changes
|
||
git add -A
|
||
git commit -m "chore(security): rotate all exposed credentials"
|
||
git push origin main
|
||
|
||
# 2. Wait for the mirror workflow to complete, then pull:
|
||
git -C /home/homelab/organized/repos/homelab-optimized pull
|
||
|
||
# 3. Verify none of the old secrets appear in the public mirror:
|
||
cd /home/homelab/organized/repos/homelab-optimized
|
||
grep -r "77e3ddaf\|52fa6ccb\|FGXlHM7d\|sk-proj-C_IYp6io\|ArP5XWdkwVyw\|bdtrpmpce\|toiunzuby" . 2>/dev/null
|
||
grep -r "244c619d\|RpRexcYo5\|mastodon_pass\|REDACTED_PASSWORD\|REDACTED_PASSWORD\|REDACTED_WATCHTOWER_TOKEN" . 2>/dev/null
|
||
grep -r "2e80b1b7d3a\|eca299ae59\|rxmr4tJoqfu\|ZjCofRlfm6\|QE5SudhZ99" . 2>/dev/null
|
||
# All should return no results
|
||
|
||
# 4. Verify GitOps deployments are healthy in Portainer:
|
||
# https://192.168.0.200:9443
|
||
```
|
||
|
||
---
|
||
|
||
## Going Forward — Preventing This Again
|
||
|
||
The root cause: secrets hard-coded in compose files that get committed to git.
|
||
|
||
**Rules:**
|
||
1. **Never hard-code secrets in compose files** — use Docker Secrets, or an
|
||
`.env` file excluded by `.gitignore` (Portainer can load env files from the
|
||
host at deploy time)
|
||
2. **Never put real values in documentation** — use `YOUR_API_KEY` placeholders
|
||
3. **Never create `Secrets.txt` or `CREDENTIALS.md` files in the repo** — use
|
||
a password manager (you already have Vaultwarden/Bitwarden)
|
||
4. **Run the sanitizer locally** before any commit that touches secrets:
|
||
|
||
```bash
|
||
# Test in a temp copy — see what the sanitizer would catch:
|
||
tmpdir=$(mktemp -d)
|
||
cp -r /path/to/homelab "$tmpdir/"
|
||
python3 "$tmpdir/homelab/.gitea/sanitize.py"
|
||
```
|
||
|
||
## Related Documentation
|
||
|
||
- [Security Hardening](../security/SERVER_HARDENING.md)
|
||
- [Repository Sanitization](../admin/REPOSITORY_SANITIZATION.md)
|
||
- [GitOps Deployment Guide](../admin/GITOPS_DEPLOYMENT_GUIDE.md)
|
||
|
||
## Portainer Git Credential Rotation
|
||
|
||
The saved Git credential **`portainer-homelab`** (credId: 1) is used by ~43 stacks to
|
||
pull compose files from `git.vish.gg`. When the Gitea token expires or is rotated,
|
||
all those stacks fail to redeploy.
|
||
|
||
```bash
|
||
# 1. Generate a new Gitea token at https://git.vish.gg/user/settings/applications
|
||
# Scope: read:repository
|
||
|
||
# 2. Test the token:
|
||
curl -s -o /dev/null -w "%{http_code}" \
|
||
-H "Authorization: token YOUR_NEW_TOKEN" \
|
||
"https://git.vish.gg/api/v1/repos/Vish/homelab"
|
||
# Should return 200
|
||
|
||
# 3. Update in Portainer:
|
||
curl -k -s -X PUT \
|
||
-H "X-API-Key: "REDACTED_API_KEY" \
|
||
-H "Content-Type: application/json" \
|
||
"https://192.168.0.200:9443/api/users/1/gitcredentials/1" \
|
||
-d '{"name":"portainer-homelab","username":"vish","password":"YOUR_NEW_TOKEN"}'
|
||
```
|
||
|
||
> Note: The API update may not immediately propagate to automated pulls.
|
||
> Pass credentials inline in redeploy calls to force use of the new token.
|
||
|
||
---
|
||
|
||
## Change Log
|
||
|
||
- 2026-02-27 — Incident: sanitization commit `037d766a` replaced credentials with
|
||
`REDACTED_PASSWORD` placeholders across 14 compose files. All affected containers
|
||
detected via Portainer API env scan and restored from `git show 037d766a^`. Added
|
||
Portainer Git credential rotation section above.
|
||
- 2026-02-20 — Initial creation (8 items)
|
||
- 2026-02-20 — Expanded after full private repo audit (27 items across 34 exposure categories)
|