package integrations import ( "fmt" "net" "os" "time" "golang.org/x/crypto/ssh" "github.com/swissmakers/fail2ban-ui/internal/config" ) type mikrotikIntegration struct{} func init() { Register(&mikrotikIntegration{}) } func (m *mikrotikIntegration) ID() string { return "mikrotik" } func (m *mikrotikIntegration) DisplayName() string { return "Mikrotik RouterOS" } func (m *mikrotikIntegration) Validate(cfg config.AdvancedActionsConfig) error { if cfg.Mikrotik.Host == "" { return fmt.Errorf("mikrotik host is required") } if cfg.Mikrotik.Username == "" { return fmt.Errorf("mikrotik username is required") } if cfg.Mikrotik.Password == "" && cfg.Mikrotik.SSHKeyPath == "" { return fmt.Errorf("mikrotik password or SSH key path is required") } if cfg.Mikrotik.AddressList == "" { return fmt.Errorf("mikrotik address list is required") } return nil } func (m *mikrotikIntegration) BlockIP(req Request) error { if err := m.Validate(req.Config); err != nil { return err } cmd := fmt.Sprintf(`/ip firewall address-list add list=%s address=%s comment="Fail2ban-UI permanent block"`, req.Config.Mikrotik.AddressList, req.IP) return m.runCommand(req, cmd) } func (m *mikrotikIntegration) UnblockIP(req Request) error { if err := m.Validate(req.Config); err != nil { return err } cmd := fmt.Sprintf(`/ip firewall address-list remove [/ip firewall address-list find address=%s list=%s]`, req.IP, req.Config.Mikrotik.AddressList) return m.runCommand(req, cmd) } func (m *mikrotikIntegration) runCommand(req Request, command string) error { cfg := req.Config.Mikrotik authMethods := []ssh.AuthMethod{} if cfg.Password != "" { authMethods = append(authMethods, ssh.Password(cfg.Password)) } if cfg.SSHKeyPath != "" { key, err := os.ReadFile(cfg.SSHKeyPath) if err != nil { return fmt.Errorf("failed to read mikrotik ssh key: %w", err) } signer, err := ssh.ParsePrivateKey(key) if err != nil { return fmt.Errorf("failed to parse mikrotik ssh key: %w", err) } authMethods = append(authMethods, ssh.PublicKeys(signer)) } if len(authMethods) == 0 { return fmt.Errorf("no authentication method available for mikrotik") } port := cfg.Port if port == 0 { port = 22 } clientCfg := &ssh.ClientConfig{ User: cfg.Username, Auth: authMethods, HostKeyCallback: ssh.InsecureIgnoreHostKey(), Timeout: 10 * time.Second, } address := net.JoinHostPort(cfg.Host, fmt.Sprintf("%d", port)) client, err := ssh.Dial("tcp", address, clientCfg) if err != nil { return fmt.Errorf("failed to connect to mikrotik: %w", err) } defer client.Close() session, err := client.NewSession() if err != nil { return fmt.Errorf("failed to create mikrotik ssh session: %w", err) } defer session.Close() if req.Logger != nil { req.Logger("Running Mikrotik command: %s", command) } output, err := session.CombinedOutput(command) if err != nil { return fmt.Errorf("mikrotik command failed: %w (output: %s)", err, string(output)) } if req.Logger != nil { req.Logger("Mikrotik command output: %s", string(output)) } return nil }