This commit is contained in:
redlineo committed 2026-07-22 14:14:53 +03:00
commit 23c7133fa9
33 files changed
+2896

No files matched your search

+123
View File
@@ -0,0 +1,123 @@
// Command ssh-access-tui is a terminal UI for administering a Linux jump
// host: creating jump/forward-only user accounts, scoping each one's
// allowed destination hosts via SSH group membership, exporting encrypted
// key/config archives for delivery, locking departed users, and viewing who
// is currently connected.
package main
import (
"flag"
"fmt"
"os"
tea "github.com/charmbracelet/bubbletea"
"ssh-access-tui/internal/audit"
"ssh-access-tui/internal/config"
"ssh-access-tui/internal/store"
"ssh-access-tui/internal/sysadm"
"ssh-access-tui/internal/ui"
)
const defaultConfigPath = "/etc/ssh-access-tui/config.yaml"
func main() {
configPath := flag.String("config", defaultConfigPath, "путь к конфигурации")
flag.Parse()
args := flag.Args()
sub := ""
if len(args) > 0 {
sub = args[0]
}
cfg, err := config.Load(*configPath)
if err != nil {
fatal(err)
}
switch sub {
case "install":
runInstall(*configPath, cfg)
case "audit-tail":
if err := audit.TailJournal(cfg.AuditLog); err != nil {
fatal(err)
}
default:
runTUI(cfg)
}
}
func runTUI(cfg config.Config) {
if err := sysadm.RequireRoot(); err != nil {
fatal(fmt.Errorf("%w (управление пользователями и authorized_keys требует привилегий root)", err))
}
if cfg.JumpHost == "" {
fatal(fmt.Errorf("jump_host не задан в %s — укажите публичный адрес этого хоста для генерации клиентских конфигов", defaultConfigPath))
}
st, err := store.New(cfg.StateDir)
if err != nil {
fatal(err)
}
p := tea.NewProgram(ui.New(cfg, st), tea.WithAltScreen())
if _, err := p.Run(); err != nil {
fatal(err)
}
}
func runInstall(configPath string, cfg config.Config) {
if err := sysadm.RequireRoot(); err != nil {
fatal(err)
}
if err := os.MkdirAll(cfg.StateDir, 0o750); err != nil {
fatal(err)
}
if err := os.MkdirAll(cfg.ExportDir, 0o750); err != nil {
fatal(err)
}
if err := os.MkdirAll(cfg.ScratchDir, 0o700); err != nil {
fatal(err)
}
if err := os.MkdirAll(dirOf(cfg.AuditLog), 0o750); err != nil {
fatal(err)
}
if err := os.MkdirAll(dirOf(configPath), 0o755); err != nil {
fatal(err)
}
if cfg.JumpHost == "" {
fmt.Print("Публичный адрес этого jump-хоста (как к нему подключаются пользователи): ")
fmt.Scanln(&cfg.JumpHost)
}
if err := config.Save(configPath, cfg); err != nil {
fatal(err)
}
if err := sysadm.EnsureJumpGroup(); err != nil {
fatal(err)
}
if err := sysadm.InstallSSHDConfig(); err != nil {
fatal(err)
}
if !sysadm.DropInIncluded() {
fmt.Println("ВНИМАНИЕ: /etc/ssh/sshd_config не похож на включающий sshd_config.d/*.conf.")
fmt.Println("Добавьте туда вручную строку: Include /etc/ssh/sshd_config.d/*.conf")
}
fmt.Println("Установка завершена.")
fmt.Printf("Конфигурация: %s\n", configPath)
fmt.Printf("Состояние (git-репозиторий): %s\n", cfg.StateDir)
fmt.Printf("Архивы экспорта: %s\n", cfg.ExportDir)
fmt.Println("Не забудьте включить фоновый сборщик журнала: systemctl enable --now ssh-access-tui-audit.service")
}
func dirOf(path string) string {
for i := len(path) - 1; i >= 0; i-- {
if path[i] == '/' {
return path[:i]
}
}
return "."
}
func fatal(err error) {
fmt.Fprintln(os.Stderr, "ssh-access-tui:", err)
os.Exit(1)
}