From e2403c9869ec2279338511747bacc126416cedc3 Mon Sep 17 00:00:00 2001 From: zhengkunwang223 <zhengkun@fit2cloud.com> Date: Wed, 28 Dec 2022 16:07:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20HTTPS=E5=A2=9E=E5=8A=A0=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/nginx/versions/1.21.4/config.json | 2 +- backend/app/api/v1/website.go | 5 +- backend/app/dto/request/website.go | 1 + backend/app/dto/response/website.go | 5 +- backend/app/model/website.go | 1 + backend/app/service/website.go | 92 +++++++++---------- backend/app/service/website_utils.go | 17 +++- backend/constant/website.go | 4 + backend/utils/nginx/components/server.go | 21 ++++- frontend/src/api/interface/website.ts | 2 + frontend/src/lang/modules/zh.ts | 10 ++ .../website/config/basic/https/index.vue | 47 +++++++--- 12 files changed, 132 insertions(+), 75 deletions(-) diff --git a/apps/nginx/versions/1.21.4/config.json b/apps/nginx/versions/1.21.4/config.json index 0b69ab743..739165b20 100644 --- a/apps/nginx/versions/1.21.4/config.json +++ b/apps/nginx/versions/1.21.4/config.json @@ -7,7 +7,7 @@ "required": true, "default": 80, "envKey": "PANEL_APP_PORT_HTTP", - "disabled": true, + "disabled": true }, { "type": "number", diff --git a/backend/app/api/v1/website.go b/backend/app/api/v1/website.go index b7ff96bfe..771d493b9 100644 --- a/backend/app/api/v1/website.go +++ b/backend/app/api/v1/website.go @@ -256,11 +256,14 @@ func (b *BaseApi) UpdateHTTPSConfig(c *gin.Context) { helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) return } - res, err := websiteService.OpWebsiteHTTPS(req) + tx, ctx := helper.GetTxAndContext() + res, err := websiteService.OpWebsiteHTTPS(ctx, req) if err != nil { + tx.Rollback() helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) return } + tx.Commit() helper.SuccessWithData(c, res) } diff --git a/backend/app/dto/request/website.go b/backend/app/dto/request/website.go index 08014a39b..37ba55532 100644 --- a/backend/app/dto/request/website.go +++ b/backend/app/dto/request/website.go @@ -102,6 +102,7 @@ type WebsiteHTTPSOp struct { Type string `json:"type" validate:"oneof=existed auto manual"` PrivateKey string `json:"privateKey"` Certificate string `json:"certificate"` + HttpConfig string `json:"HttpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"` } type WebsiteNginxUpdate struct { diff --git a/backend/app/dto/response/website.go b/backend/app/dto/response/website.go index 4a5b95ca9..5580e7ea3 100644 --- a/backend/app/dto/response/website.go +++ b/backend/app/dto/response/website.go @@ -30,6 +30,7 @@ type WebsiteWafConfig struct { } type WebsiteHTTPS struct { - Enable bool `json:"enable"` - SSL model.WebsiteSSL `json:"SSL"` + Enable bool `json:"enable"` + HttpConfig string `json:"httpConfig"` + SSL model.WebsiteSSL `json:"SSL"` } diff --git a/backend/app/model/website.go b/backend/app/model/website.go index e8c8a4310..449278d69 100644 --- a/backend/app/model/website.go +++ b/backend/app/model/website.go @@ -10,6 +10,7 @@ type Website struct { Alias string `gorm:"type:varchar(128);not null" json:"alias"` Remark string `gorm:"type:longtext;" json:"remark"` Status string `gorm:"type:varchar(64);not null" json:"status"` + HttpConfig string `gorm:"type:varchar(64);not null" json:"httpConfig"` ExpireDate time.Time `json:"expireDate"` AppInstallID uint `gorm:"type:integer" json:"appInstallId"` WebsiteGroupID uint `gorm:"type:integer" json:"webSiteGroupId"` diff --git a/backend/app/service/website.go b/backend/app/service/website.go index fcccf4ed5..5466c7154 100644 --- a/backend/app/service/website.go +++ b/backend/app/service/website.go @@ -44,7 +44,7 @@ type IWebsiteService interface { UpdateNginxConfigByScope(req request.NginxConfigUpdate) error GetWebsiteNginxConfig(websiteId uint) (response.FileInfo, error) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, error) - OpWebsiteHTTPS(req request.WebsiteHTTPSOp) (response.WebsiteHTTPS, error) + OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (response.WebsiteHTTPS, error) PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error) GetWafConfig(req request.WebsiteWafReq) (response.WebsiteWafConfig, error) UpdateWafConfig(req request.WebsiteWafUpdate) error @@ -472,84 +472,78 @@ func (w WebsiteService) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, } res.SSL = websiteSSL res.Enable = true + if website.HttpConfig != "" { + res.HttpConfig = website.HttpConfig + } else { + res.HttpConfig = constant.HTTPToHTTPS + } return res, nil } -func (w WebsiteService) OpWebsiteHTTPS(req request.WebsiteHTTPSOp) (response.WebsiteHTTPS, error) { +func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (response.WebsiteHTTPS, error) { website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) if err != nil { return response.WebsiteHTTPS{}, err } - var ( res response.WebsiteHTTPS websiteSSL model.WebsiteSSL ) res.Enable = req.Enable - - if req.Type == constant.SSLExisted { - websiteSSL, err = websiteSSLRepo.GetFirst(commonRepo.WithByID(req.WebsiteSSLID)) - if err != nil { - return response.WebsiteHTTPS{}, err + if req.Enable { + if req.Type == constant.SSLExisted { + websiteSSL, err = websiteSSLRepo.GetFirst(commonRepo.WithByID(req.WebsiteSSLID)) + if err != nil { + return response.WebsiteHTTPS{}, err + } + website.WebsiteSSLID = websiteSSL.ID + res.SSL = websiteSSL } - website.WebsiteSSLID = websiteSSL.ID - if err := websiteRepo.Save(context.TODO(), &website); err != nil { - return response.WebsiteHTTPS{}, err - } - res.SSL = websiteSSL - } - - if req.Type == constant.SSLManual { - certBlock, _ := pem.Decode([]byte(req.Certificate)) - cert, err := x509.ParseCertificate(certBlock.Bytes) - if err != nil { - return response.WebsiteHTTPS{}, err - } - websiteSSL.ExpireDate = cert.NotAfter - websiteSSL.StartDate = cert.NotBefore - websiteSSL.Type = cert.Issuer.CommonName - websiteSSL.Organization = cert.Issuer.Organization[0] - websiteSSL.PrimaryDomain = cert.Subject.CommonName - if len(cert.Subject.Names) > 0 { - var domains []string - for _, name := range cert.Subject.Names { - if v, ok := name.Value.(string); ok { - if v != cert.Subject.CommonName { - domains = append(domains, v) + if req.Type == constant.SSLManual { + certBlock, _ := pem.Decode([]byte(req.Certificate)) + cert, err := x509.ParseCertificate(certBlock.Bytes) + if err != nil { + return response.WebsiteHTTPS{}, err + } + websiteSSL.ExpireDate = cert.NotAfter + websiteSSL.StartDate = cert.NotBefore + websiteSSL.Type = cert.Issuer.CommonName + websiteSSL.Organization = cert.Issuer.Organization[0] + websiteSSL.PrimaryDomain = cert.Subject.CommonName + if len(cert.Subject.Names) > 0 { + var domains []string + for _, name := range cert.Subject.Names { + if v, ok := name.Value.(string); ok { + if v != cert.Subject.CommonName { + domains = append(domains, v) + } } } + if len(domains) > 0 { + websiteSSL.Domains = strings.Join(domains, "") + } } - if len(domains) > 0 { - websiteSSL.Domains = strings.Join(domains, "") - } + websiteSSL.Provider = constant.Manual + websiteSSL.PrivateKey = req.PrivateKey + websiteSSL.Pem = req.Certificate + res.SSL = websiteSSL } - - websiteSSL.Provider = constant.Manual - websiteSSL.PrivateKey = req.PrivateKey - websiteSSL.Pem = req.Certificate - - res.SSL = websiteSSL - } - - if req.Enable { website.Protocol = constant.ProtocolHTTPS - if err := applySSL(website, websiteSSL); err != nil { + if err := applySSL(website, websiteSSL, req.HttpConfig); err != nil { return response.WebsiteHTTPS{}, err } + website.HttpConfig = req.HttpConfig } else { website.Protocol = constant.ProtocolHTTP website.WebsiteSSLID = 0 - if err := deleteListenAndServerName(website, []int{443}, []string{}); err != nil { return response.WebsiteHTTPS{}, err } - if err := deleteNginxConfig(constant.NginxScopeServer, getKeysFromStaticFile(dto.SSL), &website); err != nil { return response.WebsiteHTTPS{}, err } } - tx, ctx := getTxAndContext() if websiteSSL.ID == 0 { if err := websiteSSLRepo.Create(ctx, &websiteSSL); err != nil { return response.WebsiteHTTPS{}, err @@ -559,8 +553,6 @@ func (w WebsiteService) OpWebsiteHTTPS(req request.WebsiteHTTPSOp) (response.Web if err := websiteRepo.Save(ctx, &website); err != nil { return response.WebsiteHTTPS{}, err } - - tx.Commit() return res, nil } diff --git a/backend/app/service/website_utils.go b/backend/app/service/website_utils.go index d2914d4b0..a806bc890 100644 --- a/backend/app/service/website_utils.go +++ b/backend/app/service/website_utils.go @@ -298,7 +298,7 @@ func createPemFile(website model.Website, websiteSSL model.WebsiteSSL) error { return nil } -func applySSL(website model.Website, websiteSSL model.WebsiteSSL) error { +func applySSL(website model.Website, websiteSSL model.WebsiteSSL, httpConfig string) error { nginxFull, err := getNginxFull(&website) if err != nil { return nil @@ -306,10 +306,22 @@ func applySSL(website model.Website, websiteSSL model.WebsiteSSL) error { config := nginxFull.SiteConfig.Config server := config.FindServers()[0] server.UpdateListen("443", false, "ssl") + + switch httpConfig { + case constant.HTTPSOnly: + server.RemoveListenByBind("80") + server.RemoveDirective("if", []string{"($scheme"}) + case constant.HTTPToHTTPS: + server.UpdateListen("80", false) + server.AddHTTP2HTTPS() + case constant.HTTPAlso: + server.UpdateListen("80", false) + server.RemoveDirective("if", []string{"($scheme"}) + } + if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { return err } - if err := createPemFile(website, websiteSSL); err != nil { return err } @@ -325,7 +337,6 @@ func applySSL(website model.Website, websiteSSL model.WebsiteSSL) error { if err := updateNginxConfig(constant.NginxScopeServer, nginxParams, &website); err != nil { return err } - return nil } diff --git a/backend/constant/website.go b/backend/constant/website.go index 22e1de807..d8282f7b2 100644 --- a/backend/constant/website.go +++ b/backend/constant/website.go @@ -28,4 +28,8 @@ const ( StartWeb = "start" StopWeb = "stop" + + HTTPSOnly = "HTTPSOnly" + HTTPAlso = "HTTPAlso" + HTTPToHTTPS = "HTTPToHTTPS" ) diff --git a/backend/utils/nginx/components/server.go b/backend/utils/nginx/components/server.go index cdb3b89ec..1fd2b010f 100644 --- a/backend/utils/nginx/components/server.go +++ b/backend/utils/nginx/components/server.go @@ -242,13 +242,26 @@ func (s *Server) UpdateDirectiveBySecondKey(name string, key string, directive D } func (s *Server) RemoveListenByBind(bind string) { - index := 0 - listens := s.Listens + var listens []*ServerListen for _, listen := range s.Listens { if listen.Bind != bind || len(listen.Parameters) > 0 { - listens[index] = listen - index++ + listens = append(listens, listen) } } s.Listens = listens } + +func (s *Server) AddHTTP2HTTPS() { + newDir := Directive{ + Name: "if", + Parameters: []string{"($scheme = http)"}, + Block: &Block{}, + } + block := &Block{} + block.Directives = append(block.Directives, &Directive{ + Name: "return", + Parameters: []string{"301", "https://$host$request_uri"}, + }) + newDir.Block = block + s.UpdateDirectiveBySecondKey("if", "($scheme", newDir) +} diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index f55c1b0bd..59655e7d3 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -204,11 +204,13 @@ export namespace Website { type: string; certificate?: string; privateKey?: string; + httpConfig: string; } export interface HTTPSConfig { enable: boolean; SSL: SSL; + httpConfig: string; } export interface CheckReq { diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index f6ae1149b..b67595ca6 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -921,6 +921,16 @@ export default { logFoler: '网站日志', sslFolder: '网站证书', enableOrNot: '是否启用', + oldSSL: '选择已有证书', + manualSSL: '手动导入证书', + select: '选择', + selectSSL: '选择证书', + privateKey: '密钥代码(pem格式)', + certificate: '密钥代码(pem格式)', + HTTPConfig: 'HTTP选项', + HTTPSOnly: '禁止HTTTP', + HTTPToHTTPS: '访问HTTP自动跳转到HTTPS', + HTTPAlso: 'HTTP可直接访问', }, nginx: { serverNamesHashBucketSizeHelper: '服务器名字的hash表大小', diff --git a/frontend/src/views/website/website/config/basic/https/index.vue b/frontend/src/views/website/website/config/basic/https/index.vue index 03d321a81..fcb996a63 100644 --- a/frontend/src/views/website/website/config/basic/https/index.vue +++ b/frontend/src/views/website/website/config/basic/https/index.vue @@ -9,22 +9,29 @@ :rules="rules" :loading="loading" > - <el-form-item prop="websiteSSLId"> + <el-form-item prop="enable"> <el-checkbox v-model="form.enable"> {{ $t('website.enableHTTPS') }} </el-checkbox> </el-form-item> + <el-form-item :label="$t('website.HTTPConfig')" prop="httpConfig"> + <el-select v-model="form.httpConfig" style="width: 240px"> + <el-option :label="$t('website.HTTPToHTTPS')" :value="'HTTPToHTTPS'"></el-option> + <el-option :label="$t('website.HTTPAlso')" :value="'HTTPAlso'"></el-option> + <el-option :label="$t('website.HTTPSOnly')" :value="'HTTPSOnly'"></el-option> + </el-select> + </el-form-item> <el-form-item :label="$t('website.ssl')" prop="type"> - <el-select v-model="form.type" @change="changeType()"> - <el-option :label="'选择已有证书'" :value="'existed'"></el-option> - <el-option :label="'手动导入证书'" :value="'manual'"></el-option> + <el-select v-model="form.type" @change="changeType(form.type)"> + <el-option :label="$t('website.oldSSL')" :value="'existed'"></el-option> + <el-option :label="$t('website.manualSSL')" :value="'manual'"></el-option> <!-- <el-option :label="'自动生成证书'" :value="'auto'"></el-option> --> </el-select> </el-form-item> - <el-form-item :label="' '" prop="websiteSSLId" v-if="form.type === 'existed'"> + <el-form-item :label="$t('website.select')" prop="websiteSSLId" v-if="form.type === 'existed'"> <el-select v-model="form.websiteSSLId" - placeholder="选择证书" + :placeholder="$t('website.selectSSL')" @change="changeSSl(form.websiteSSLId)" > <el-option @@ -36,18 +43,22 @@ </el-select> </el-form-item> <div v-if="form.type === 'manual'"> - <el-form-item :label="'密钥代码(pem格式)'" prop="privateKey"> + <el-form-item :label="$t('website.privateKey')" prop="privateKey"> <el-input v-model="form.privateKey" :rows="6" type="textarea" /> </el-form-item> - <el-form-item :label="'证书代码(pem格式)'" prop="certificate"> + <el-form-item :label="$t('website.certificate')" prop="certificate"> <el-input v-model="form.certificate" :rows="6" type="textarea" /> </el-form-item> </div> <el-form-item :label="' '" v-if="websiteSSL && websiteSSL.id > 0"> <el-descriptions :column="3" border direction="vertical"> - <el-descriptions-item label="主域名">{{ websiteSSL.primaryDomain }}</el-descriptions-item> - <el-descriptions-item label="备用域名">{{ websiteSSL.otherDomains }}</el-descriptions-item> - <el-descriptions-item label="过期时间"> + <el-descriptions-item :label="$t('website.primaryDomain')"> + {{ websiteSSL.primaryDomain }} + </el-descriptions-item> + <el-descriptions-item :label="$t('website.otherDomains')"> + {{ websiteSSL.otherDomains }} + </el-descriptions-item> + <el-descriptions-item :label="$t('website.expireDate')"> {{ dateFromat(1, 1, websiteSSL.expireDate) }} </el-descriptions-item> </el-descriptions> @@ -87,6 +98,7 @@ let form = reactive({ type: 'existed', privateKey: '', certificate: '', + httpConfig: 'HTTPToHTTPS', }); let loading = ref(false); const ssls = ref(); @@ -95,11 +107,14 @@ let rules = ref({ type: [Rules.requiredSelect], privateKey: [Rules.requiredInput], certificate: [Rules.requiredInput], + websiteSSLId: [Rules.requiredSelect], + httpConfig: [Rules.requiredSelect], }); const listSSL = () => { ListSSL({}).then((res) => { ssls.value = res.data; + changeSSl(form.websiteSSLId); }); }; @@ -110,15 +125,19 @@ const changeSSl = (sslid: number) => { websiteSSL.value = res[0]; }; -const changeType = () => { - websiteSSL.value = {}; - form.websiteSSLId = undefined; +const changeType = (type: string) => { + if (type != 'existed') { + websiteSSL.value = {}; + form.websiteSSLId = undefined; + } }; const get = () => { GetHTTPSConfig(id.value).then((res) => { + console.log(res); if (res.data) { form.enable = res.data.enable; + form.httpConfig = res.data.httpConfig; } if (res.data?.SSL && res.data?.SSL.id > 0) { form.websiteSSLId = res.data.SSL.id;