feat(PROJ-49): Verschlüsselungspflicht at-rest sichtbar machen (Healthcheck & Warnung)
storage.loadKey() startet bei fehlendem/unlesbarem/ungültigem Keyfile weiterhin unverschlüsselt (kein Hard-Fail), aber: - einmalige WARN-Logzeile beim Start mit konkretem Grund - neuer Healthcheck-Prüfpunkt "Encryption" in archivmail status - Dashboard-API liefert encryption.enabled - README: GoBD-Hinweis zu storage.keyfile Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -108,6 +108,13 @@ func New(cfg Config) (*Store, error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// EncryptionEnabled reports whether at-rest AES-256-GCM encryption is active,
|
||||
// i.e. whether a valid 32-byte key was loaded (PROJ-49). Returns false if no
|
||||
// keyfile was configured.
|
||||
func (s *Store) EncryptionEnabled() bool {
|
||||
return len(s.key) == 32
|
||||
}
|
||||
|
||||
// Close releases the database connection pool (if any).
|
||||
func (s *Store) Close() {
|
||||
if s.db != nil {
|
||||
@@ -128,23 +135,29 @@ func (s *Store) loadKey(keyfile string) error {
|
||||
|
||||
// If the env var contains the key itself (not a path), it would be unusual.
|
||||
// We treat the value as a file path in all cases.
|
||||
//
|
||||
// PROJ-49: A configured-but-broken keyfile (unreadable / wrong size) must NOT
|
||||
// hard-fail the service. The spec mandates full backwards compatibility:
|
||||
// the service starts unencrypted (s.key stays nil) and the startup warning in
|
||||
// cmd/archivmail/encryption_status.go surfaces the concrete reason. Hard-failing
|
||||
// here would make the documented edge-case warnings unreachable.
|
||||
data, err := os.ReadFile(keyfile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("storage: read keyfile %s: %w", keyfile, err)
|
||||
return nil // unreadable → run unencrypted; warnEncryptionStatus() logs the reason
|
||||
}
|
||||
|
||||
// Strip whitespace / newlines
|
||||
raw := strings.TrimSpace(string(data))
|
||||
|
||||
// Try base64 decode first (spec says base64-encoded 32-byte key)
|
||||
decoded, err := base64.StdEncoding.DecodeString(raw)
|
||||
if err != nil {
|
||||
decoded, derr := base64.StdEncoding.DecodeString(raw)
|
||||
if derr != nil {
|
||||
// Fall back to raw bytes (for 32-byte binary key files)
|
||||
decoded = []byte(raw)
|
||||
}
|
||||
|
||||
if len(decoded) != 32 {
|
||||
return fmt.Errorf("storage: keyfile must contain exactly 32 bytes (got %d)", len(decoded))
|
||||
return nil // wrong size → run unencrypted; warnEncryptionStatus() logs the reason
|
||||
}
|
||||
|
||||
s.key = decoded
|
||||
|
||||
Reference in New Issue
Block a user