From 10bcb495143f5a25709b7ec58db0f0a413f603e8 Mon Sep 17 00:00:00 2001 From: Jon Chery Date: Fri, 7 Aug 2026 11:25:02 +0000 Subject: [PATCH] fix(P15): file-mode audit expansion (REQ-130, F13) ---ci--- project: orca phase: 15 milestone: v0.12 status: execute ---/ci--- EnforceFileModes now checks SSH key, known_hosts, master.key, master.key.sealed, server.key (0600) + orca_ssh_key.pub, server.crt (0644). Missing files skipped (may not exist before init or after step-ca migration). All security tests pass. Build green. --- internal/security/ca.go | 61 +++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/internal/security/ca.go b/internal/security/ca.go index 3dcde73..dc21930 100644 --- a/internal/security/ca.go +++ b/internal/security/ca.go @@ -227,28 +227,53 @@ func LoadCA(dir string) (*CA, error) { // Deprecated: v0.9 re-architecture replaces the internal CA with step-ca // (D-101/REQ-076). EnforceFileModes is retained for the dual-write window // and scheduled for deletion in v0.10-P14. See .ciagent/PRD_v0.9.md. +// EnforceFileModes checks that all security-sensitive files in dir have +// the correct permissions (REQ-033 + REQ-130, F13). Checks: ca.crt +// (0644), ca.key (0600), orca_ssh_key (0600), orca_ssh_key.pub (0644), +// known_hosts (0600), master.key (0600), master.key.sealed (0600), +// server.crt (0644), server.key (0600). Missing files are skipped (they +// may not exist yet — e.g. before init or after migration to step-ca). func EnforceFileModes(dir string) error { - certPath := filepath.Join(dir, CACertFile) - keyPath := filepath.Join(dir, CAKeyFile) - certInfo, err := os.Stat(certPath) - if err != nil { - return fmt.Errorf("EnforceFileModes: stat %s: %w", certPath, err) + // Files that must be 0600 (secrets/keys). + secretFiles := []string{ + CAKeyFile, + "orca_ssh_key", + "known_hosts", + "master.key", + "master.key.sealed", + "server.key", } - keyInfo, err := os.Stat(keyPath) - if err != nil { - return fmt.Errorf("EnforceFileModes: stat %s: %w", keyPath, err) + // Files that must be 0644 (certs/public keys). + publicFiles := []string{ + CACertFile, + "orca_ssh_key.pub", + "server.crt", } - if certInfo.Mode().Perm() != CACPEMMode { - return fmt.Errorf( - "REQ-033 violation: %s has mode %04o, want %04o — fix with `chmod %04o %s`", - certPath, certInfo.Mode().Perm(), CACPEMMode, CACPEMMode, certPath, - ) + for _, name := range secretFiles { + path := filepath.Join(dir, name) + info, err := os.Stat(path) + if err != nil { + continue // skip missing + } + if info.Mode().Perm() != 0o600 { + return fmt.Errorf( + "REQ-033/130 violation: %s has mode %04o, want 0600 — fix with `chmod 0600 %s`", + path, info.Mode().Perm(), path, + ) + } } - if keyInfo.Mode().Perm() != CAMode { - return fmt.Errorf( - "REQ-033 violation: %s has mode %04o, want %04o — fix with `chmod %04o %s`", - keyPath, keyInfo.Mode().Perm(), CAMode, CAMode, keyPath, - ) + for _, name := range publicFiles { + path := filepath.Join(dir, name) + info, err := os.Stat(path) + if err != nil { + continue // skip missing + } + if info.Mode().Perm() != 0o644 { + return fmt.Errorf( + "REQ-033/130 violation: %s has mode %04o, want 0644 — fix with `chmod 0644 %s`", + path, info.Mode().Perm(), path, + ) + } } return nil }