mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-10 07:26:35 +08:00
Some checks failed
SonarCloud Scan / SonarCloud (push) Has been cancelled
* 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>
269 lines
6.4 KiB
Go
269 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端口
|
||
}
|