From 00869c6f5b6bc9aacc40b4d235fb61b70e33fbbd Mon Sep 17 00:00:00 2001 From: Jon Chery Date: Tue, 4 Aug 2026 11:36:42 +0000 Subject: [PATCH] refactor(security): export WriteAtomic (T02.2, REQ-059) ---ci--- project: orca phase: 2 milestone: v0.8 status: execute ---/ci--- --- internal/security/ca.go | 27 +++++++++++++++------------ internal/security/sshkey.go | 4 ++-- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/internal/security/ca.go b/internal/security/ca.go index e19acc1..33fca5b 100644 --- a/internal/security/ca.go +++ b/internal/security/ca.go @@ -121,10 +121,10 @@ func CAInit(dir, commonName string) (*CA, error) { // Atomic write: temp file + rename. This avoids leaving a half-written // ca.key on disk if the process crashes mid-write. - if err := writeAtomic(certPath, CACPEMMode, certPEM); err != nil { + if err := WriteAtomic(certPath, CACPEMMode, certPEM); err != nil { return nil, err } - if err := writeAtomic(keyPath, CAMode, keyPEM); err != nil { + if err := WriteAtomic(keyPath, CAMode, keyPEM); err != nil { return nil, err } @@ -290,23 +290,26 @@ func bothExist(paths ...string) (bool, error) { // WriteCert writes a cert PEM blob to path with mode 0644 atomically. // REQ-033 requires cert files to be 0644; this helper enforces that. func WriteCert(path string, pemBytes []byte) error { - return writeAtomic(path, CACPEMMode, pemBytes) + return WriteAtomic(path, CACPEMMode, pemBytes) } // WriteKey writes a private-key PEM blob to path with mode 0600 // atomically. REQ-033 requires key files to be 0600; this helper // enforces that. func WriteKey(path string, pemBytes []byte) error { - return writeAtomic(path, CAMode, pemBytes) + return WriteAtomic(path, CAMode, pemBytes) } -// writeAtomic writes data to a temp file in dir and renames. Sets the +// WriteAtomic writes data to a temp file in dir and renames. Sets the // requested perm before the rename so the file lands at the right mode. -func writeAtomic(path string, mode os.FileMode, data []byte) error { +// Exported (AD-029) so the key-reset / known_hosts atomic rewrite path +// in proxmox (T02.6/T02.7) can reuse it instead of duplicating the +// ~20-LOC pattern (RESEARCH ยง5 pitfall #10). +func WriteAtomic(path string, mode os.FileMode, data []byte) error { dir := filepath.Dir(path) tmp, err := os.CreateTemp(dir, ".tmp-*") if err != nil { - return fmt.Errorf("writeAtomic: create temp: %w", err) + return fmt.Errorf("WriteAtomic: create temp: %w", err) } tmpName := tmp.Name() // Best-effort cleanup if we fail before rename. @@ -315,21 +318,21 @@ func writeAtomic(path string, mode os.FileMode, data []byte) error { }() if _, err := tmp.Write(data); err != nil { _ = tmp.Close() - return fmt.Errorf("writeAtomic: write: %w", err) + return fmt.Errorf("WriteAtomic: write: %w", err) } if err := tmp.Chmod(mode); err != nil { _ = tmp.Close() - return fmt.Errorf("writeAtomic: chmod: %w", err) + return fmt.Errorf("WriteAtomic: chmod: %w", err) } if err := tmp.Sync(); err != nil { _ = tmp.Close() - return fmt.Errorf("writeAtomic: sync: %w", err) + return fmt.Errorf("WriteAtomic: sync: %w", err) } if err := tmp.Close(); err != nil { - return fmt.Errorf("writeAtomic: close: %w", err) + return fmt.Errorf("WriteAtomic: close: %w", err) } if err := os.Rename(tmpName, path); err != nil { - return fmt.Errorf("writeAtomic: rename: %w", err) + return fmt.Errorf("WriteAtomic: rename: %w", err) } return nil } diff --git a/internal/security/sshkey.go b/internal/security/sshkey.go index 5af34a5..b793e5c 100644 --- a/internal/security/sshkey.go +++ b/internal/security/sshkey.go @@ -95,10 +95,10 @@ func GenerateOrLoadSSHKey(dir string) (keyPEM, pubLine []byte, err error) { pubLine = ssh.MarshalAuthorizedKey(sshPub) // Persist with correct modes (atomic write + chmod). - if err := writeAtomic(keyPath, SSHKeyMode, keyPEM); err != nil { + if err := WriteAtomic(keyPath, SSHKeyMode, keyPEM); err != nil { return nil, nil, fmt.Errorf("write SSH key: %w", err) } - if err := writeAtomic(pubPath, SSHPubMode, pubLine); err != nil { + if err := WriteAtomic(pubPath, SSHPubMode, pubLine); err != nil { return nil, nil, fmt.Errorf("write SSH pub: %w", err) }