diff --git a/internal/sshutil/sshutil.go b/internal/sshutil/sshutil.go index d97eb19..a6e5cd4 100644 --- a/internal/sshutil/sshutil.go +++ b/internal/sshutil/sshutil.go @@ -8,6 +8,7 @@ import ( "os/exec" "path/filepath" "strings" + "syscall" "time" ) @@ -134,11 +135,14 @@ func RepairKnownHosts(ctx context.Context, logger *log.Logger, knownHostsFiles [ } func removeKnownHostEntry(ctx context.Context, logger *log.Logger, file string, entry string) { + uid, gid, mode := captureOwnership(file) + runCtx, cancel := context.WithTimeout(ctx, 8*time.Second) defer cancel() cmd := exec.CommandContext(runCtx, "ssh-keygen", "-R", entry, "-f", file) out, err := cmd.CombinedOutput() + restoreOwnership(file, file+".old", uid, gid, mode) if err == nil { logf(logger, "known_hosts repaired: removed %s from %s", entry, file) return @@ -151,6 +155,36 @@ func removeKnownHostEntry(ctx context.Context, logger *log.Logger, file string, logf(logger, "warning: known_hosts cleanup failed for %s in %s: %v: %s", entry, file, err, strings.TrimSpace(string(out))) } +func captureOwnership(path string) (int, int, os.FileMode) { + info, err := os.Stat(path) + if err != nil { + return -1, -1, 0 + } + st, ok := info.Sys().(*syscall.Stat_t) + if !ok { + return -1, -1, info.Mode().Perm() + } + return int(st.Uid), int(st.Gid), info.Mode().Perm() +} + +func restoreOwnership(path string, backupPath string, uid int, gid int, mode os.FileMode) { + if uid < 0 || gid < 0 { + return + } + for _, candidate := range []string{path, backupPath} { + if candidate == "" { + continue + } + if _, err := os.Stat(candidate); err != nil { + continue + } + _ = os.Chown(candidate, uid, gid) + if mode != 0 { + _ = os.Chmod(candidate, mode) + } + } +} + func logf(logger *log.Logger, format string, args ...any) { if logger != nil { logger.Printf(format, args...)