refactor(security): export WriteAtomic (T02.2, REQ-059)

---ci---
project: orca
phase: 2
milestone: v0.8
status: execute
---/ci---
This commit is contained in:
Jon Chery
2026-08-04 11:36:42 +00:00
parent aa3462826b
commit 00869c6f5b
2 changed files with 17 additions and 14 deletions
+15 -12
View File
@@ -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
}
+2 -2
View File
@@ -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)
}