2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 14:57:16 +08:00

feat: fail2ban plugin

This commit is contained in:
耗子
2023-07-30 03:50:55 +08:00
parent 26ef34ef9b
commit 7e8ef74bee
10 changed files with 1156 additions and 4 deletions

View File

@@ -0,0 +1,459 @@
package fail2ban
import (
"regexp"
"strings"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/spf13/cast"
"panel/app/models"
"panel/app/http/controllers"
"panel/app/services"
"panel/pkg/tools"
)
type Fail2banController struct {
website services.Website
}
func NewFail2banController() *Fail2banController {
return &Fail2banController{
website: services.NewWebsiteImpl(),
}
}
type Jail struct {
Name string `json:"name"`
Enabled bool `json:"enabled"`
LogPath string `json:"log_path"`
MaxRetry int `json:"max_retry"`
FindTime int `json:"find_time"`
BanTime int `json:"ban_time"`
}
// Status 获取运行状态
func (c *Fail2banController) Status(ctx http.Context) {
if !controllers.Check(ctx, "fail2ban") {
return
}
status := tools.ExecShell("systemctl status fail2ban | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
return
}
if status == "active" {
controllers.Success(ctx, true)
} else {
controllers.Success(ctx, false)
}
}
// Reload 重载配置
func (c *Fail2banController) Reload(ctx http.Context) {
if !controllers.Check(ctx, "fail2ban") {
return
}
tools.ExecShell("systemctl reload fail2ban")
status := tools.ExecShell("systemctl status fail2ban | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
return
}
if status == "active" {
controllers.Success(ctx, true)
} else {
controllers.Success(ctx, false)
}
}
// Restart 重启服务
func (c *Fail2banController) Restart(ctx http.Context) {
if !controllers.Check(ctx, "fail2ban") {
return
}
tools.ExecShell("systemctl restart fail2ban")
status := tools.ExecShell("systemctl status fail2ban | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
return
}
if status == "active" {
controllers.Success(ctx, true)
} else {
controllers.Success(ctx, false)
}
}
// Start 启动服务
func (c *Fail2banController) Start(ctx http.Context) {
if !controllers.Check(ctx, "fail2ban") {
return
}
tools.ExecShell("systemctl start fail2ban")
status := tools.ExecShell("systemctl status fail2ban | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
return
}
if status == "active" {
controllers.Success(ctx, true)
} else {
controllers.Success(ctx, false)
}
}
// Stop 停止服务
func (c *Fail2banController) Stop(ctx http.Context) {
if !controllers.Check(ctx, "fail2ban") {
return
}
tools.ExecShell("systemctl stop fail2ban")
status := tools.ExecShell("systemctl status fail2ban | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
return
}
if status != "active" {
controllers.Success(ctx, true)
} else {
controllers.Success(ctx, false)
}
}
// List 所有 Fail2ban 规则
func (c *Fail2banController) List(ctx http.Context) {
if !controllers.Check(ctx, "fail2ban") {
return
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
raw := tools.ReadFile("/etc/fail2ban/jail.local")
if len(raw) == 0 {
controllers.Error(ctx, http.StatusBadRequest, "Fail2ban 规则为空")
return
}
jailList := regexp.MustCompile(`\[(.*?)]`).FindAllStringSubmatch(raw, -1)
if len(jailList) == 0 {
controllers.Error(ctx, http.StatusBadRequest, "Fail2ban 规则为空")
return
}
var jails []Jail
for i, jail := range jailList {
if i == 0 {
continue
}
jailName := jail[1]
jailRaw := tools.Cut(raw, "# "+jailName+"-START", "# "+jailName+"-END")
if len(jailRaw) == 0 {
continue
}
jailEnabled := strings.Contains(jailRaw, "enabled = true")
jailLogPath := regexp.MustCompile(`logpath = (.*)`).FindStringSubmatch(jailRaw)
jailMaxRetry := regexp.MustCompile(`maxretry = (.*)`).FindStringSubmatch(jailRaw)
jailFindTime := regexp.MustCompile(`findtime = (.*)`).FindStringSubmatch(jailRaw)
jailBanTime := regexp.MustCompile(`bantime = (.*)`).FindStringSubmatch(jailRaw)
jails = append(jails, Jail{
Name: jailName,
Enabled: jailEnabled,
LogPath: jailLogPath[1],
MaxRetry: cast.ToInt(jailMaxRetry[1]),
FindTime: cast.ToInt(jailFindTime[1]),
BanTime: cast.ToInt(jailBanTime[1]),
})
}
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(jails) {
controllers.Success(ctx, http.Json{
"total": 0,
"items": []Jail{},
})
return
}
if endIndex > len(jails) {
endIndex = len(jails)
}
pagedJails := jails[startIndex:endIndex]
controllers.Success(ctx, http.Json{
"total": len(jails),
"items": pagedJails,
})
}
// Add 添加 Fail2ban 规则
func (c *Fail2banController) Add(ctx http.Context) {
if !controllers.Check(ctx, "fail2ban") {
return
}
validator, err := ctx.Request().Validate(map[string]string{
"name": "required",
"type": "required|in:website,service",
"maxretry": "required",
"findtime": "required",
"bantime": "required",
"website_mode": "required_if:type,website",
"website_path": "required_if:type,website",
})
if err != nil {
controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
return
}
jailName := ctx.Request().Input("name")
jailType := ctx.Request().Input("type")
jailMaxRetry := ctx.Request().Input("maxretry")
jailFindTime := ctx.Request().Input("findtime")
jailBanTime := ctx.Request().Input("bantime")
jailWebsiteMode := ctx.Request().Input("website_mode")
jailWebsitePath := ctx.Request().Input("website_path")
raw := tools.ReadFile("/etc/fail2ban/jail.local")
if strings.Contains(raw, "["+jailName+"]") || (strings.Contains(raw, "["+jailName+"]"+"-cc") && jailWebsiteMode == "cc") || (strings.Contains(raw, "["+jailName+"]"+"-path") && jailWebsiteMode == "path") {
controllers.Error(ctx, http.StatusUnprocessableEntity, "规则已存在")
return
}
switch jailType {
case "website":
var website models.Website
err := facades.Orm().Query().Where("name", jailName).FirstOrFail(&website)
if err != nil {
controllers.Error(ctx, http.StatusUnprocessableEntity, "网站不存在")
return
}
config, err := c.website.GetConfig(int(website.ID))
if err != nil {
controllers.Error(ctx, http.StatusUnprocessableEntity, "获取网站配置失败")
return
}
var ports string
for _, port := range config.Ports {
if len(strings.Split(port, " ")) > 1 {
ports += strings.Split(port, " ")[0] + ","
} else {
ports += port + ","
}
}
rule := `
# ` + jailName + `-` + jailWebsiteMode + `-START
[` + jailName + `-` + jailWebsiteMode + `]
enabled = true
filter = haozi-` + jailName + `-` + jailWebsiteMode + `
port = ` + ports + `
maxretry = ` + jailMaxRetry + `
findtime = ` + jailFindTime + `
bantime = ` + jailBanTime + `
action = %(action_mwl)s
logpath = /www/wwwlogs/` + website.Name + `.log
# ` + jailName + `-` + jailWebsiteMode + `-END
`
raw += rule
tools.WriteFile("/etc/fail2ban/jail.local", raw, 0644)
var filter string
if jailWebsiteMode == "cc" {
filter = `
[Definition]
failregex = ^<HOST>\s-.*HTTP/.*$
ignoreregex =
`
} else {
filter = `
[Definition]
failregex = ^<HOST>\s-.*\s` + jailWebsitePath + `.*HTTP/.*$
ignoreregex =
`
}
tools.WriteFile("/etc/fail2ban/filter.d/haozi-"+jailName+"-"+jailWebsiteMode+".conf", filter, 0644)
case "service":
var logPath string
var filter string
var port string
switch jailName {
case "ssh":
if tools.IsDebian() {
logPath = "/var/log/auth.log"
} else {
logPath = "/var/log/secure"
}
filter = "sshd"
port = tools.ExecShell("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
case "mysql":
logPath = "/www/server/mysql/mysql-error.log"
filter = "mysqld-auth"
port = tools.ExecShell("cat /www/server/mysql/conf/my.cnf | grep 'port' | head -n 1 | awk '{print $3}'")
case "pure-ftpd":
logPath = "/var/log/messages"
filter = "pure-ftpd"
port = tools.ExecShell(`cat /www/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`)
default:
controllers.Error(ctx, http.StatusUnprocessableEntity, "未知服务")
return
}
if len(port) == 0 {
controllers.Error(ctx, http.StatusUnprocessableEntity, "获取服务端口失败,请检查是否安装")
return
}
rule := `
# ` + jailName + `-START
[` + jailName + `]
enabled = true
filter = ` + filter + `
port = ` + port + `
maxretry = ` + jailMaxRetry + `
findtime = ` + jailFindTime + `
bantime = ` + jailBanTime + `
action = %(action_mwl)s
logpath = ` + logPath + `
# ` + jailName + `-END
`
raw += rule
tools.WriteFile("/etc/fail2ban/jail.local", raw, 0644)
}
tools.ExecShell("fail2ban-client reload")
controllers.Success(ctx, nil)
}
// Delete 删除规则
func (c *Fail2banController) Delete(ctx http.Context) {
if !controllers.Check(ctx, "fail2ban") {
return
}
jailName := ctx.Request().Input("name")
raw := tools.ReadFile("/etc/fail2ban/jail.local")
if !strings.Contains(raw, "["+jailName+"]") {
controllers.Error(ctx, http.StatusUnprocessableEntity, "规则不存在")
return
}
rule := tools.Cut(raw, "# "+jailName+"-START", "# "+jailName+"-END")
raw = strings.Replace(raw, "\n# "+jailName+"-START"+rule+"# "+jailName+"-END", "", -1)
raw = strings.TrimSpace(raw)
tools.WriteFile("/etc/fail2ban/jail.local", raw, 0644)
tools.ExecShell("fail2ban-client reload")
controllers.Success(ctx, nil)
}
// BanList 获取封禁列表
func (c *Fail2banController) BanList(ctx http.Context) {
if !controllers.Check(ctx, "fail2ban") {
return
}
name := ctx.Request().Query("name")
if len(name) == 0 {
controllers.Error(ctx, http.StatusUnprocessableEntity, "缺少参数")
return
}
currentlyBan := tools.ExecShell(`fail2ban-client status ` + name + ` | grep "Currently banned" | awk '{print $4}'`)
totalBan := tools.ExecShell(`fail2ban-client status ` + name + ` | grep "Total banned" | awk '{print $4}'`)
bannedIp := tools.ExecShell(`fail2ban-client status ` + name + ` | grep "Banned IP list" | awk -F ":" '{print $2}'`)
bannedIpList := strings.Split(bannedIp, " ")
var list []map[string]string
for _, ip := range bannedIpList {
if len(ip) > 0 {
list = append(list, map[string]string{
"name": name,
"ip": ip,
})
}
}
controllers.Success(ctx, http.Json{
"currentlyBan": currentlyBan,
"totalBan": totalBan,
"bannedIpList": list,
})
}
// Unban 解封
func (c *Fail2banController) Unban(ctx http.Context) {
if !controllers.Check(ctx, "fail2ban") {
return
}
name := ctx.Request().Input("name")
ip := ctx.Request().Input("ip")
if len(name) == 0 || len(ip) == 0 {
controllers.Error(ctx, http.StatusUnprocessableEntity, "缺少参数")
return
}
tools.ExecShell("fail2ban-client set " + name + " unbanip " + ip)
controllers.Success(ctx, nil)
}
// SetWhiteList 设置白名单
func (c *Fail2banController) SetWhiteList(ctx http.Context) {
if !controllers.Check(ctx, "fail2ban") {
return
}
ip := ctx.Request().Input("ip")
if len(ip) == 0 {
controllers.Error(ctx, http.StatusUnprocessableEntity, "缺少参数")
return
}
raw := tools.ReadFile("/etc/fail2ban/jail.local")
// 正则替换
reg := regexp.MustCompile(`ignoreip\s*=\s*.*\n`)
if reg.MatchString(raw) {
raw = reg.ReplaceAllString(raw, "ignoreip = "+ip+"\n")
} else {
controllers.Error(ctx, http.StatusInternalServerError, "解析Fail2ban规则失败Fail2ban可能已损坏")
return
}
tools.WriteFile("/etc/fail2ban/jail.local", raw, 0644)
tools.ExecShell("fail2ban-client reload")
controllers.Success(ctx, nil)
}
// GetWhiteList 获取白名单
func (c *Fail2banController) GetWhiteList(ctx http.Context) {
if !controllers.Check(ctx, "fail2ban") {
return
}
raw := tools.ReadFile("/etc/fail2ban/jail.local")
reg := regexp.MustCompile(`ignoreip\s*=\s*(.*)\n`)
if reg.MatchString(raw) {
ignoreIp := reg.FindStringSubmatch(raw)[1]
controllers.Success(ctx, ignoreIp)
} else {
controllers.Error(ctx, http.StatusInternalServerError, "解析Fail2ban规则失败Fail2ban可能已损坏")
}
}

View File

@@ -0,0 +1,13 @@
package fail2ban
var (
Name = "Fail2ban"
Description = "Fail2ban 扫描系统日志文件并从中找出多次尝试失败的IP地址将该IP地址加入防火墙的拒绝访问列表中。"
Slug = "fail2ban"
Version = "1.0.0"
Requires = []string{}
Excludes = []string{}
Install = `bash /www/panel/scripts/fail2ban/install.sh`
Uninstall = `bash /www/panel/scripts/fail2ban/uninstall.sh`
Update = `bash /www/panel/scripts/fail2ban/update.sh`
)

View File

@@ -5,6 +5,7 @@ import (
"github.com/goravel/framework/facades"
"panel/app/models"
"panel/app/plugins/fail2ban"
"panel/app/plugins/mysql57"
"panel/app/plugins/mysql80"
"panel/app/plugins/openresty"
@@ -168,6 +169,17 @@ func (r *PluginImpl) All() []PanelPlugin {
Uninstall: supervisor.Uninstall,
Update: supervisor.Update,
})
p = append(p, PanelPlugin{
Name: fail2ban.Name,
Description: fail2ban.Description,
Slug: fail2ban.Slug,
Version: fail2ban.Version,
Requires: fail2ban.Requires,
Excludes: fail2ban.Excludes,
Install: fail2ban.Install,
Uninstall: fail2ban.Uninstall,
Update: fail2ban.Update,
})
return p
}

View File

@@ -0,0 +1,335 @@
<!--
Name: Fail2ban管理器
Author: 耗子
Date: 2023-07-30
-->
<title>Fail2ban</title>
<div class="layui-fluid" id="component-tabs">
<div class="layui-row">
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card-header">Fail2ban 运行状态</div>
<div class="layui-card-body">
<blockquote id="fail2ban-status" class="layui-elem-quote layui-quote-nm">当前状态:<span
class="layui-badge layui-bg-black">获取中</span></blockquote>
<div class="layui-btn-container" style="padding-top: 30px;">
<button id="fail2ban-start" class="layui-btn">启动</button>
<button id="fail2ban-stop" class="layui-btn layui-btn-danger">停止</button>
<button id="fail2ban-restart" class="layui-btn layui-btn-warm">重启</button>
<button id="fail2ban-reload" class="layui-btn layui-btn-normal">重载</button>
</div>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
<legend>基本设置</legend>
</fieldset>
<div class="layui-form" lay-filter="fail2ban_setting">
<div class="layui-form-item">
<label class="layui-form-label" style="font-size: 13px;">IP白名单</label>
<div class="layui-input-inline">
<input type="text" name="fail2ban_white_list" value="获取中ing..." class="layui-input"
disabled>
</div>
<div class="layui-form-mid layui-word-aux">IP白名单以英文逗号,分隔</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn layui-btn-sm" lay-submit lay-filter="fail2ban_setting_submit">
确认修改
</button>
</div>
</div>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">Fail2ban 规则列表</div>
<div class="layui-card-body">
<table class="layui-hide" id="fail2ban-rule-list" lay-filter="fail2ban-rule-list"></table>
<!-- 顶部工具栏 -->
<script type="text/html" id="fail2ban-rule-list-bar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="add_rule">新建规则</button>
</div>
</script>
<!-- 右侧管理 -->
<script type="text/html" id="fail2ban-rule-list-control">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="view">查看</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
</div>
</div>
</div>
</div>
</div>
<script>
layui.use(['index', 'form', 'table', 'view'], function () {
let $ = layui.$
, admin = layui.admin
, table = layui.table
, form = layui.form
, view = layui.view;
// 获取运行状态并渲染
admin.req({
url: "/api/plugins/fail2ban/status"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
return false;
}
if (result.data) {
$('#fail2ban-status').html('当前状态:<span class="layui-badge layui-bg-green">运行中</span>');
} else {
$('#fail2ban-status').html('当前状态:<span class="layui-badge layui-bg-red">已停止</span>');
}
}
});
// 获取白名单并渲染
admin.req({
url: "/api/plugins/fail2ban/whiteList"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
return false;
}
$('input[name=fail2ban_white_list]').val(result.data);
$('input').attr('disabled', false);
form.render();
}
});
// 监听提交
form.on('submit(fail2ban_setting_submit)', function (data) {
data.field.ip = $('input[name=fail2ban_white_list]').val();
index = layer.msg('请稍候...', {icon: 16, time: 0, shade: 0.3});
admin.req({
url: "/api/plugins/fail2ban/whiteList"
, type: 'post'
, data: data.field
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
layer.msg('设置成功', {icon: 1});
}
});
return false;
});
let websiteList = [];
admin.req({
url: '/api/panel/website/list?page=1&limit=10000'
, type: 'get'
, async: false
, success: function (res) {
websiteList = res.data.items;
}
});
// 获取规则列表
table.render({
elem: '#fail2ban-rule-list'
, url: '/api/plugins/fail2ban/list'
, toolbar: '#fail2ban-rule-list-bar'
, title: 'Fail2ban 规则列表'
, cols: [[
{field: 'name', title: '规则名', fixed: 'left', unresize: true, sort: true}
, {
field: 'enabled', title: '状态', templet: function (d) {
if (d.enabled) {
return '<span class="layui-badge layui-bg-green">启用</span>';
} else {
return '<span class="layui-badge layui-bg-gray">禁用</span>';
}
}, sort: true
}
, {field: 'max_retry', title: '最大尝试次数', sort: true}
, {field: 'ban_time', title: '封禁时间', sort: true}
, {field: 'find_time', title: '查找时间', sort: true}
, {field: 'log_path', title: '日志路径'}
, {fixed: 'right', title: '操作', toolbar: '#fail2ban-rule-list-control', width: 150}
]]
, page: true
, text: {
none: '无数据'
}
, parseData: function (res) {
return {
"code": res.code,
"msg": res.message,
"count": res.data.total,
"data": res.data.items
};
}
});
// 头工具栏事件
table.on('toolbar(fail2ban-rule-list)', function (obj) {
if (obj.event === 'add_rule') {
index = layer.msg('加载中...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.popup({
title: '新建Fail2ban规则'
, area: ['600px', '600px']
, id: 'LAY-popup-fail2ban-rule-add'
, success: function () {
layer.close(index);
view(this.id).render('plugins/fail2ban/add_rule', {
websiteList: websiteList
}).done(function () {
form.render(null, 'LAY-popup-fail2ban-rule-add');
});
}
});
}
});
// 行工具事件
table.on('tool(fail2ban-rule-list)', function (obj) {
let data = obj.data;
if (obj.event === 'del') {
layer.confirm('确定要删除 <b style="color: red;">' + data.name + '</b> 吗?', function (index) {
index = layer.msg('请稍等...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: "/api/plugins/fail2ban/delete"
, type: 'post'
, data: data
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
obj.del();
layer.alert(data.name + '删除成功!');
}
});
});
} else if (obj.event === 'view') {
index = layer.msg('加载中...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: "/api/plugins/fail2ban/ban?name=" + data.name
, type: 'get'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
admin.popup({
title: '查看Fail2ban规则'
, area: ['600px', '600px']
, id: 'LAY-popup-fail2ban-rule-view'
, success: function () {
layer.close(index);
view(this.id).render('plugins/fail2ban/view_rule', {
data: data
, banList: result.data
}).done(function () {
form.render(null, 'LAY-popup-fail2ban-rule-view');
});
}
});
}
});
}
});
$('#fail2ban-start').click(function () {
layer.confirm('确定要启动Fail2ban吗', {
btn: ['启动', '取消']
}, function () {
index = layer.msg('请稍等...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: "/api/plugins/fail2ban/start"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
admin.events.refresh();
layer.alert('Fail2ban启动成功');
}
});
});
});
$('#fail2ban-stop').click(function () {
layer.confirm('停止Fail2ban将导致Fail2ban防护失效是否继续停止', {
btn: ['停止', '取消']
}, function () {
index = layer.msg('请稍等...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: "/api/plugins/fail2ban/stop"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
admin.events.refresh();
layer.alert('Fail2ban停止成功');
}
});
});
});
$('#fail2ban-restart').click(function () {
layer.confirm('确定要重启Fail2ban吗', {
btn: ['重启', '取消']
}, function () {
index = layer.msg('请稍等...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: "/api/plugins/fail2ban/restart"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
admin.events.refresh();
layer.alert('Fail2ban重启成功');
}
});
});
});
$('#fail2ban-reload').click(function () {
index = layer.msg('请稍等...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: "/api/plugins/fail2ban/reload"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
layer.alert('Fail2ban重载成功');
}
});
});
});
</script>

