From 49d8582658b7f5098ff337ad29dab0c2d6ee9cee Mon Sep 17 00:00:00 2001 From: ssongliu <73214554+ssongliu@users.noreply.github.com> Date: Thu, 16 Nov 2023 14:40:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B7=A5=E5=85=B7=E7=AE=B1=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=20Fail2Ban=20=E7=AE=A1=E7=90=86=20(#2966)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs #2209 #1643 #1787 #2645 --- backend/app/api/v1/entry.go | 2 + backend/app/api/v1/fail2ban.go | 153 +++++++++ backend/app/dto/common_req.go | 4 + backend/app/dto/fail2ban.go | 27 ++ backend/app/service/fail2ban.go | 211 ++++++++++++ backend/init/router/router.go | 4 +- backend/router/entry.go | 1 + backend/router/ro_toolbox.go | 27 ++ backend/utils/toolbox/fail2ban.go | 155 +++++++++ backend/utils/toolbox/fail2ban_test.go | 61 ++++ cmd/server/docs/docs.go | 296 +++++++++++++++++ cmd/server/docs/swagger.json | 296 +++++++++++++++++ cmd/server/docs/swagger.yaml | 188 +++++++++++ frontend/src/api/interface/index.ts | 3 + frontend/src/api/interface/toolbox.ts | 29 ++ frontend/src/api/modules/toolbox.ts | 31 ++ .../src/components/system-upgrade/index.vue | 2 +- frontend/src/lang/modules/en.ts | 20 ++ frontend/src/lang/modules/tw.ts | 20 ++ frontend/src/lang/modules/zh.ts | 20 ++ frontend/src/routers/modules/host.ts | 11 - frontend/src/routers/modules/log.ts | 2 +- frontend/src/routers/modules/setting.ts | 2 +- frontend/src/routers/modules/toolbox.ts | 45 +++ frontend/src/utils/util.ts | 37 +++ .../src/views/container/setting/index.vue | 2 +- frontend/src/views/host/firewall/ip/index.vue | 2 +- .../src/views/host/firewall/port/index.vue | 2 +- .../src/views/host/firewall/status/index.vue | 3 +- frontend/src/views/host/ssh/ssh/index.vue | 3 +- frontend/src/views/setting/about/index.vue | 8 +- .../setting/backup-account/operate/index.vue | 2 +- .../toolbox/fail2ban/ban-action/index.vue | 138 ++++++++ .../views/toolbox/fail2ban/ban-time/index.vue | 106 ++++++ .../toolbox/fail2ban/find-time/index.vue | 110 +++++++ frontend/src/views/toolbox/fail2ban/index.vue | 305 ++++++++++++++++++ .../src/views/toolbox/fail2ban/ips/index.vue | 121 +++++++ .../toolbox/fail2ban/max-retry/index.vue | 95 ++++++ .../views/{host/tool => toolbox}/index.vue | 7 +- .../supervisor/config/basic/index.vue | 0 .../supervisor/config/index.vue | 0 .../supervisor/config/log/index.vue | 2 - .../supervisor/config/source/index.vue | 2 - .../supervisor/create/index.vue | 0 .../supervisor/file/index.vue | 0 .../tool => toolbox}/supervisor/index.vue | 2 - .../supervisor/status/index.vue | 4 +- .../supervisor/status/init/index.vue | 0 48 files changed, 2526 insertions(+), 35 deletions(-) create mode 100644 backend/app/api/v1/fail2ban.go create mode 100644 backend/app/dto/fail2ban.go create mode 100644 backend/app/service/fail2ban.go create mode 100644 backend/router/ro_toolbox.go create mode 100644 backend/utils/toolbox/fail2ban.go create mode 100644 backend/utils/toolbox/fail2ban_test.go create mode 100644 frontend/src/api/interface/toolbox.ts create mode 100644 frontend/src/api/modules/toolbox.ts create mode 100644 frontend/src/routers/modules/toolbox.ts create mode 100644 frontend/src/views/toolbox/fail2ban/ban-action/index.vue create mode 100644 frontend/src/views/toolbox/fail2ban/ban-time/index.vue create mode 100644 frontend/src/views/toolbox/fail2ban/find-time/index.vue create mode 100644 frontend/src/views/toolbox/fail2ban/index.vue create mode 100644 frontend/src/views/toolbox/fail2ban/ips/index.vue create mode 100644 frontend/src/views/toolbox/fail2ban/max-retry/index.vue rename frontend/src/views/{host/tool => toolbox}/index.vue (74%) rename frontend/src/views/{host/tool => toolbox}/supervisor/config/basic/index.vue (100%) rename frontend/src/views/{host/tool => toolbox}/supervisor/config/index.vue (100%) rename frontend/src/views/{host/tool => toolbox}/supervisor/config/log/index.vue (97%) rename frontend/src/views/{host/tool => toolbox}/supervisor/config/source/index.vue (98%) rename frontend/src/views/{host/tool => toolbox}/supervisor/create/index.vue (100%) rename frontend/src/views/{host/tool => toolbox}/supervisor/file/index.vue (100%) rename frontend/src/views/{host/tool => toolbox}/supervisor/index.vue (99%) rename frontend/src/views/{host/tool => toolbox}/supervisor/status/index.vue (97%) rename frontend/src/views/{host/tool => toolbox}/supervisor/status/init/index.vue (100%) diff --git a/backend/app/api/v1/entry.go b/backend/app/api/v1/entry.go index cadec348b..86e996167 100644 --- a/backend/app/api/v1/entry.go +++ b/backend/app/api/v1/entry.go @@ -33,6 +33,8 @@ var ( sshService = service.NewISSHService() firewallService = service.NewIFirewallService() + fail2banService = service.NewIFail2BanService() + settingService = service.NewISettingService() backupService = service.NewIBackupService() diff --git a/backend/app/api/v1/fail2ban.go b/backend/app/api/v1/fail2ban.go new file mode 100644 index 000000000..d4b4d0c21 --- /dev/null +++ b/backend/app/api/v1/fail2ban.go @@ -0,0 +1,153 @@ +package v1 + +import ( + "os" + + "github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/constant" + "github.com/gin-gonic/gin" +) + +// @Tags Fail2Ban +// @Summary Load fail2ban base info +// @Description 获取 Fail2Ban 基础信息 +// @Success 200 {object} dto.Fail2BanBaseInfo +// @Security ApiKeyAuth +// @Router /toolbox/fail2ban/base [get] +func (b *BaseApi) LoadFail2BanBaseInfo(c *gin.Context) { + data, err := fail2banService.LoadBaseInfo() + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + + helper.SuccessWithData(c, data) +} + +// @Tags Fail2Ban +// @Summary Page fail2ban ip list +// @Description 获取 Fail2Ban ip +// @Accept json +// @Param request body dto.Fail2BanSearch true "request" +// @Success 200 {Array} string +// @Security ApiKeyAuth +// @Router /toolbox/fail2ban/search [post] +func (b *BaseApi) SearchFail2Ban(c *gin.Context) { + var req dto.Fail2BanSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + list, err := fail2banService.Search(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + + helper.SuccessWithData(c, list) +} + +// @Tags Fail2Ban +// @Summary Operate fail2ban +// @Description 修改 Fail2Ban 状态 +// @Accept json +// @Param request body dto.Operate true "request" +// @Security ApiKeyAuth +// @Router /toolbox/fail2ban/operate [post] +// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] Fail2Ban","formatEN":"[operation] Fail2Ban"} +func (b *BaseApi) OperateFail2Ban(c *gin.Context) { + var req dto.Operate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := fail2banService.Operate(req.Operation); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + + helper.SuccessWithData(c, nil) +} + +// @Tags Fail2Ban +// @Summary Operate sshd of fail2ban +// @Description 配置 sshd +// @Accept json +// @Param request body dto.Operate true "request" +// @Security ApiKeyAuth +// @Router /toolbox/fail2ban/operate/sshd [post] +func (b *BaseApi) OperateSSHD(c *gin.Context) { + var req dto.Fail2BanSet + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := fail2banService.OperateSSHD(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + + helper.SuccessWithData(c, nil) +} + +// @Tags Fail2Ban +// @Summary Update fail2ban conf +// @Description 修改 Fail2Ban 配置 +// @Accept json +// @Param request body dto.Fail2BanUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /toolbox/fail2ban/update [post] +// @x-panel-log {"bodyKeys":["key","value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 Fail2Ban 配置 [key] => [value]","formatEN":"update fail2ban conf [key] => [value]"} +func (b *BaseApi) UpdateFail2BanConf(c *gin.Context) { + var req dto.Fail2BanUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := fail2banService.UpdateConf(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} + +// @Tags Fail2Ban +// @Summary Load fail2ban conf +// @Description 获取 fail2ban 配置文件 +// @Accept json +// @Success 200 +// @Security ApiKeyAuth +// @Router /toolbox/fail2ban/load/conf [get] +func (b *BaseApi) LoadFail2BanConf(c *gin.Context) { + path := "/etc/fail2ban/jail.local" + file, err := os.ReadFile(path) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + + helper.SuccessWithData(c, string(file)) +} + +// @Tags Fail2Ban +// @Summary Update fail2ban conf by file +// @Description 通过文件修改 fail2ban 配置 +// @Accept json +// @Param request body dto.UpdateByFile true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /toolbox/fail2ban/update/byconf [post] +func (b *BaseApi) UpdateFail2BanConfByFile(c *gin.Context) { + var req dto.UpdateByFile + if err := helper.CheckBind(&req, c); err != nil { + return + } + if err := fail2banService.UpdateConfByFile(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + + helper.SuccessWithData(c, nil) +} diff --git a/backend/app/dto/common_req.go b/backend/app/dto/common_req.go index 6f900f584..02e9409c9 100644 --- a/backend/app/dto/common_req.go +++ b/backend/app/dto/common_req.go @@ -41,6 +41,10 @@ type DeleteByName struct { Name string `json:"name" validate:"required"` } +type UpdateByFile struct { + File string `json:"file"` +} + type OperationWithNameAndType struct { Name string `json:"name"` Type string `json:"type" validate:"required"` diff --git a/backend/app/dto/fail2ban.go b/backend/app/dto/fail2ban.go new file mode 100644 index 000000000..b96898d5c --- /dev/null +++ b/backend/app/dto/fail2ban.go @@ -0,0 +1,27 @@ +package dto + +type Fail2BanBaseInfo struct { + IsEnable bool `json:"isEnable"` + IsActive bool `json:"isActive"` + IsExist bool `json:"isExist"` + Version string `json:"version"` + + MaxRetry int `json:"maxRetry"` + BanTime string `json:"banTime"` + FindTime string `json:"findTime"` + BanAction string `json:"banAction"` +} + +type Fail2BanSearch struct { + Status string `json:"status" validate:"required,oneof=banned ignore"` +} + +type Fail2BanUpdate struct { + Key string `json:"key" validate:"required,oneof=port bantime findtime maxretry banaction"` + Value string `json:"value"` +} + +type Fail2BanSet struct { + IPs []string `json:"ips"` + Operate string `json:"operate" validate:"required,oneof=banned ignore"` +} diff --git a/backend/app/service/fail2ban.go b/backend/app/service/fail2ban.go new file mode 100644 index 000000000..729925209 --- /dev/null +++ b/backend/app/service/fail2ban.go @@ -0,0 +1,211 @@ +package service + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" + + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/utils/toolbox" +) + +const defaultFail2BanPath = "/etc/fail2ban/jail.local" + +type Fail2BanService struct{} + +type IFail2BanService interface { + LoadBaseInfo() (dto.Fail2BanBaseInfo, error) + Search(search dto.Fail2BanSearch) ([]string, error) + Operate(operation string) error + OperateSSHD(req dto.Fail2BanSet) error + UpdateConf(req dto.Fail2BanUpdate) error + UpdateConfByFile(req dto.UpdateByFile) error +} + +func NewIFail2BanService() IFail2BanService { + return &Fail2BanService{} +} + +func (u *Fail2BanService) LoadBaseInfo() (dto.Fail2BanBaseInfo, error) { + var baseInfo dto.Fail2BanBaseInfo + client, err := toolbox.NewFail2Ban() + if err != nil { + return baseInfo, err + } + baseInfo.IsEnable, baseInfo.IsActive, baseInfo.IsExist = client.Status() + if !baseInfo.IsExist { + baseInfo.Version = "-" + return baseInfo, nil + } + baseInfo.Version = client.Version() + conf, err := os.ReadFile(defaultFail2BanPath) + if err != nil { + return baseInfo, fmt.Errorf("read fail2ban conf of %s failed, err: %v", defaultFail2BanPath, err) + } + lines := strings.Split(string(conf), "\n") + + block := "" + for _, line := range lines { + if strings.HasPrefix(strings.ToLower(line), "[default]") { + block = "default" + continue + } + if strings.HasPrefix(line, "[sshd]") { + block = "sshd" + continue + } + if strings.HasPrefix(line, "[") { + block = "" + continue + } + if block != "default" && block != "sshd" { + continue + } + loadFailValue(line, &baseInfo) + } + + return baseInfo, nil +} + +func (u *Fail2BanService) Search(req dto.Fail2BanSearch) ([]string, error) { + var list []string + client, err := toolbox.NewFail2Ban() + if err != nil { + return nil, err + } + if req.Status == "banned" { + list, err = client.ListBanned() + + } else { + list, err = client.ListIgnore() + } + if err != nil { + return nil, err + } + + return list, nil +} + +func (u *Fail2BanService) Operate(operation string) error { + client, err := toolbox.NewFail2Ban() + if err != nil { + return err + } + return client.Operate(operation) +} + +func (u *Fail2BanService) UpdateConf(req dto.Fail2BanUpdate) error { + conf, err := os.ReadFile(defaultFail2BanPath) + if err != nil { + return fmt.Errorf("read fail2ban conf of %s failed, err: %v", defaultFail2BanPath, err) + } + lines := strings.Split(string(conf), "\n") + + isStart, isEnd, hasKey := false, false, false + newFile := "" + for index, line := range lines { + if !isStart && strings.HasPrefix(line, "[sshd]") { + isStart = true + newFile += fmt.Sprintf("%s\n", line) + continue + } + if !isStart || isEnd { + newFile += fmt.Sprintf("%s\n", line) + continue + } + if strings.HasPrefix(line, req.Key) { + hasKey = true + newFile += fmt.Sprintf("%s = %s\n", req.Key, req.Value) + continue + } + if strings.HasPrefix(line, "[") || index != len(lines)-1 { + isEnd = true + if !hasKey { + newFile += fmt.Sprintf("%s = %s\n", req.Key, req.Value) + } + } + newFile += line + if index != len(lines)-1 { + newFile += "\n" + } + } + file, err := os.OpenFile(defaultFail2BanPath, os.O_WRONLY|os.O_TRUNC, 0640) + if err != nil { + return err + } + defer file.Close() + write := bufio.NewWriter(file) + _, _ = write.WriteString(newFile) + write.Flush() + + client, err := toolbox.NewFail2Ban() + if err != nil { + return err + } + if err := client.Operate("reload"); err != nil { + return err + } + return nil +} + +func (u *Fail2BanService) UpdateConfByFile(req dto.UpdateByFile) error { + file, err := os.OpenFile(defaultFail2BanPath, os.O_WRONLY|os.O_TRUNC, 0640) + if err != nil { + return err + } + defer file.Close() + write := bufio.NewWriter(file) + _, _ = write.WriteString(req.File) + write.Flush() + + client, err := toolbox.NewFail2Ban() + if err != nil { + return err + } + if err := client.Operate("reload"); err != nil { + return err + } + return nil +} + +func (u *Fail2BanService) OperateSSHD(req dto.Fail2BanSet) error { + if req.Operate == "ignore" { + if err := u.UpdateConf(dto.Fail2BanUpdate{Key: "ignoreip", Value: strings.Join(req.IPs, ",")}); err != nil { + return err + } + return nil + } + client, err := toolbox.NewFail2Ban() + if err != nil { + return err + } + if err := client.ReBanIPs(req.IPs); err != nil { + return err + } + return nil +} + +func loadFailValue(line string, baseInfo *dto.Fail2BanBaseInfo) { + if strings.HasPrefix(line, "maxretry") { + itemValue := strings.ReplaceAll(line, "maxretry", "") + itemValue = strings.ReplaceAll(itemValue, "=", "") + baseInfo.MaxRetry, _ = strconv.Atoi(strings.TrimSpace(itemValue)) + } + if strings.HasPrefix(line, "findtime") { + itemValue := strings.ReplaceAll(line, "findtime", "") + itemValue = strings.ReplaceAll(itemValue, "=", "") + baseInfo.FindTime = strings.TrimSpace(itemValue) + } + if strings.HasPrefix(line, "bantime") { + itemValue := strings.ReplaceAll(line, "bantime", "") + itemValue = strings.ReplaceAll(itemValue, "=", "") + baseInfo.BanTime = strings.TrimSpace(itemValue) + } + if strings.HasPrefix(line, "banaction") { + itemValue := strings.ReplaceAll(line, "banaction", "") + itemValue = strings.ReplaceAll(itemValue, "=", "") + baseInfo.BanAction = strings.TrimSpace(itemValue) + } +} diff --git a/backend/init/router/router.go b/backend/init/router/router.go index ebcd39b8a..a601a50eb 100644 --- a/backend/init/router/router.go +++ b/backend/init/router/router.go @@ -1,11 +1,12 @@ package router import ( - "github.com/gin-contrib/gzip" "html/template" "net/http" "strings" + "github.com/gin-contrib/gzip" + "github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/i18n" "github.com/1Panel-dev/1Panel/backend/middleware" @@ -94,6 +95,7 @@ func Routers() *gin.Engine { systemRouter.InitNginxRouter(PrivateGroup) systemRouter.InitRuntimeRouter(PrivateGroup) systemRouter.InitProcessRouter(PrivateGroup) + systemRouter.InitToolboxRouter(PrivateGroup) } return Router diff --git a/backend/router/entry.go b/backend/router/entry.go index b69635804..781f38739 100644 --- a/backend/router/entry.go +++ b/backend/router/entry.go @@ -8,6 +8,7 @@ type RouterGroup struct { MonitorRouter LogRouter FileRouter + ToolboxRouter TerminalRouter CronjobRouter SettingRouter diff --git a/backend/router/ro_toolbox.go b/backend/router/ro_toolbox.go new file mode 100644 index 000000000..6027e6669 --- /dev/null +++ b/backend/router/ro_toolbox.go @@ -0,0 +1,27 @@ +package router + +import ( + v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1" + "github.com/1Panel-dev/1Panel/backend/middleware" + + "github.com/gin-gonic/gin" +) + +type ToolboxRouter struct{} + +func (s *ToolboxRouter) InitToolboxRouter(Router *gin.RouterGroup) { + toolboxRouter := Router.Group("toolbox"). + Use(middleware.JwtAuth()). + Use(middleware.SessionAuth()). + Use(middleware.PasswordExpired()) + baseApi := v1.ApiGroupApp.BaseApi + { + toolboxRouter.GET("/fail2ban/base", baseApi.LoadFail2BanBaseInfo) + toolboxRouter.GET("/fail2ban/load/conf", baseApi.LoadFail2BanConf) + toolboxRouter.POST("/fail2ban/search", baseApi.SearchFail2Ban) + toolboxRouter.POST("/fail2ban/operate", baseApi.OperateFail2Ban) + toolboxRouter.POST("/fail2ban/operate/sshd", baseApi.OperateSSHD) + toolboxRouter.POST("/fail2ban/update", baseApi.UpdateFail2BanConf) + toolboxRouter.POST("/fail2ban/update/byconf", baseApi.UpdateFail2BanConfByFile) + } +} diff --git a/backend/utils/toolbox/fail2ban.go b/backend/utils/toolbox/fail2ban.go new file mode 100644 index 000000000..c18eefb7b --- /dev/null +++ b/backend/utils/toolbox/fail2ban.go @@ -0,0 +1,155 @@ +package toolbox + +import ( + "encoding/json" + "fmt" + "os" + "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, error) + Version() (string, error) + Operate(operate string) error + OperateSSHD(operate, ip string) error +} + +func NewFail2Ban() (*Fail2Ban, error) { + isExist, _ := systemctl.IsExist("fail2ban.service") + if isExist { + if _, err := os.Stat(defaultPath); err != nil { + if err := initLocalFile(); err != nil { + return nil, err + } + stdout, err := cmd.Exec("fail2ban-client reload") + if err != nil { + global.LOG.Errorf("reload fail2ban failed, err: %s", stdout) + 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 "-" + } + return strings.ReplaceAll(stdout, "\n", "") +} + +func (f *Fail2Ban) Operate(operate string) error { + switch operate { + case "start", "restart", "stop", "enable", "disable": + stdout, err := cmd.Execf("systemctl %s fail2ban.service", operate) + if err != nil { + return fmt.Errorf("%s the fail2ban.service failed, err: %s", operate, stdout) + } + 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 get sshd banned") + if err != nil { + return lists, err + } + stdout = strings.ReplaceAll(stdout, "\n", "") + stdout = strings.ReplaceAll(stdout, "'", "\"") + if err := json.Unmarshal([]byte(stdout), &lists); err != nil { + return lists, fmt.Errorf("handle json unmarshal (%s) failed, err: %v", stdout, err) + } + 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 { + if _, err := os.Create(defaultPath); err != nil { + return err + } + initFile := `#DEFAULT-START +[DEFAULT] +bantime = 600 +findtime = 300 +maxretry = 5 +banaction = firewallcmd-ipset +action = %(action_mwl)s +#DEFAULT-END + +[sshd] +ignoreip = 127.0.0.1/8 +enabled = true +filter = sshd +port = 22 +maxretry = 5 +findtime = 300 +bantime = 600 +action = %(action_mwl)s +logpath = /var/log/secure` + if err := os.WriteFile(defaultPath, []byte(initFile), 0640); err != nil { + return err + } + return nil +} diff --git a/backend/utils/toolbox/fail2ban_test.go b/backend/utils/toolbox/fail2ban_test.go new file mode 100644 index 000000000..a167e5ae5 --- /dev/null +++ b/backend/utils/toolbox/fail2ban_test.go @@ -0,0 +1,61 @@ +package toolbox + +import ( + "fmt" + "os" + "strings" + "testing" + + "github.com/1Panel-dev/1Panel/backend/utils/ssh" +) + +func TestCds(t *testing.T) { + kk := ssh.ConnInfo{ + Port: 22, + AuthMode: "password", + User: "root", + } + sd, err := kk.Run("fail2ban-client get sshd ignoreip") + if err != nil { + fmt.Println(err) + } + sd = strings.ReplaceAll(sd, "|", "") + sd = strings.ReplaceAll(sd, "`", "") + sd = strings.ReplaceAll(sd, "\n", "") + + addrs := strings.Split(sd, "-") + for _, addr := range addrs { + if !strings.HasPrefix(addr, " ") { + continue + } + fmt.Println(strings.TrimPrefix(addr, " ")) + } +} + +func TestCdsxx(t *testing.T) { + initFile := `#DEFAULT-START +[DEFAULT] +ignoreip = 127.0.0.1/8,172.16.10.114,172.16.10.116 +bantime = 600 +findtime = 300 +maxretry = 5 +banaction = firewallcmd-ipset +action = %(action_mwl)s +#DEFAULT-END + +#sshd-START +[sshd] +enabled = true +filter = sshd +port = 22 +maxretry = 5 +findtime = 300 +bantime = 86400 +action = %(action_mwl)s +logpath = /var/log/secure +#sshd-END` + + if err := os.WriteFile("/Users/slooop/Downloads/tex.txt", []byte(initFile), 0640); err != nil { + fmt.Println(err) + } +} diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index 095802c53..9205829b2 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -10086,6 +10086,229 @@ const docTemplate = `{ } } }, + "/toolbox/fail2ban/base": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取 Fail2Ban 基础信息", + "tags": [ + "Fail2Ban" + ], + "summary": "Load fail2ban base info", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.Fail2BanBaseInfo" + } + } + } + } + }, + "/toolbox/fail2ban/load/conf": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取 fail2ban 配置文件", + "consumes": [ + "application/json" + ], + "tags": [ + "Fail2Ban" + ], + "summary": "Load fail2ban conf", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/toolbox/fail2ban/operate": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "修改 Fail2Ban 状态", + "consumes": [ + "application/json" + ], + "tags": [ + "Fail2Ban" + ], + "summary": "Operate fail2ban", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Operate" + } + } + ], + "responses": {}, + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operation" + ], + "formatEN": "[operation] Fail2Ban", + "formatZH": "[operation] Fail2Ban", + "paramKeys": [] + } + } + }, + "/toolbox/fail2ban/operate/sshd": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "配置 sshd", + "consumes": [ + "application/json" + ], + "tags": [ + "Fail2Ban" + ], + "summary": "Operate sshd of fail2ban", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Operate" + } + } + ], + "responses": {} + } + }, + "/toolbox/fail2ban/search": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取 Fail2Ban ip", + "consumes": [ + "application/json" + ], + "tags": [ + "Fail2Ban" + ], + "summary": "Page fail2ban ip list", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Fail2BanSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "Array" + } + } + } + } + }, + "/toolbox/fail2ban/update": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "修改 Fail2Ban 配置", + "consumes": [ + "application/json" + ], + "tags": [ + "Fail2Ban" + ], + "summary": "Update fail2ban conf", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Fail2BanUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "value" + ], + "formatEN": "update fail2ban conf [key] =\u003e [value]", + "formatZH": "修改 Fail2Ban 配置 [key] =\u003e [value]", + "paramKeys": [] + } + } + }, + "/toolbox/fail2ban/update/byconf": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "通过文件修改 fail2ban 配置", + "consumes": [ + "application/json" + ], + "tags": [ + "Fail2Ban" + ], + "summary": "Update fail2ban conf by file", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateByFile" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/websites": { "post": { "security": [ @@ -13999,6 +14222,71 @@ const docTemplate = `{ } } }, + "dto.Fail2BanBaseInfo": { + "type": "object", + "properties": { + "banAction": { + "type": "string" + }, + "banTime": { + "type": "string" + }, + "findTime": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isEnable": { + "type": "boolean" + }, + "isExist": { + "type": "boolean" + }, + "maxRetry": { + "type": "integer" + }, + "version": { + "type": "string" + } + } + }, + "dto.Fail2BanSearch": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "banned", + "ignore" + ] + } + } + }, + "dto.Fail2BanUpdate": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string", + "enum": [ + "port", + "bantime", + "findtime", + "maxretry", + "banaction" + ] + }, + "value": { + "type": "string" + } + } + }, "dto.FirewallBaseInfo": { "type": "object", "properties": { @@ -15961,6 +16249,14 @@ const docTemplate = `{ } } }, + "dto.UpdateByFile": { + "type": "object", + "properties": { + "file": { + "type": "string" + } + } + }, "dto.UpdateDescription": { "type": "object", "required": [ diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index bb4421d08..95b7eb021 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -10079,6 +10079,229 @@ } } }, + "/toolbox/fail2ban/base": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取 Fail2Ban 基础信息", + "tags": [ + "Fail2Ban" + ], + "summary": "Load fail2ban base info", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.Fail2BanBaseInfo" + } + } + } + } + }, + "/toolbox/fail2ban/load/conf": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取 fail2ban 配置文件", + "consumes": [ + "application/json" + ], + "tags": [ + "Fail2Ban" + ], + "summary": "Load fail2ban conf", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/toolbox/fail2ban/operate": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "修改 Fail2Ban 状态", + "consumes": [ + "application/json" + ], + "tags": [ + "Fail2Ban" + ], + "summary": "Operate fail2ban", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Operate" + } + } + ], + "responses": {}, + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operation" + ], + "formatEN": "[operation] Fail2Ban", + "formatZH": "[operation] Fail2Ban", + "paramKeys": [] + } + } + }, + "/toolbox/fail2ban/operate/sshd": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "配置 sshd", + "consumes": [ + "application/json" + ], + "tags": [ + "Fail2Ban" + ], + "summary": "Operate sshd of fail2ban", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Operate" + } + } + ], + "responses": {} + } + }, + "/toolbox/fail2ban/search": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取 Fail2Ban ip", + "consumes": [ + "application/json" + ], + "tags": [ + "Fail2Ban" + ], + "summary": "Page fail2ban ip list", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Fail2BanSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "Array" + } + } + } + } + }, + "/toolbox/fail2ban/update": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "修改 Fail2Ban 配置", + "consumes": [ + "application/json" + ], + "tags": [ + "Fail2Ban" + ], + "summary": "Update fail2ban conf", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Fail2BanUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "value" + ], + "formatEN": "update fail2ban conf [key] =\u003e [value]", + "formatZH": "修改 Fail2Ban 配置 [key] =\u003e [value]", + "paramKeys": [] + } + } + }, + "/toolbox/fail2ban/update/byconf": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "通过文件修改 fail2ban 配置", + "consumes": [ + "application/json" + ], + "tags": [ + "Fail2Ban" + ], + "summary": "Update fail2ban conf by file", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateByFile" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/websites": { "post": { "security": [ @@ -13992,6 +14215,71 @@ } } }, + "dto.Fail2BanBaseInfo": { + "type": "object", + "properties": { + "banAction": { + "type": "string" + }, + "banTime": { + "type": "string" + }, + "findTime": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isEnable": { + "type": "boolean" + }, + "isExist": { + "type": "boolean" + }, + "maxRetry": { + "type": "integer" + }, + "version": { + "type": "string" + } + } + }, + "dto.Fail2BanSearch": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "banned", + "ignore" + ] + } + } + }, + "dto.Fail2BanUpdate": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string", + "enum": [ + "port", + "bantime", + "findtime", + "maxretry", + "banaction" + ] + }, + "value": { + "type": "string" + } + } + }, "dto.FirewallBaseInfo": { "type": "object", "properties": { @@ -15954,6 +16242,14 @@ } } }, + "dto.UpdateByFile": { + "type": "object", + "properties": { + "file": { + "type": "string" + } + } + }, "dto.UpdateDescription": { "type": "object", "required": [ diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index d81aa442a..6aa39f996 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -973,6 +973,50 @@ definitions: - fileName - source type: object + dto.Fail2BanBaseInfo: + properties: + banAction: + type: string + banTime: + type: string + findTime: + type: string + isActive: + type: boolean + isEnable: + type: boolean + isExist: + type: boolean + maxRetry: + type: integer + version: + type: string + type: object + dto.Fail2BanSearch: + properties: + status: + enum: + - banned + - ignore + type: string + required: + - status + type: object + dto.Fail2BanUpdate: + properties: + key: + enum: + - port + - bantime + - findtime + - maxretry + - banaction + type: string + value: + type: string + required: + - key + type: object dto.FirewallBaseInfo: properties: name: @@ -2296,6 +2340,11 @@ definitions: label: type: string type: object + dto.UpdateByFile: + properties: + file: + type: string + type: object dto.UpdateDescription: properties: description: @@ -10781,6 +10830,145 @@ paths: formatEN: upgrade service => [version] formatZH: 更新系统 => [version] paramKeys: [] + /toolbox/fail2ban/base: + get: + description: 获取 Fail2Ban 基础信息 + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.Fail2BanBaseInfo' + security: + - ApiKeyAuth: [] + summary: Load fail2ban base info + tags: + - Fail2Ban + /toolbox/fail2ban/load/conf: + get: + consumes: + - application/json + description: 获取 fail2ban 配置文件 + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: Load fail2ban conf + tags: + - Fail2Ban + /toolbox/fail2ban/operate: + post: + consumes: + - application/json + description: 修改 Fail2Ban 状态 + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.Operate' + responses: {} + security: + - ApiKeyAuth: [] + summary: Operate fail2ban + tags: + - Fail2Ban + x-panel-log: + BeforeFunctions: [] + bodyKeys: + - operation + formatEN: '[operation] Fail2Ban' + formatZH: '[operation] Fail2Ban' + paramKeys: [] + /toolbox/fail2ban/operate/sshd: + post: + consumes: + - application/json + description: 配置 sshd + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.Operate' + responses: {} + security: + - ApiKeyAuth: [] + summary: Operate sshd of fail2ban + tags: + - Fail2Ban + /toolbox/fail2ban/search: + post: + consumes: + - application/json + description: 获取 Fail2Ban ip + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.Fail2BanSearch' + responses: + "200": + description: OK + schema: + type: Array + security: + - ApiKeyAuth: [] + summary: Page fail2ban ip list + tags: + - Fail2Ban + /toolbox/fail2ban/update: + post: + consumes: + - application/json + description: 修改 Fail2Ban 配置 + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.Fail2BanUpdate' + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: Update fail2ban conf + tags: + - Fail2Ban + x-panel-log: + BeforeFunctions: [] + bodyKeys: + - key + - value + formatEN: update fail2ban conf [key] => [value] + formatZH: 修改 Fail2Ban 配置 [key] => [value] + paramKeys: [] + /toolbox/fail2ban/update/byconf: + post: + consumes: + - application/json + description: 通过文件修改 fail2ban 配置 + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.UpdateByFile' + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: Update fail2ban conf by file + tags: + - Fail2Ban /websites: post: consumes: diff --git a/frontend/src/api/interface/index.ts b/frontend/src/api/interface/index.ts index eff34b927..02e14acb1 100644 --- a/frontend/src/api/interface/index.ts +++ b/frontend/src/api/interface/index.ts @@ -34,3 +34,6 @@ export interface DescriptionUpdate { id: number; description: string; } +export interface UpdateByFile { + file: string; +} diff --git a/frontend/src/api/interface/toolbox.ts b/frontend/src/api/interface/toolbox.ts new file mode 100644 index 000000000..c4272d79c --- /dev/null +++ b/frontend/src/api/interface/toolbox.ts @@ -0,0 +1,29 @@ +export namespace Toolbox { + export interface Fail2banBaseInfo { + isEnable: boolean; + isActive: boolean; + isExist: boolean; + version: string; + + port: number; + maxRetry: number; + banTime: string; + findTime: string; + banAction: string; + logPath: string; + } + + export interface Fail2banSearch { + status: string; + } + + export interface Fail2banUpdate { + key: string; + value: string; + } + + export interface Fail2banSet { + ips: Array; + operate: string; + } +} diff --git a/frontend/src/api/modules/toolbox.ts b/frontend/src/api/modules/toolbox.ts new file mode 100644 index 000000000..6da7ac86c --- /dev/null +++ b/frontend/src/api/modules/toolbox.ts @@ -0,0 +1,31 @@ +import http from '@/api'; +import { UpdateByFile } from '../interface'; +import { Toolbox } from '../interface/toolbox'; + +// fail2ban +export const getFail2banBase = () => { + return http.get(`/toolbox/fail2ban/base`); +}; +export const getFail2banConf = () => { + return http.get(`/toolbox/fail2ban/load/conf`); +}; + +export const searchFail2ban = (param: Toolbox.Fail2banSearch) => { + return http.post>(`/toolbox/fail2ban/search`, param); +}; + +export const operateFail2ban = (operate: string) => { + return http.post(`/toolbox/fail2ban/operate`, { operation: operate }); +}; + +export const operatorFail2banSSHD = (param: Toolbox.Fail2banSet) => { + return http.post(`/toolbox/fail2ban/operate/sshd`, param); +}; + +export const updateFail2ban = (param: Toolbox.Fail2banUpdate) => { + return http.post(`/toolbox/fail2ban/update`, param); +}; + +export const updateFail2banByFile = (param: UpdateByFile) => { + return http.post(`/toolbox/fail2ban/update/byconf`, param); +}; diff --git a/frontend/src/components/system-upgrade/index.vue b/frontend/src/components/system-upgrade/index.vue index c791baf78..4f310b896 100644 --- a/frontend/src/components/system-upgrade/index.vue +++ b/frontend/src/components/system-upgrade/index.vue @@ -96,7 +96,7 @@ const handleClose = () => { }; const toDoc = () => { - window.open('https://1panel.cn/docs/', '_blank'); + window.open('https://1panel.cn/docs/', '_blank', 'noopener,noreferrer'); }; const toForum = () => { diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 2ec25745d..dbc534f86 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -866,6 +866,25 @@ const message = { keyPassword: 'Private key password', emptyTerminal: 'No terminal is currently connected', }, + toolbox: { + fail2ban: { + noFail2ban: 'Fail2Ban service not detected, please refer to the official documentation for installation', + unActive: 'The Fail2Ban service is not enabled at present, please enable it first!', + operation: 'Perform [{0}] operation on Fail2Ban service, continue?', + fail2banChange: 'Fail2Ban Configuration Modification', + ignoreHelper: 'The IP list in the whitelist will be ignored for blocking, continue?', + bannedHelper: 'The IP list in the blacklist will be blocked by the server, continue?', + maxRetry: 'Maximum Retry Attempts', + banTime: 'Ban Time', + banTimeHelper: 'Default ban time is 10 minutes, -1 indicates permanent ban', + findTime: 'Discovery Period', + banAction: 'Ban Action', + banActionOption: 'Ban specified IP addresses using {0}', + allPorts: ' (All Ports)', + ignoreIP: 'IP Whitelist', + bannedIP: 'IP Blacklist', + }, + }, logs: { panelLog: 'Panel logs', operation: 'Operation logs', @@ -887,6 +906,7 @@ const message = { files: 'File Manage', runtimes: 'Runtime', process: 'Process', + toolbox: 'Toolbox', logs: 'Panel Logs', settings: 'Panel Setting', cronjobs: 'Cronjob', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 953ed1398..d9474ffc8 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -830,6 +830,25 @@ const message = { keyPassword: '私鑰密碼', emptyTerminal: '暫無終端連接', }, + toolbox: { + fail2ban: { + noFail2ban: '未檢測到 Fail2Ban 服務,請參考官方文檔進行安裝', + unActive: '當前未開啟 Fail2Ban 服務,請先開啟!', + operation: '對 Fail2Ban 服務進行 [{0}] 操作,是否繼續?', + fail2banChange: 'Fail2Ban 配置修改', + ignoreHelper: '白名單中的 IP 列表將被忽略屏蔽,是否繼續?', + bannedHelper: '黑名單中的 IP 列表將被伺服器屏蔽,是否繼續?', + maxRetry: '最大重試次數', + banTime: '禁用時間', + banTimeHelper: '默認禁用時間為 10 分鐘,禁用時間為 -1 則表示永久禁用', + findTime: '發現周期', + banAction: '禁用方式', + banActionOption: '通過 {0} 來禁用指定的 IP 地址', + allPorts: ' (所有端口)', + ignoreIP: 'IP 白名單', + bannedIP: 'IP 黑名單', + }, + }, logs: { panelLog: '面板日誌', operation: '操作日誌', @@ -851,6 +870,7 @@ const message = { files: '文件管理', runtimes: '運行環境', process: '進程管理', + toolbox: '工具箱', logs: '日誌審計', settings: '面板設置', cronjobs: '計劃任務', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 473a20994..98971c757 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -831,6 +831,25 @@ const message = { keyPassword: '私钥密码', emptyTerminal: '暂无终端连接', }, + toolbox: { + fail2ban: { + noFail2ban: '未检测到 Fail2Ban 服务,请参考官方文档进行安装', + unActive: '当前未开启 Fail2Ban 服务,请先开启!', + operation: '对 Fail2Ban 服务进行 [{0}] 操作,是否继续?', + fail2banChange: 'Fail2Ban 配置修改', + ignoreHelper: '白名单中的 IP 列表将被忽略屏蔽,是否继续?', + bannedHelper: '黑名单中的 IP 列表将被服务器屏蔽,是否继续?', + maxRetry: '最大重试次数', + banTime: '禁用时间', + banTimeHelper: '默认禁用时间为 10 分钟,禁用时间为 -1 则表示永久禁用', + findTime: '发现周期', + banAction: '禁用方式', + banActionOption: '通过 {0} 来禁用指定的 IP 地址', + allPorts: ' (所有端口)', + ignoreIP: 'IP 白名单', + bannedIP: 'IP 黑名单', + }, + }, logs: { panelLog: '面板日志', operation: '操作日志', @@ -852,6 +871,7 @@ const message = { files: '文件管理', runtimes: '运行环境', process: '进程管理', + toolbox: '工具箱', logs: '日志审计', settings: '面板设置', cronjobs: '计划任务', diff --git a/frontend/src/routers/modules/host.ts b/frontend/src/routers/modules/host.ts index 63a05ac28..eb719a7ce 100644 --- a/frontend/src/routers/modules/host.ts +++ b/frontend/src/routers/modules/host.ts @@ -91,17 +91,6 @@ const hostRouter = { requiresAuth: false, }, }, - { - path: '/hosts/tool/supersivor', - name: 'Supervisor', - component: () => import('@/views/host/tool/supervisor/index.vue'), - meta: { - title: 'menu.supervisor', - activeMenu: '/hosts/tool/supersivor', - keepAlive: true, - requiresAuth: false, - }, - }, { path: '/hosts/ssh/ssh', name: 'SSH', diff --git a/frontend/src/routers/modules/log.ts b/frontend/src/routers/modules/log.ts index 8db55d2f5..013a97ace 100644 --- a/frontend/src/routers/modules/log.ts +++ b/frontend/src/routers/modules/log.ts @@ -1,7 +1,7 @@ import { Layout } from '@/routers/constant'; const logsRouter = { - sort: 7, + sort: 8, path: '/logs', component: Layout, redirect: '/logs/operation', diff --git a/frontend/src/routers/modules/setting.ts b/frontend/src/routers/modules/setting.ts index 4cdfe266f..e278b22bd 100644 --- a/frontend/src/routers/modules/setting.ts +++ b/frontend/src/routers/modules/setting.ts @@ -1,7 +1,7 @@ import { Layout } from '@/routers/constant'; const settingRouter = { - sort: 8, + sort: 9, path: '/settings', component: Layout, redirect: '/settings/panel', diff --git a/frontend/src/routers/modules/toolbox.ts b/frontend/src/routers/modules/toolbox.ts new file mode 100644 index 000000000..e56c61fa0 --- /dev/null +++ b/frontend/src/routers/modules/toolbox.ts @@ -0,0 +1,45 @@ +import { Layout } from '@/routers/constant'; + +const toolboxRouter = { + sort: 7, + path: '/toolbox', + component: Layout, + redirect: '/toolbox/supervisor', + meta: { + title: 'menu.toolbox', + icon: 'p-toolbox', + }, + children: [ + { + path: '/toolbox', + name: 'Toolbox', + redirect: '/toolbox/supervisor', + component: () => import('@/views/toolbox/index.vue'), + meta: {}, + children: [ + { + path: 'supervisor', + name: 'Supervisor', + component: () => import('@/views/toolbox/supervisor/index.vue'), + hidden: true, + meta: { + activeMenu: '/toolbox', + requiresAuth: false, + }, + }, + { + path: 'fail2Ban', + name: 'Fail2Ban', + component: () => import('@/views/toolbox/fail2ban/index.vue'), + hidden: true, + meta: { + activeMenu: '/toolbox', + requiresAuth: false, + }, + }, + ], + }, + ], +}; + +export default toolboxRouter; diff --git a/frontend/src/utils/util.ts b/frontend/src/utils/util.ts index 1c3ede1d7..3823590d4 100644 --- a/frontend/src/utils/util.ts +++ b/frontend/src/utils/util.ts @@ -285,6 +285,43 @@ export function getProvider(provider: string): string { } } +export function splitTime(item: string): any { + if (item.indexOf('s') !== -1) { + return { time: Number(item.replaceAll('s', '')), unit: 's' }; + } + if (item.indexOf('m') !== -1) { + return { time: Number(item.replaceAll('m', '')), unit: 'm' }; + } + if (item.indexOf('h') !== -1) { + return { time: Number(item.replaceAll('h', '')), unit: 'h' }; + } + if (item.indexOf('d') !== -1) { + return { time: Number(item.replaceAll('d', '')), unit: 'd' }; + } + if (item.indexOf('y') !== -1) { + return { time: Number(item.replaceAll('y', '')), unit: 'y' }; + } + return { time: Number(item), unit: 's' }; +} +export function transTimeUnit(val: string): any { + if (val.indexOf('s') !== -1) { + return val.replaceAll('s', i18n.global.t('commons.units.second')); + } + if (val.indexOf('m') !== -1) { + return val.replaceAll('m', i18n.global.t('commons.units.minute')); + } + if (val.indexOf('h') !== -1) { + return val.replaceAll('h', i18n.global.t('commons.units.hour')); + } + if (val.indexOf('d') !== -1) { + return val.replaceAll('d', i18n.global.t('commons.units.day')); + } + if (val.indexOf('y') !== -1) { + return val.replaceAll('y', i18n.global.t('commons.units.year')); + } + return val + i18n.global.t('commons.units.second'); +} + export function getAge(d1: string): string { const dateBegin = new Date(d1); const dateEnd = new Date(); diff --git a/frontend/src/views/container/setting/index.vue b/frontend/src/views/container/setting/index.vue index 07cdfb705..11e3edcd9 100644 --- a/frontend/src/views/container/setting/index.vue +++ b/frontend/src/views/container/setting/index.vue @@ -367,7 +367,7 @@ const save = async (key: string, value: string) => { }; const toDoc = () => { - window.open('https://1panel.cn/docs/user_manual/containers/setting/', '_blank'); + window.open('https://1panel.cn/docs/user_manual/containers/setting/', '_blank', 'noopener,noreferrer'); }; const onOperator = async (operation: string) => { diff --git a/frontend/src/views/host/firewall/ip/index.vue b/frontend/src/views/host/firewall/ip/index.vue index e248b0352..91c67e2c5 100644 --- a/frontend/src/views/host/firewall/ip/index.vue +++ b/frontend/src/views/host/firewall/ip/index.vue @@ -208,7 +208,7 @@ const onOpenDialog = async ( }; const toDoc = () => { - window.open('https://1panel.cn/docs/user_manual/hosts/firewall/', '_blank'); + window.open('https://1panel.cn/docs/user_manual/hosts/firewall/', '_blank', 'noopener,noreferrer'); }; const onChange = async (info: any) => { diff --git a/frontend/src/views/host/firewall/port/index.vue b/frontend/src/views/host/firewall/port/index.vue index ce2df9231..68156c095 100644 --- a/frontend/src/views/host/firewall/port/index.vue +++ b/frontend/src/views/host/firewall/port/index.vue @@ -268,7 +268,7 @@ const quickJump = () => { router.push({ name: 'AppInstalled' }); }; const toDoc = () => { - window.open('https://1panel.cn/docs/user_manual/hosts/firewall/', '_blank'); + window.open('https://1panel.cn/docs/user_manual/hosts/firewall/', '_blank', 'noopener,noreferrer'); }; const onChangeStatus = async (row: Host.RuleInfo, status: string) => { diff --git a/frontend/src/views/host/firewall/status/index.vue b/frontend/src/views/host/firewall/status/index.vue index 4e47a0bea..894799bf1 100644 --- a/frontend/src/views/host/firewall/status/index.vue +++ b/frontend/src/views/host/firewall/status/index.vue @@ -26,7 +26,8 @@ {{ $t('firewall.noPing') }} { }; const toDoc = () => { - window.open('https://1panel.cn/docs/', '_blank'); + window.open('https://1panel.cn/docs/', '_blank', 'noopener,noreferrer'); }; const toGithub = () => { - window.open('https://github.com/1Panel-dev/1Panel', '_blank'); + window.open('https://github.com/1Panel-dev/1Panel', '_blank', 'noopener,noreferrer'); }; const toIssue = () => { - window.open('https://github.com/1Panel-dev/1Panel/issues', '_blank'); + window.open('https://github.com/1Panel-dev/1Panel/issues', '_blank', 'noopener,noreferrer'); }; const toGithubStar = () => { - window.open('https://github.com/1Panel-dev/1Panel', '_blank'); + window.open('https://github.com/1Panel-dev/1Panel', '_blank', 'noopener,noreferrer'); }; onMounted(() => { diff --git a/frontend/src/views/setting/backup-account/operate/index.vue b/frontend/src/views/setting/backup-account/operate/index.vue index fd628bb7f..94ea273d5 100644 --- a/frontend/src/views/setting/backup-account/operate/index.vue +++ b/frontend/src/views/setting/backup-account/operate/index.vue @@ -348,7 +348,7 @@ function hasEndpoint(val: string) { } const toDoc = () => { - window.open('https://1panel.cn/docs/user_manual/settings/', '_blank'); + window.open('https://1panel.cn/docs/user_manual/settings/', '_blank', 'noopener,noreferrer'); }; const getBuckets = async (formEl: FormInstance | undefined) => { diff --git a/frontend/src/views/toolbox/fail2ban/ban-action/index.vue b/frontend/src/views/toolbox/fail2ban/ban-action/index.vue new file mode 100644 index 000000000..20abfe7ae --- /dev/null +++ b/frontend/src/views/toolbox/fail2ban/ban-action/index.vue @@ -0,0 +1,138 @@ + + + + diff --git a/frontend/src/views/toolbox/fail2ban/ban-time/index.vue b/frontend/src/views/toolbox/fail2ban/ban-time/index.vue new file mode 100644 index 000000000..bd6bfaf58 --- /dev/null +++ b/frontend/src/views/toolbox/fail2ban/ban-time/index.vue @@ -0,0 +1,106 @@ + + diff --git a/frontend/src/views/toolbox/fail2ban/find-time/index.vue b/frontend/src/views/toolbox/fail2ban/find-time/index.vue new file mode 100644 index 000000000..e5c72fb9c --- /dev/null +++ b/frontend/src/views/toolbox/fail2ban/find-time/index.vue @@ -0,0 +1,110 @@ + + diff --git a/frontend/src/views/toolbox/fail2ban/index.vue b/frontend/src/views/toolbox/fail2ban/index.vue new file mode 100644 index 000000000..89011f044 --- /dev/null +++ b/frontend/src/views/toolbox/fail2ban/index.vue @@ -0,0 +1,305 @@ + + + diff --git a/frontend/src/views/toolbox/fail2ban/ips/index.vue b/frontend/src/views/toolbox/fail2ban/ips/index.vue new file mode 100644 index 000000000..8ba8caca7 --- /dev/null +++ b/frontend/src/views/toolbox/fail2ban/ips/index.vue @@ -0,0 +1,121 @@ + + diff --git a/frontend/src/views/toolbox/fail2ban/max-retry/index.vue b/frontend/src/views/toolbox/fail2ban/max-retry/index.vue new file mode 100644 index 000000000..749c6af77 --- /dev/null +++ b/frontend/src/views/toolbox/fail2ban/max-retry/index.vue @@ -0,0 +1,95 @@ + + diff --git a/frontend/src/views/host/tool/index.vue b/frontend/src/views/toolbox/index.vue similarity index 74% rename from frontend/src/views/host/tool/index.vue rename to frontend/src/views/toolbox/index.vue index 17bf3973c..88fdcd960 100644 --- a/frontend/src/views/host/tool/index.vue +++ b/frontend/src/views/toolbox/index.vue @@ -9,12 +9,15 @@ diff --git a/frontend/src/views/host/tool/supervisor/config/basic/index.vue b/frontend/src/views/toolbox/supervisor/config/basic/index.vue similarity index 100% rename from frontend/src/views/host/tool/supervisor/config/basic/index.vue rename to frontend/src/views/toolbox/supervisor/config/basic/index.vue diff --git a/frontend/src/views/host/tool/supervisor/config/index.vue b/frontend/src/views/toolbox/supervisor/config/index.vue similarity index 100% rename from frontend/src/views/host/tool/supervisor/config/index.vue rename to frontend/src/views/toolbox/supervisor/config/index.vue diff --git a/frontend/src/views/host/tool/supervisor/config/log/index.vue b/frontend/src/views/toolbox/supervisor/config/log/index.vue similarity index 97% rename from frontend/src/views/host/tool/supervisor/config/log/index.vue rename to frontend/src/views/toolbox/supervisor/config/log/index.vue index d4f148fa6..bca16630e 100644 --- a/frontend/src/views/host/tool/supervisor/config/log/index.vue +++ b/frontend/src/views/toolbox/supervisor/config/log/index.vue @@ -36,5 +36,3 @@ onMounted(() => { getConfig(); }); - - diff --git a/frontend/src/views/host/tool/supervisor/config/source/index.vue b/frontend/src/views/toolbox/supervisor/config/source/index.vue similarity index 98% rename from frontend/src/views/host/tool/supervisor/config/source/index.vue rename to frontend/src/views/toolbox/supervisor/config/source/index.vue index 654cf9929..36b968ebc 100644 --- a/frontend/src/views/host/tool/supervisor/config/source/index.vue +++ b/frontend/src/views/toolbox/supervisor/config/source/index.vue @@ -59,5 +59,3 @@ onMounted(() => { getConfig(); }); - - diff --git a/frontend/src/views/host/tool/supervisor/create/index.vue b/frontend/src/views/toolbox/supervisor/create/index.vue similarity index 100% rename from frontend/src/views/host/tool/supervisor/create/index.vue rename to frontend/src/views/toolbox/supervisor/create/index.vue diff --git a/frontend/src/views/host/tool/supervisor/file/index.vue b/frontend/src/views/toolbox/supervisor/file/index.vue similarity index 100% rename from frontend/src/views/host/tool/supervisor/file/index.vue rename to frontend/src/views/toolbox/supervisor/file/index.vue diff --git a/frontend/src/views/host/tool/supervisor/index.vue b/frontend/src/views/toolbox/supervisor/index.vue similarity index 99% rename from frontend/src/views/host/tool/supervisor/index.vue rename to frontend/src/views/toolbox/supervisor/index.vue index 20444d465..b01b5641e 100644 --- a/frontend/src/views/host/tool/supervisor/index.vue +++ b/frontend/src/views/toolbox/supervisor/index.vue @@ -1,6 +1,5 @@