From 9b9fe5444d8adc034fc48171da43c5065a63fe00 Mon Sep 17 00:00:00 2001 From: zhengkunwang <31820853+zhengkunwang223@users.noreply.github.com> Date: Wed, 27 Mar 2024 18:36:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9B=E5=BB=BA=E7=BD=91=E7=AB=99?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20waf=20=E5=85=B3=E8=81=94=20(#4333)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/dto/request/website.go | 24 ++--- backend/app/dto/response/website.go | 5 - backend/app/service/website.go | 116 +++++++++++++++++++++- backend/app/service/website_utils.go | 139 ++++++++++++++++++++++++++- 4 files changed, 255 insertions(+), 29 deletions(-) diff --git a/backend/app/dto/request/website.go b/backend/app/dto/request/website.go index 6af6d69ea..37548fcb1 100644 --- a/backend/app/dto/request/website.go +++ b/backend/app/dto/request/website.go @@ -69,24 +69,12 @@ type WebsiteOp struct { Operate string `json:"operate"` } -type WebsiteWafReq struct { - WebsiteID uint `json:"websiteId" validate:"required"` - Key string `json:"key" validate:"required"` - Rule string `json:"rule" validate:"required"` -} - type WebsiteRedirectUpdate struct { WebsiteID uint `json:"websiteId" validate:"required"` Key string `json:"key" validate:"required"` Enable bool `json:"enable"` } -type WebsiteWafUpdate struct { - WebsiteID uint `json:"websiteId" validate:"required"` - Key string `json:"key" validate:"required"` - Enable bool `json:"enable"` -} - type WebsiteRecover struct { WebsiteName string `json:"websiteName" validate:"required"` Type string `json:"type" validate:"required"` @@ -207,12 +195,12 @@ type WebsiteRedirectReq struct { WebsiteID uint `json:"websiteId" validate:"required"` } -type WebsiteWafFileUpdate struct { - WebsiteID uint `json:"websiteID" validate:"required"` - Content string `json:"content" validate:"required"` - Type string `json:"type" validate:"required,oneof=cc ip_white ip_block url_white url_block cookie_block args_check post_check ua_check file_ext_block user_agent"` -} - type WebsiteCommonReq struct { ID uint `json:"id" validate:"required"` } + +type WafWebsite struct { + Key string `json:"key"` + Domains []string `json:"domains"` + Host []string `json:"host"` +} diff --git a/backend/app/dto/response/website.go b/backend/app/dto/response/website.go index dc18345ae..dd2fe1486 100644 --- a/backend/app/dto/response/website.go +++ b/backend/app/dto/response/website.go @@ -31,11 +31,6 @@ type WebsiteNginxConfig struct { Params []NginxParam `json:"params"` } -type WebsiteWafConfig struct { - Enable bool `json:"enable"` - Content string `json:"content"` -} - type WebsiteHTTPS struct { Enable bool `json:"enable"` HttpConfig string `json:"httpConfig"` diff --git a/backend/app/service/website.go b/backend/app/service/website.go index bc63e1e4e..1289cbe52 100644 --- a/backend/app/service/website.go +++ b/backend/app/service/website.go @@ -61,6 +61,7 @@ type IWebsiteService interface { UpdateWebsite(req request.WebsiteUpdate) error DeleteWebsite(req request.WebsiteDelete) error GetWebsite(id uint) (response.WebsiteDTO, error) + CreateWebsiteDomain(create request.WebsiteDomainCreate) ([]model.WebsiteDomain, error) GetWebsiteDomain(websiteId uint) ([]model.WebsiteDomain, error) DeleteWebsiteDomain(domainId uint) error @@ -328,6 +329,10 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error) if err = configDefaultNginx(website, domains, appInstall, runtime); err != nil { return err } + if err = createWafConfig(website, domains); err != nil { + return err + } + tx, ctx := helper.GetTxAndContext() defer tx.Rollback() if err = websiteRepo.Create(ctx, website); err != nil { @@ -420,7 +425,11 @@ func (w WebsiteService) DeleteWebsite(req request.WebsiteDelete) error { if err != nil { return err } - if err := delNginxConfig(website, req.ForceDelete); err != nil { + if err = delNginxConfig(website, req.ForceDelete); err != nil { + return err + } + + if err = delWafConfig(website, req.ForceDelete); err != nil { return err } @@ -489,6 +498,53 @@ func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate) return nil, err } + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return nil, err + } + wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data") + fileOp := files.NewFileOp() + if fileOp.Stat(wafDataPath) { + websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json") + content, err := fileOp.GetContent(websitesConfigPath) + if err != nil { + return nil, err + } + var websitesArray []request.WafWebsite + if content != nil { + if err := json.Unmarshal(content, &websitesArray); err != nil { + return nil, err + } + } + for index, wafWebsite := range websitesArray { + if wafWebsite.Key == website.Alias { + wafSite := request.WafWebsite{ + Key: website.Alias, + Domains: wafWebsite.Domains, + Host: wafWebsite.Host, + } + for _, domain := range domainModels { + wafSite.Domains = append(wafSite.Domains, domain.Domain) + if domain.Port != 80 && domain.Port != 443 { + wafSite.Host = append(wafSite.Host, domain.Domain+":"+string(rune(domain.Port))) + } + } + if len(wafSite.Host) == 0 { + wafSite.Host = []string{} + } + websitesArray[index] = wafSite + break + } + } + websitesContent, err := json.Marshal(websitesArray) + if err != nil { + return nil, err + } + if err := fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, 0644); err != nil { + return nil, err + } + } + return domainModels, websiteDomainRepo.BatchCreate(context.TODO(), domainModels) } @@ -518,6 +574,7 @@ func (w WebsiteService) DeleteWebsiteDomain(domainId uint) error { if oldDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(webSiteDomain.WebsiteID), websiteDomainRepo.WithDomain(webSiteDomain.Domain)); len(oldDomains) == 1 { domains = append(domains, webSiteDomain.Domain) } + if len(ports) > 0 || len(domains) > 0 { stringBinds := make([]string, len(ports)) for i := 0; i < len(ports); i++ { @@ -528,6 +585,63 @@ func (w WebsiteService) DeleteWebsiteDomain(domainId uint) error { } } + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data") + fileOp := files.NewFileOp() + if fileOp.Stat(wafDataPath) { + websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json") + content, err := fileOp.GetContent(websitesConfigPath) + if err != nil { + return err + } + var websitesArray []request.WafWebsite + var newWebsitesArray []request.WafWebsite + if content != nil { + if err := json.Unmarshal(content, &websitesArray); err != nil { + return err + } + } + for _, wafWebsite := range websitesArray { + if wafWebsite.Key == website.Alias { + wafSite := wafWebsite + oldDomains := wafSite.Domains + var newDomains []string + for _, domain := range oldDomains { + if domain == webSiteDomain.Domain { + continue + } + newDomains = append(newDomains, domain) + } + wafSite.Domains = newDomains + oldHostArray := wafSite.Host + var newHostArray []string + for _, host := range oldHostArray { + if host == webSiteDomain.Domain+":"+string(rune(webSiteDomain.Port)) { + continue + } + newHostArray = append(newHostArray, host) + } + wafSite.Host = newHostArray + if len(wafSite.Host) == 0 { + wafSite.Host = []string{} + } + newWebsitesArray = append(newWebsitesArray, wafSite) + } else { + newWebsitesArray = append(newWebsitesArray, wafWebsite) + } + } + websitesContent, err := json.Marshal(newWebsitesArray) + if err != nil { + return err + } + if err = fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, 0644); err != nil { + return err + } + } + return websiteDomainRepo.DeleteBy(context.TODO(), commonRepo.WithByID(domainId)) } diff --git a/backend/app/service/website_utils.go b/backend/app/service/website_utils.go index 3a96fe811..bad4e2428 100644 --- a/backend/app/service/website_utils.go +++ b/backend/app/service/website_utils.go @@ -1,6 +1,7 @@ package service import ( + "encoding/json" "fmt" "log" "os" @@ -197,7 +198,7 @@ func createWebsiteFolder(nginxInstall model.AppInstall, website *model.Website, } } } - return fileOp.CopyDir(path.Join(nginxFolder, "www", "common", "waf", "rules"), path.Join(siteFolder, "waf")) + return nil } func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, appInstall *model.AppInstall, runtime *model.Runtime) error { @@ -233,12 +234,8 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a server.UpdateServerName(serverNames) siteFolder := path.Join("/www", "sites", website.Alias) - commonFolder := path.Join("/www", "common") server.UpdateDirective("access_log", []string{path.Join(siteFolder, "log", "access.log")}) server.UpdateDirective("error_log", []string{path.Join(siteFolder, "log", "error.log")}) - server.UpdateDirective("access_by_lua_file", []string{path.Join(commonFolder, "waf", "access.lua")}) - server.UpdateDirective("set", []string{"$RulePath", path.Join(siteFolder, "waf", "rules")}) - server.UpdateDirective("set", []string{"$logdir", path.Join(siteFolder, "log")}) rootIndex := path.Join("/www/sites", website.Alias, "index") switch website.Type { @@ -285,6 +282,92 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a return nil } +func createWafConfig(website *model.Website, domains []model.WebsiteDomain) error { + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data") + fileOp := files.NewFileOp() + if !fileOp.Stat(wafDataPath) { + return nil + } + websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json") + content, err := fileOp.GetContent(websitesConfigPath) + if err != nil { + return err + } + var websitesArray []request.WafWebsite + if len(content) != 0 { + if err := json.Unmarshal(content, &websitesArray); err != nil { + return err + } + } + wafWebsite := request.WafWebsite{ + Key: website.Alias, + Domains: make([]string, 0), + Host: make([]string, 0), + } + + for _, domain := range domains { + wafWebsite.Domains = append(wafWebsite.Domains, domain.Domain) + if domain.Port != 80 && domain.Port != 443 { + wafWebsite.Host = append(wafWebsite.Host, domain.Domain+":"+string(rune(domain.Port))) + } + } + websitesArray = append(websitesArray, wafWebsite) + websitesContent, err := json.Marshal(websitesArray) + if err != nil { + return err + } + if err := fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, 0644); err != nil { + return err + } + + var ( + sitesDir = path.Join(wafDataPath, "sites") + defaultConfigPath = path.Join(wafDataPath, "conf", "siteConfig.json") + defaultRuleDir = path.Join(wafDataPath, "rules") + websiteDir = path.Join(sitesDir, website.Alias) + ) + + defaultConfigContent, err := fileOp.GetContent(defaultConfigPath) + if err != nil { + return err + } + + if !fileOp.Stat(websiteDir) { + if err = fileOp.CreateDir(websiteDir, 0755); err != nil { + return err + } + } + defer func() { + if err != nil { + _ = fileOp.DeleteDir(websiteDir) + } + }() + + if err = fileOp.SaveFileWithByte(path.Join(websiteDir, "config.json"), defaultConfigContent, 0644); err != nil { + return err + } + + websiteRuleDir := path.Join(websiteDir, "rules") + if !fileOp.Stat(websiteRuleDir) { + if err := fileOp.CreateDir(websiteRuleDir, 0755); err != nil { + return err + } + } + defaultRulesName := []string{"acl", "args", "cookie", "defaultUaBlack", "defaultUrlBlack", "fileExt", "header", "methodWhite"} + for _, ruleName := range defaultRulesName { + srcPath := path.Join(defaultRuleDir, ruleName+".json") + if err = fileOp.Copy(srcPath, websiteRuleDir); err != nil { + return err + } + } + + return nil +} + func delNginxConfig(website model.Website, force bool) error { nginxApp, err := appRepo.GetFirst(appRepo.WithKey(constant.AppOpenresty)) if err != nil { @@ -322,6 +405,52 @@ func delNginxConfig(website model.Website, force bool) error { return nil } +func delWafConfig(website model.Website, force bool) error { + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data") + fileOp := files.NewFileOp() + if !fileOp.Stat(wafDataPath) { + return nil + } + websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json") + content, err := fileOp.GetContent(websitesConfigPath) + if err != nil { + return err + } + var websitesArray []request.WafWebsite + var newWebsiteArray []request.WafWebsite + if len(content) > 0 { + if err = json.Unmarshal(content, &websitesArray); err != nil { + return err + } + } + for _, wafWebsite := range websitesArray { + if wafWebsite.Key != website.Alias { + newWebsiteArray = append(newWebsiteArray, wafWebsite) + } + } + websitesContent, err := json.Marshal(newWebsiteArray) + if err != nil { + return err + } + if err := fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, 0644); err != nil { + return err + } + + _ = fileOp.DeleteDir(path.Join(wafDataPath, "sites", website.Alias)) + + if err := opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil { + if force { + return nil + } + return err + } + return nil +} + func addListenAndServerName(website model.Website, ports []int, domains []string) error { nginxFull, err := getNginxFull(&website) if err != nil {