View File

@@ -0,0 +1,170 @@
<!--
Name: Fail2ban管理器 - 新建规则
Author: 耗子
Date: 2023-07-30
-->
<script type="text/html" template lay-done="layui.data.sendParams(d.params)">
<form class="layui-form" action="" lay-filter="add-pure-ftpd-user-form">
<div class="layui-form-item">
<label class="layui-form-label">类型</label>
<div class="layui-input-block">
<input type="radio" lay-filter="add-fail2ban-rule-type-radio" name="type" value="website"
title="网站" checked="">
<input type="radio" lay-filter="add-fail2ban-rule-type-radio" name="type" value="service"
title="服务">
</div>
</div>
<div id="add-fail2ban-rule-website-input" class="layui-form-item">
<label class="layui-form-label">网站</label>
<div class="layui-input-block">
<select name="website" lay-filter="add-fail2ban-rule-website">
{{# layui.each(d.params.websiteList, function(index, item){ }}
{{# if(index == 0){ }}
<option value="{{ item.name }}" selected="">{{ item.name }}</option>
{{# }else{ }}
<option value="{{ item.name }}">{{ item.name }}</option>
{{# } }}
{{# }); }}
</select>
</div>
</div>
<div id="add-fail2ban-rule-service-input" class="layui-form-item">
<label class="layui-form-label">服务</label>
<div class="layui-input-block">
<select name="service" lay-filter="add-fail2ban-rule-service">
<option value="ssh" selected="">ssh</option>
<option value="mysql">mysql</option>
<option value="pure-ftpd">pure-ftpd</option>
</select>
</div>
</div>
<div id="add-fail2ban-rule-website-mode-input" class="layui-form-item">
<label class="layui-form-label">网站模式</label>
<div class="layui-input-block">
<input type="radio" lay-filter="add-fail2ban-rule-website-mode-radio" name="website_mode" value="cc"
title="CC" checked="">
<input type="radio" lay-filter="add-fail2ban-rule-website-mode-radio" name="website_mode" value="path"
title="目录">
</div>
</div>
<div id="add-fail2ban-rule-website-path-input" class="layui-form-item">
<label class="layui-form-label">网站目录</label>
<div class="layui-input-block">
<input type="text" name="website_path"
lay-verify="required" placeholder="输入一个禁止访问的目录" class="layui-input"
value="/admin">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">最大重试</label>
<div class="layui-input-block">
<input type="text" name="maxretry"
lay-verify="required" placeholder="单位:次" class="layui-input"
value="30">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">周期</label>
<div class="layui-input-block">
<input type="text" name="findtime"
lay-verify="required" placeholder="单位:秒" class="layui-input"
value="300">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">禁止时间</label>
<div class="layui-input-block">
<input type="text" name="bantime"
lay-verify="required" placeholder="单位:秒" class="layui-input"
value="600">
</div>
<div class="layui-form-mid layui-word-aux">
在设置周期内()有超过最大重试()的IP访问将禁止该IP禁止时间()
</div>
<div class="layui-form-mid layui-word-aux">
<span style="color: red;">防护端口自动获取如果修改了规则项对应的端口请删除重新添加否则防护可能不会生效</span>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<div class="layui-footer">
<button class="layui-btn" lay-submit="" lay-filter="add-fail2ban-rule-submit">立即提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</div>
</form>
</script>
<script>
layui.data.sendParams = function (params) {
layui.use(['admin', 'form', 'jquery', 'table'], function () {
var $ = layui.jquery
, admin = layui.admin
, layer = layui.layer
, form = layui.form
, table = layui.table;
form.render();
// 监听类型选择
$('#add-fail2ban-rule-service-input').hide();
form.on('radio(add-fail2ban-rule-type-radio)', function (data) {
if (data.value == 'website') {
$('#add-fail2ban-rule-website-input').show();
$('#add-fail2ban-rule-website-mode-input').show();
$('#add-fail2ban-rule-service-input').hide();
if ($('input[name="website_mode"]:checked').val() == 'cc') {
$('#add-fail2ban-rule-website-path-input').hide();
} else {
$('#add-fail2ban-rule-website-path-input').show();
}
} else if (data.value == 'service') {
$('#add-fail2ban-rule-website-input').hide();
$('#add-fail2ban-rule-website-mode-input').hide();
$('#add-fail2ban-rule-service-input').show();
$('#add-fail2ban-rule-website-path-input').hide();
}
});
// 监听网站模式选择
$('#add-fail2ban-rule-website-path-input').hide();
form.on('radio(add-fail2ban-rule-website-mode-radio)', function (data) {
if (data.value == 'cc') {
$('#add-fail2ban-rule-website-path-input').hide();
} else if (data.value == 'path') {
$('#add-fail2ban-rule-website-path-input').show();
}
});
// 提交
form.on('submit(add-fail2ban-rule-submit)', function (data) {
if (data.field.type === 'website') {
data.field.name = data.field.website;
} else if (data.field.type === 'service') {
data.field.name = data.field.service;
}
index = layer.msg('提交中...', {icon: 16, time: 0});
admin.req({
url: "/api/plugins/fail2ban/add"
, type: 'post'
, data: data.field
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
table.reload('fail2ban-rule-list');
layer.alert('Fail2ban规则添加成功', {
icon: 1
, title: '提示'
, btn: ['确定']
, yes: function (index) {
layer.closeAll();
}
});
}
});
return false;
});
});
};
</script>

View File

@@ -0,0 +1,101 @@
<!--
Name: Fail2ban管理器 - 查看规则
Author: 耗子
Date: 2023-07-30
-->
<script type="text/html" template lay-done="layui.data.sendParams(d.params)">
{{# console.log(d.params) }}
<div class="layui-fluid">
<div class="layui-row layui-col-space15">
<div class="layui-col-sm6 layui-col-md6">
<div class="layui-card">
<div class="layui-card-body layuiadmin-card-list">
<p class="layuiadmin-big-font">{{ d.params.banList.currentlyBan }}</p>
<p>当前封禁IP数</p>
</div>
</div>
</div>
<div class="layui-col-sm6 layui-col-md6">
<div class="layui-card">
<div class="layui-card-body layuiadmin-card-list">
<p class="layuiadmin-big-font">{{ d.params.banList.totalBan }}</p>
<p>累计封禁IP数</p>
</div>
</div>
</div>
</div>
<div class="layui-row layui-col-space15">
<div class="layui-col-sm12 layui-col-md12">
<div class="layui-card">
<div class="layui-card-body layuiadmin-card-list">
<table class="layui-table" id="fail2ban-view-rule-table"
lay-filter="fail2ban-view-rule-table"></table>
</div>
</div>
</div>
</div>
</div>
</script>
<script type="text/html" id="fail2ban-view-rule-ip-control">
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="unBan">解封</a>
</script>
<script>
layui.data.sendParams = function (params) {
if (params.banList.bannedIpList === null) {
params.banList.bannedIpList = [];
}
layui.use(['admin', 'jquery'], function () {
var admin = layui.admin
, layer = layui.layer
, table = layui.table;
table.render({
elem: '#fail2ban-view-rule-table'
, cols: [[
{field: 'name', title: '规则名', unresize: true, hide: true}
, {field: 'ip', title: 'IP', unresize: true, sort: true}
, {
fixed: 'right',
title: '操作',
width: 150,
unresize: true,
toolbar: '#fail2ban-view-rule-ip-control'
}
]]
, data: params.banList.bannedIpList
, page: true
, text: {
none: '无数据'
}
});
// 监听工具条
table.on('tool(fail2ban-view-rule-table)', function (obj) {
var data = obj.data;
if (obj.event === 'unBan') {
layer.confirm('确定要解封 <b style="color: red;">' + data.ip + '</b> 吗?', function (index) {
index = layer.msg('请稍等...', {
icon: 16
, time: 0
});
admin.req({
url: "/api/plugins/fail2ban/unban"
, type: 'post'
, data: data
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
obj.del();
layer.alert(data.ip + '解封成功!');
}
});
layer.close(index);
});
}
});
});
};
</script>

View File

@@ -3,6 +3,7 @@ package routes
import (
"github.com/goravel/framework/contracts/route"
"github.com/goravel/framework/facades"
"panel/app/http/controllers/plugins/fail2ban"
"panel/app/http/controllers/plugins/mysql57"
"panel/app/http/controllers/plugins/mysql80"
@@ -198,4 +199,19 @@ func Plugin() {
route.Post("addProcess", supervisorController.AddProcess)
})
facades.Route().Prefix("api/plugins/fail2ban").Middleware(middleware.Jwt()).Group(func(route route.Route) {
fail2banController := fail2ban.NewFail2banController()
route.Get("status", fail2banController.Status)
route.Post("start", fail2banController.Start)
route.Post("stop", fail2banController.Stop)
route.Post("restart", fail2banController.Restart)
route.Post("reload", fail2banController.Reload)
route.Get("list", fail2banController.List)
route.Post("add", fail2banController.Add)
route.Post("delete", fail2banController.Delete)
route.Get("ban", fail2banController.BanList)
route.Post("unban", fail2banController.Unban)
route.Post("whiteList", fail2banController.SetWhiteList)
route.Get("whiteList", fail2banController.GetWhiteList)
})
}

View File

@@ -39,7 +39,7 @@ fi
# 修改 fail2ban 配置文件
sed -i 's!# logtarget.*!logtarget = /var/log/fail2ban.log!' /etc/fail2ban/fail2ban.conf
sed -i 's!logtarget\s*=.*!logtarget = /var/log/fail2ban.log!' /etc/fail2ban/jail.conf
cat >/etc/fail2ban/jail.local <<EOF
cat > /etc/fail2ban/jail.local << EOF
[DEFAULT]
ignoreip = 127.0.0.1/8
bantime = 600
@@ -78,8 +78,8 @@ if [ "${sshPort}" == "" ]; then
sshPort="22"
fi
sed -i "s/port = 22/port = ${sshPort}/g" /etc/fail2ban/jail.local
if [ -f "/etc/pure-ftpd/pure-ftpd.conf" ]; then
ftpPort=$(cat /etc/pure-ftpd/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}')
if [ -f "/www/server/pure-ftpd/etc/pure-ftpd.conf" ]; then
ftpPort=$(cat /www/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}')
fi
if [ "${ftpPort}" == "" ]; then
ftpPort="21"
@@ -87,10 +87,17 @@ if [ "${ftpPort}" == "" ]; then
else
sed -i "s/port = 21/port = ${ftpPort}/g" /etc/fail2ban/jail.local
fi
# Debian 的特殊处理
if [ "${OS}" == "debian" ]; then
sed -i "s/\/var\/log\/secure/\/var\/log\/auth.log/g" /etc/fail2ban/jail.local
sed -i "s/banaction = firewallcmd-ipset/banaction = ufw/g" /etc/fail2ban/jail.local
fi
# 启动 fail2ban
systemctl unmask fail2ban
systemctl daemon-reload
systemctl enable fail2ban
systemctl restart fail2ban
panel writePlugin fail2ban
panel writePlugin fail2ban 1.0.0

View File

@@ -20,6 +20,8 @@ limitations under the License.
HR="+----------------------------------------------------"
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
fail2ban-client unban --all
fail2ban-client stop
systemctl stop fail2ban
systemctl disable fail2ban

View File

@@ -0,0 +1,37 @@
#!/bin/bash
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
: '
Copyright 2022 HaoZi Technology Co., Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
'
HR="+----------------------------------------------------"
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
if [ "${OS}" == "centos" ]; then
dnf install -y fail2ban
elif [ "${OS}" == "debian" ]; then
apt install -y fail2ban
else
echo -e $HR
echo "错误:不支持的操作系统"
exit 1
fi
if [ "$?" != "0" ]; then
echo -e $HR
echo "错误fail2ban安装失败请截图错误信息寻求帮助。"
exit 1
fi