Files
1Panel/backend/utils/toolbox/fail2ban.go
T
巴山夜语andgcsong023 6b379389d3 WIP: refactor(service): Refactor OpenRC service manager (#8416)
* refactor(service): 重构 OpenRC 服务管理器

- 更新 IsEnabled 和 IsActive 检查逻辑,使用更可靠的命令
- 修复 ServiceExists 检查,直接使用文件路径判断
- 优化 FindServices 函数,扫描 /etc/init.d 目录
- 调整 BuildCommand 函数,支持 OpenRC 特定操作
- 修改 ParseStatus 函数,使用更新后的正则表达式

* feat(backend): 优化 Fail2ban 初始化配置以支持 Alpine 系统

- 增加对 Alpine 系统的特殊配置支持
- 改进防火墙类型检测逻辑,支持多种防火墙服务
- 增加 SSH 端口和认证日志路径的自动检测
- 优化配置文件模板,提高兼容性和安全性

* refactor(backend): 重构 SSH 日志解析功能

- 改进了对不同日志格式的支持,包括 secure, auth 和 messages 文件
- 优化了日志解析逻辑,提高了代码的可读性和可维护性
- 增加了对 RFC3339 时间格式的支持
- 改善了对失败登录尝试的解析,包括无效用户和连接关闭的情况
- 重构了日期解析和 IP 地址验证的逻辑

* refactor(upgrade): 优化升级服务中的初始化脚本选择逻辑

- 新增 selectInitScript 函数,根据系统初始化管理器类型选择合适的初始化脚本
- 支持 systemd、openrc 和 sysvinit 三种初始化管理器
- 对于 sysvinit,增加对 /etc/rc.common 文件存在性的判断,以区分不同的初始化脚本
- 默认情况下使用当前服务名称作为初始化脚本名称

* fix(upgrade): 修复升级时初始化脚本更新问题

- 修改了 criticalUpdates 数组中的服务脚本更新逻辑
- 在 selectInitScript 函数中增加了复制脚本文件的逻辑,以应对服务名和脚本名不一致的情况

* feat(snap): 添加初始化脚本到快照

- 在创建快照时,将服务脚本复制到 initscript 目录
- 然后将整个 initscript 目录复制到快照的目标目录
- 添加了日志输出,便于调试和记录

* refactor(backend): 重构快照恢复流程

- 移除了未使用的 import 语句
- 删除了注释掉的代码块
- 修改了 1Panel 服务恢复的逻辑,增加了对当前服务名称的获取
- 快照恢复过程中,根据宿主机类型,自动选择初始化服务脚本
- 添加了日志输出以提高可追踪性
- 优化了文件路径的处理方式

---------

Co-authored-by: gcsong023 <gcsong023@users.noreply.github.com>
2025-04-23 17:04:08 +08:00

270 lines
6.4 KiB
Go

package toolbox
import (
"fmt"
"os"
"regexp"
"strings"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
)
type Fail2ban struct{}
const defaultPath = "/etc/fail2ban/jail.local"
type FirewallClient interface {
Status() (bool, bool, bool)
Version() (string, error)
Operate(operate string) error
OperateSSHD(operate, ip string) error
}
func NewFail2Ban() (*Fail2ban, error) {
isExist, _ := systemctl.IsExist("fail2ban")
if isExist {
if _, err := os.Stat(defaultPath); err != nil {
if err := initLocalFile(); err != nil {
return nil, err
}
err := systemctl.Restart("fail2ban")
if err != nil {
global.LOG.Errorf("restart fail2ban failed, err: %s", err)
return nil, err
}
}
}
return &Fail2ban{}, nil
}
func (f *Fail2ban) Status() (bool, bool, bool) {
isEnable, _ := systemctl.IsEnable("fail2ban.service")
isActive, _ := systemctl.IsActive("fail2ban.service")
isExist, _ := systemctl.IsExist("fail2ban.service")
return isEnable, isActive, isExist
}
func (f *Fail2ban) Version() string {
stdout, err := cmd.Exec("fail2ban-client --version")
if err != nil {
global.LOG.Errorf("load the fail2ban version failed, err: %s", stdout)
return "-"
}
versionRe := regexp.MustCompile(`(?i)fail2ban[:\s-]*v?(\d+\.\d+\.\d+)`)
matches := versionRe.FindStringSubmatch(stdout)
if len(matches) > 1 {
return matches[1]
}
global.LOG.Errorf("Version regex failed to match output: %s", stdout)
return "-"
}
func (f *Fail2ban) Operate(operate string) error {
switch operate {
case "start", "restart", "stop", "enable", "disable":
stdout, err := systemctl.CustomAction(operate, "fail2ban")
if err != nil {
return fmt.Errorf("%s the fail2ban failed, err: %s", operate, stdout.Output)
}
return nil
case "reload":
stdout, err := cmd.Exec("fail2ban-client reload")
if err != nil {
return fmt.Errorf("fail2ban-client reload, err: %s", stdout)
}
return nil
default:
return fmt.Errorf("not support such operation: %v", operate)
}
}
func (f *Fail2ban) ReBanIPs(ips []string) error {
ipItems, _ := f.ListBanned()
stdout, err := cmd.Execf("fail2ban-client unban --all")
if err != nil {
stdout1, err := cmd.Execf("fail2ban-client set sshd banip %s", strings.Join(ipItems, " "))
if err != nil {
global.LOG.Errorf("rebanip after fail2ban-client unban --all failed, err: %s", stdout1)
}
return fmt.Errorf("fail2ban-client unban --all failed, err: %s", stdout)
}
stdout1, err := cmd.Execf("fail2ban-client set sshd banip %s", strings.Join(ips, " "))
if err != nil {
return fmt.Errorf("handle `fail2ban-client set sshd banip %s` failed, err: %s", strings.Join(ips, " "), stdout1)
}
return nil
}
func (f *Fail2ban) ListBanned() ([]string, error) {
var lists []string
stdout, err := cmd.Exec("fail2ban-client status sshd | grep 'Banned IP list:'")
if err != nil {
return lists, err
}
itemList := strings.Split(strings.Trim(stdout, "\n"), "Banned IP list:")
if len(itemList) != 2 {
return lists, nil
}
ips := strings.Fields(itemList[1])
for _, item := range ips {
if len(item) != 0 {
lists = append(lists, item)
}
}
return lists, nil
}
func (f *Fail2ban) ListIgnore() ([]string, error) {
var lists []string
stdout, err := cmd.Exec("fail2ban-client get sshd ignoreip")
if err != nil {
return lists, err
}
stdout = strings.ReplaceAll(stdout, "|", "")
stdout = strings.ReplaceAll(stdout, "`", "")
stdout = strings.ReplaceAll(stdout, "\n", "")
addrs := strings.Split(stdout, "-")
for _, addr := range addrs {
if !strings.HasPrefix(addr, " ") {
continue
}
lists = append(lists, strings.ReplaceAll(addr, " ", ""))
}
return lists, nil
}
func initLocalFile() error {
f, err := os.Create(defaultPath)
if err != nil {
return err
}
defer f.Close()
var initFile string
if systemctl.GetGlobalManager().Name() == "openrc" {
initFile = `[sshd]
enabled = true
filter = alpine-sshd
port = $ssh_port
logpath = $logpath
maxretry = 2
banaction = $banaction
[sshd-ddos]
enabled = true
filter = alpine-sshd-ddos
port = $ssh_port
logpath = /var/log/messages
maxretry = 2
[sshd-key]
enabled = true
filter = alpine-sshd-key
port = $ssh_port
logpath = /var/log/messages
maxretry = 2
`
} else {
initFile = `# DEFAULT-START
[DEFAULT]
bantime = 600
findtime = 300
maxretry = 5
banaction = $banaction
action = %(action_mwl)s
#DEFAULT-END
[sshd]
ignoreip = 127.0.0.1/8 ::1
enabled = true
filter = sshd
port = $ssh_port
maxretry = 5
findtime = 300
bantime = 600
banaction = $banaction
action = %(action_mwl)s
logpath = $logpath
maxmatches = 3
`
}
// 检测防火墙类型
banaction := detectFirewall()
// 检测SSH端口(支持Alpine的sshd_config位置)
sshPort := detectSSHPort()
// 检测日志路径(兼容Alpine的多种日志位置)
logPath := detectAuthLogPath()
// 执行变量替换
initFile = strings.ReplaceAll(initFile, "$banaction", banaction)
initFile = strings.ReplaceAll(initFile, "$logpath", logPath)
initFile = strings.ReplaceAll(initFile, "$ssh_port", sshPort)
if err := os.WriteFile(defaultPath, []byte(initFile), 0640); err != nil {
return err
}
return nil
}
func detectFirewall() string {
if active, _ := systemctl.IsActive("firewalld"); active {
return "firewallcmd-ipset"
}
if active, _ := systemctl.IsActive("ufw"); active {
return "ufw"
}
if active, _ := systemctl.IsActive("iptables"); active {
return "iptables-allports"
}
if active, _ := systemctl.IsActive("nftables"); active {
return "nftables-allports"
}
return "iptables-allports"
}
func detectAuthLogPath() string {
paths := []string{
"/var/log/auth.log", // 常见默认路径
"/var/log/messages", // Alpine系统日志
"/var/log/secure", // RHEL风格路径
"/var/log/syslog", // Debian风格路径
}
for _, path := range paths {
if _, err := os.Stat(path); err == nil {
return path
}
}
return "/var/log/auth.log"
}
func detectSSHPort() string {
// 检查标准sshd_config路径
configPaths := []string{
"/etc/ssh/sshd_config",
"/etc/sshd_config",
}
for _, path := range configPaths {
data, err := os.ReadFile(path)
if err != nil {
continue
}
lines := strings.Split(string(data), "\n")
for _, line := range lines {
if strings.HasPrefix(strings.TrimSpace(line), "Port") {
parts := strings.Fields(line)
if len(parts) >= 2 {
return parts[1]
}
}
}
}
return "22" // 默认SSH端口
}