Overview
Design the Configuration Backup subsystem that replaces the legacy backupd.nethesis.it service. Covers the end-to-end flow, storage layout, RBAC, retention, security posture and the admin UI surface for Alpha 2.
Architecture
- Ingest path (appliance → collect):
POST /api/systems/backups with HTTP Basic auth using system_key:system_secret. Body is a GPG-encrypted blob produced by the appliance; the server never sees plaintext.
- Read path (operator → backend):
GET /api/systems/:id/backups, GET /api/systems/:id/backups/:backup_id/download (presigned URL), DELETE /api/systems/:id/backups/:backup_id. RBAC gated identically to GET /api/systems/:id.
- Storage: bring-your-own S3 (DigitalOcean Spaces, AWS S3, R2, MinIO, Garage). No server-managed storage component; operators configure
BACKUP_S3_* env vars.
- Object key layout:
{org_id}/{system_key}/{backup_id}.{ext} — system_key chosen over internal UUID so the bucket listing is human-readable from an operator's S3 console.
- Retention: enforced inline at ingest under a Redis
SET NX lock; default caps are 10 backups + 500 MB per system + optional aggregate cap per org.
- GDPR:
DestroySystem purges the system's S3 prefix before deleting the DB row; destroy is refused if the purge fails (operator retries).
Security
backup_id regex-pinned to UUIDv7 + known extension whitelist.
- Streaming SHA-256 via
io.TeeReader; metadata reconciled via same-key CopyObject.
- Presigned download TTL capped at 15 m; per-system ingest rate limit; cross-service auth-cache invalidation bus.
- All metadata fields sanitised before being stored as
x-amz-meta-* headers.
- Minimum non-forgeable metadata set:
sha256, filename (sanitised), uploader_ip (peer TCP, X-Forwarded-For deliberately ignored).
Legacy-client migration
During the transition NS8 and NethSecurity keep uploading to backupd.nethesis.it and dual-send to the new MY through a translation proxy that maps system_id:auth_token → system_key:system_secret and streams the body to collect. No appliance registration rewrite is required for Alpha 2 — the proxy absorbs the credential format difference.
UI
Per-system detail page, visible next to the existing Alerts tab:
- Backups tab with:
- Table columns: uploaded-at, filename, size, SHA-256 (truncated with tooltip on full hash).
- Kebab menu on each row with
Download and Delete (destructive → red).
- Retention summary tiles above the table: slots used / max, storage used / max, retention-policy description.
- Manual Refresh button; empty / loading / error states; notification on delete success or failure.
- Overview → Status card gains two rows:
Alerts — green check when 0 active, amber triangle when ≥ 1.
Backups — green check when ≥ 1 stored, amber triangle when 0.
Out of scope
- Rewriting the backup client on the appliances (NS8 / NethSecurity keep their existing
system_id:auth_token flow + dual-send via proxy).
- Migration of existing
backupd.nethesis.it archives (encryption keys differ; natural cutover on first new upload).
Related issue: #83
Overview
Design the Configuration Backup subsystem that replaces the legacy
backupd.nethesis.itservice. Covers the end-to-end flow, storage layout, RBAC, retention, security posture and the admin UI surface for Alpha 2.Architecture
POST /api/systems/backupswith HTTP Basic auth usingsystem_key:system_secret. Body is a GPG-encrypted blob produced by the appliance; the server never sees plaintext.GET /api/systems/:id/backups,GET /api/systems/:id/backups/:backup_id/download(presigned URL),DELETE /api/systems/:id/backups/:backup_id. RBAC gated identically toGET /api/systems/:id.BACKUP_S3_*env vars.{org_id}/{system_key}/{backup_id}.{ext}—system_keychosen over internal UUID so the bucket listing is human-readable from an operator's S3 console.SET NXlock; default caps are 10 backups + 500 MB per system + optional aggregate cap per org.DestroySystempurges the system's S3 prefix before deleting the DB row; destroy is refused if the purge fails (operator retries).Security
backup_idregex-pinned to UUIDv7 + known extension whitelist.io.TeeReader; metadata reconciled via same-keyCopyObject.x-amz-meta-*headers.sha256,filename(sanitised),uploader_ip(peer TCP, X-Forwarded-For deliberately ignored).Legacy-client migration
During the transition NS8 and NethSecurity keep uploading to
backupd.nethesis.itand dual-send to the new MY through a translation proxy that mapssystem_id:auth_token→system_key:system_secretand streams the body to collect. No appliance registration rewrite is required for Alpha 2 — the proxy absorbs the credential format difference.UI
Per-system detail page, visible next to the existing Alerts tab:
DownloadandDelete(destructive → red).Alerts— green check when 0 active, amber triangle when ≥ 1.Backups— green check when ≥ 1 stored, amber triangle when 0.Out of scope
system_id:auth_tokenflow + dual-send via proxy).backupd.nethesis.itarchives (encryption keys differ; natural cutover on first new upload).Related issue: #83