diff --git a/backend/app/dto/request/website.go b/backend/app/dto/request/website.go index faaa33a25..2cd425e01 100644 --- a/backend/app/dto/request/website.go +++ b/backend/app/dto/request/website.go @@ -123,6 +123,7 @@ type WebsiteHTTPSOp struct { HttpConfig string `json:"httpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"` SSLProtocol []string `json:"SSLProtocol"` Algorithm string `json:"algorithm"` + Hsts bool `json:"hsts"` } type WebsiteNginxUpdate struct { diff --git a/backend/app/dto/response/website.go b/backend/app/dto/response/website.go index dd2fe1486..b80368224 100644 --- a/backend/app/dto/response/website.go +++ b/backend/app/dto/response/website.go @@ -37,6 +37,7 @@ type WebsiteHTTPS struct { SSL model.WebsiteSSL `json:"SSL"` SSLProtocol []string `json:"SSLProtocol"` Algorithm string `json:"algorithm"` + Hsts bool `json:"hsts"` } type WebsiteLog struct { diff --git a/backend/app/service/website.go b/backend/app/service/website.go index 8d301d4b9..624e2dfe0 100644 --- a/backend/app/service/website.go +++ b/backend/app/service/website.go @@ -754,7 +754,7 @@ func (w WebsiteService) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, } else { res.HttpConfig = constant.HTTPToHTTPS } - params, err := getNginxParamsByKeys(constant.NginxScopeServer, []string{"ssl_protocols", "ssl_ciphers"}, &website) + params, err := getNginxParamsByKeys(constant.NginxScopeServer, []string{"ssl_protocols", "ssl_ciphers", "add_header"}, &website) if err != nil { return res, err } @@ -765,6 +765,9 @@ func (w WebsiteService) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, if p.Name == "ssl_ciphers" { res.Algorithm = p.Params[0] } + if p.Name == "add_header" && len(p.Params) > 0 && p.Params[0] == "Strict-Transport-Security" { + res.Hsts = true + } } return res, nil } @@ -782,7 +785,7 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH if err != nil { return nil, err } - if err = ChangeHSTSConfig(req.Enable, nginxInstall, website); err != nil { + if err = ChangeHSTSConfig(req.Hsts, nginxInstall, website); err != nil { return nil, err } res.Enable = req.Enable @@ -1616,9 +1619,6 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error) } location.UpdateDirective("proxy_pass", []string{req.ProxyPass}) location.UpdateDirective("proxy_set_header", []string{"Host", req.ProxyHost}) - if website.Protocol == constant.ProtocolHTTPS { - location.UpdateDirective("add_header", []string{"Strict-Transport-Security", "\"max-age=31536000\""}) - } location.ChangePath(req.Modifier, req.Match) if req.Cache { location.AddCache(req.CacheTime, req.CacheUnit) diff --git a/backend/app/service/website_utils.go b/backend/app/service/website_utils.go index 40ef2c20a..bdd76692c 100644 --- a/backend/app/service/website_utils.go +++ b/backend/app/service/website_utils.go @@ -609,6 +609,10 @@ func applySSL(website model.Website, websiteSSL model.WebsiteSSL, req request.We } } + if !req.Hsts { + server.RemoveDirective("add_header", []string{"Strict-Transport-Security", "\"max-age=31536000\""}) + } + if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { return err } @@ -630,6 +634,13 @@ func applySSL(website model.Website, websiteSSL model.WebsiteSSL, req request.We nginxParams[i].Params = []string{req.Algorithm} } } + if req.Hsts { + nginxParams = append(nginxParams, dto.NginxParam{ + Name: "add_header", + Params: []string{"Strict-Transport-Security", "\"max-age=31536000\""}, + }) + } + if err := updateNginxConfig(constant.NginxScopeServer, nginxParams, &website); err != nil { return err } diff --git a/cmd/server/nginx_conf/ssl.conf b/cmd/server/nginx_conf/ssl.conf index cc1786e91..d5c87bac6 100644 --- a/cmd/server/nginx_conf/ssl.conf +++ b/cmd/server/nginx_conf/ssl.conf @@ -5,7 +5,6 @@ ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDS ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; -add_header Strict-Transport-Security "max-age=31536000"; error_page 497 https://$host$request_uri; proxy_set_header X-Forwarded-Proto https; ssl_stapling on; diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index 25ed25f4e..a34070a10 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -263,6 +263,7 @@ export namespace Website { httpConfig: string; SSLProtocol: string[]; algorithm: string; + hsts: boolean; } export interface CheckReq { diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 952dd3435..fe4e6b702 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -2026,6 +2026,7 @@ const message = { websiteBackupWarn: 'Only supports importing local backups, importing backups from other machines may cause recovery failure', ipWebsiteWarn: 'Websites with IP as domain names need to be set as default sites to be accessed normally', + hstsHelper: 'Enabling HSTS can increase website security', }, php: { short_open_tag: 'Short tag support', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index de9b5376e..54926ebf3 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -1887,6 +1887,7 @@ const message = { rewriteHelper2: '從應用程式商店安裝的 WordPress 等應用,預設已經配置好偽靜態,重複配置可能會報錯', websiteBackupWarn: '僅支援導入本機備份,導入其他機器備份可能會恢復失敗', ipWebsiteWarn: 'IP 為網域名稱的網站,需要設定為預設網站才能正常存取', + hstsHelper: '開啟 HSTS 可以增加網站安全性', }, php: { short_open_tag: '短標簽支持', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index db3dad369..e217a6596 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1888,6 +1888,7 @@ const message = { rewriteHelper2: '从应用商店安装的 WordPress 等应用,默认已经配置好伪静态,重复配置可能会报错', websiteBackupWarn: '仅支持导入本机备份,导入其他机器备份可能会恢复失败', ipWebsiteWarn: 'IP 为域名的网站,需要设置为默认站点才能正常访问', + hstsHelper: '开启 HSTS 可以增加网站安全性', }, php: { short_open_tag: '短标签支持', 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 a1a8fba4c..316895e95 100644 --- a/frontend/src/views/website/website/config/basic/https/index.vue +++ b/frontend/src/views/website/website/config/basic/https/index.vue @@ -22,6 +22,10 @@ + + {{ $t('commons.button.start') }} + {{ $t('website.hstsHelper') }} + @@ -190,6 +194,7 @@ const form = reactive({ privateKeyPath: '', certificatePath: '', httpConfig: 'HTTPToHTTPS', + hsts: true, algorithm: 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!SRP:!CAMELLIA:!SEED', SSLProtocol: ['TLSv1.3', 'TLSv1.2', 'TLSv1.1', 'TLSv1'], @@ -199,6 +204,7 @@ const ssls = ref(); const acmeAccounts = ref(); const websiteSSL = ref(); const rules = ref({ + hsts: [Rules.requiredInput], type: [Rules.requiredSelect], privateKey: [Rules.requiredInput], certificate: [Rules.requiredInput], @@ -285,6 +291,7 @@ const get = () => { websiteSSL.value = res.data.SSL; form.acmeAccountID = res.data.SSL.acmeAccountId; } + form.hsts = res.data.hsts; } listSSL(); listAcmeAccount();