package sysadm import ( "fmt" "os" "os/exec" ) // dropInPath is the sshd config snippet this tool owns. Modern OpenSSH // (Debian/Ubuntu/RHEL defaults) includes /etc/ssh/sshd_config.d/*.conf from // the main sshd_config automatically; DropInIncluded checks that assumption. const dropInPath = "/etc/ssh/sshd_config.d/50-ssh-access-tui.conf" const dropInTemplate = `# Managed by ssh-access-tui. Do not edit by hand — re-run # "ssh-access-tui install" to regenerate this file. # # Baseline (defense-in-depth) restrictions for all jump/forward-only # accounts. The authoritative per-user host whitelist lives in each # account's authorized_keys "permitopen=" options, not here — this block # only backstops that in case a key's options were ever mis-set. Match Group ` + JumpGroup + ` PermitTTY no X11Forwarding no AllowAgentForwarding no AllowStreamLocalForwarding no AllowTcpForwarding local PermitTunnel no Match all # VERBOSE logging records which destination host:port each forwarded # channel was opened to — needed for the audit trail of "who connected # where", since plain port-forward sessions don't show up in ` + "`who`" + `. LogLevel VERBOSE ` // DropInIncluded reports whether the main sshd_config appears to include // the sshd_config.d drop-in directory. If not, the caller should tell the // admin to add "Include /etc/ssh/sshd_config.d/*.conf" manually. func DropInIncluded() bool { data, err := os.ReadFile("/etc/ssh/sshd_config") if err != nil { return false } return contains(string(data), "sshd_config.d") } // InstallSSHDConfig writes the managed drop-in, validates it with // `sshd -t`, and reloads the running sshd. It refuses to reload (leaving the // previous config live) if validation fails. func InstallSSHDConfig() error { if err := RequireRoot(); err != nil { return err } if err := os.WriteFile(dropInPath, []byte(dropInTemplate), 0o644); err != nil { return fmt.Errorf("write %s: %w", dropInPath, err) } if out, err := exec.Command("sshd", "-t").CombinedOutput(); err != nil { return fmt.Errorf("sshd config now invalid, NOT reloaded — fix and rerun: %s", out) } if err := reloadSSHD(); err != nil { return fmt.Errorf("config valid but reload failed: %w", err) } return nil } func reloadSSHD() error { // Service name differs across distros; try both, ignore first failure. if err := exec.Command("systemctl", "reload", "ssh").Run(); err == nil { return nil } return exec.Command("systemctl", "reload", "sshd").Run() } func contains(s, substr string) bool { for i := 0; i+len(substr) <= len(s); i++ { if s[i:i+len(substr)] == substr { return true } } return false }