backup: the panel's archive is the command's, private key included

Owner, 2026-10-11: the private key is needed, and a backup from the
panel and one from the console have to be the same.

The commit that made the panel's backup work skipped a file under
certs/ the panel could not open - on the test server the private key,
which the certificate hook leaves to root and Postfix's group. That gave
an archive with a certificate and no key, and two kinds of archive.

- Params.Unprivileged is gone. Nothing is left out on account of who
  makes the archive; the queue is out for everyone (previous commit).
- When the panel cannot read a file under certs/, backup.Check says so
  before the download and the panel refuses with what to do on the host:
  chgrp 998 certs/privkey.pem && chmod 640. backup.UnreadableCert tells
  that case from any other failure.
- 998 is the container's selfpost group, which holds the panel and
  Postfix. Its id was whatever groupadd gave; the Dockerfile now fixes
  it at the value it has always had, because an operator addresses it
  by number from the host.
- Guide: method 1 is the same archive as method 2, with the permission
  the key needs and a note for whatever renews the certificate. Help and
  the box follow the design commit before this one.
- e2e: the restore scenario first makes the key unreadable and expects
  the refusal naming the file and the way out, then takes the backup and
  expects the key in it; nothing is put back by hand before the restart.

  go vet ./internal/backup/ ./internal/web/handlers/: ok
  go test ./internal/backup/ ./cmd/... ./internal/web/...: ok
  make e2e on the test server: ok

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mix
2026-10-11 20:43:10 +03:00
co-authored by Claude Opus 5.5
parent 75ebbf6a57
commit dd0fefdcb7
10 changed files with 138 additions and 93 deletions
+9 -1
View File
@@ -113,5 +113,13 @@ func writeSelfSignedCert(certPath, keyPath string) error {
return err
}
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
return os.WriteFile(keyPath, keyPEM, 0o600)
// Readable by everyone, which a real key must never be. On a server the
// operator gives the key to the container's selfpost group (998) so the
// panel can put it into a full backup; the runner of this suite is not
// always root and cannot hand a file to a group it is not in. The key is
// thrown away with the stage directory.
if err := os.WriteFile(keyPath, keyPEM, 0o644); err != nil {
return err
}
return os.Chmod(keyPath, 0o644)
}