From 9eee2d6b6f62cd3166a09fe0e89f574d62c6086b Mon Sep 17 00:00:00 2001 From: Snrat Date: Wed, 13 Aug 2025 09:55:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=90=AF=E7=94=A8HSTS?= =?UTF-8?q?=E5=AD=90=E5=9F=9F=E5=90=8D=E9=80=89=E9=A1=B9=20(#9971)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add HSTS Subdomains * Fix Prettier Error --- agent/app/dto/request/website.go | 1 + agent/app/dto/response/website.go | 1 + agent/app/service/website.go | 11 +++++++- agent/app/service/website_utils.go | 25 +++++++++++++++---- core/cmd/server/docs/docs.go | 6 +++++ core/cmd/server/docs/swagger.json | 6 +++++ frontend/src/api/interface/website.ts | 1 + frontend/src/lang/modules/en.ts | 3 +++ frontend/src/lang/modules/ja.ts | 2 ++ frontend/src/lang/modules/ko.ts | 2 ++ frontend/src/lang/modules/ms.ts | 3 +++ frontend/src/lang/modules/pt-br.ts | 3 +++ frontend/src/lang/modules/ru.ts | 3 +++ frontend/src/lang/modules/tr.ts | 3 +++ frontend/src/lang/modules/zh-Hant.ts | 2 ++ frontend/src/lang/modules/zh.ts | 2 ++ .../website/config/basic/https/index.vue | 16 ++++++++++++ 17 files changed, 84 insertions(+), 6 deletions(-) diff --git a/agent/app/dto/request/website.go b/agent/app/dto/request/website.go index 3a623ff85..15c86276f 100644 --- a/agent/app/dto/request/website.go +++ b/agent/app/dto/request/website.go @@ -165,6 +165,7 @@ type WebsiteHTTPSOp struct { SSLProtocol []string `json:"SSLProtocol"` Algorithm string `json:"algorithm"` Hsts bool `json:"hsts"` + HstsIncludeSubDomains bool `json:"hstsIncludeSubDomains"` HttpsPorts []int `json:"httpsPorts"` Http3 bool `json:"http3"` } diff --git a/agent/app/dto/response/website.go b/agent/app/dto/response/website.go index ed0ad4cdb..3f4570e1a 100644 --- a/agent/app/dto/response/website.go +++ b/agent/app/dto/response/website.go @@ -65,6 +65,7 @@ type WebsiteHTTPS struct { SSLProtocol []string `json:"SSLProtocol"` Algorithm string `json:"algorithm"` Hsts bool `json:"hsts"` + HstsIncludeSubDomains bool `json:"hstsIncludeSubDomains"` HttpsPorts []int `json:"httpsPorts"` HttpsPort string `json:"httpsPort"` Http3 bool `json:"http3"` diff --git a/agent/app/service/website.go b/agent/app/service/website.go index 24b9ec543..4b4841ebf 100644 --- a/agent/app/service/website.go +++ b/agent/app/service/website.go @@ -474,6 +474,7 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error) SSLProtocol: []string{"TLSv1.3", "TLSv1.2"}, 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", Hsts: true, + HstsIncludeSubDomains: true, } if err = applySSL(website, *websiteModel, appSSLReq); err != nil { return err @@ -959,6 +960,13 @@ func (w WebsiteService) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, if p.Name == "add_header" && len(p.Params) > 0 { if p.Params[0] == "Strict-Transport-Security" { res.Hsts = true + //增加HSTS下子域名检查逻辑 + if len(p.Params) > 1 { + hstsValue := p.Params[1] + if strings.Contains(hstsValue, "includeSubDomains") { + res.HstsIncludeSubDomains = true + } + } } if p.Params[0] == "Alt-Svc" { res.Http3 = true @@ -977,12 +985,13 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH res response.WebsiteHTTPS websiteSSL model.WebsiteSSL ) - if err = ChangeHSTSConfig(req.Hsts, req.Http3, website); err != nil { + if err = ChangeHSTSConfig(req.Hsts, req.HstsIncludeSubDomains, req.Http3, website); err != nil { return nil, err } res.Enable = req.Enable res.SSLProtocol = req.SSLProtocol res.Algorithm = req.Algorithm + res.HstsIncludeSubDomains = req.HstsIncludeSubDomains if !req.Enable { website.Protocol = constant.ProtocolHTTP website.WebsiteSSLID = 0 diff --git a/agent/app/service/website_utils.go b/agent/app/service/website_utils.go index 5298cce9a..1fa887b8f 100644 --- a/agent/app/service/website_utils.go +++ b/agent/app/service/website_utils.go @@ -753,6 +753,7 @@ func applySSL(website *model.Website, websiteSSL model.WebsiteSSL, req request.W if !req.Hsts { server.RemoveDirective("add_header", []string{"Strict-Transport-Security", "\"max-age=31536000\""}) + server.RemoveDirective("add_header", []string{"Strict-Transport-Security", "\"max-age=31536000; includeSubDomains\""}) } if !req.Http3 { for port := range httpsPorts { @@ -793,9 +794,15 @@ func applySSL(website *model.Website, websiteSSL model.WebsiteSSL, req request.W } } if req.Hsts { + var hstsValue string + if req.HstsIncludeSubDomains { + hstsValue = "\"max-age=31536000; includeSubDomains\"" + } else { + hstsValue = "\"max-age=31536000\"" + } nginxParams = append(nginxParams, dto.NginxParam{ Name: "add_header", - Params: []string{"Strict-Transport-Security", "\"max-age=31536000\""}, + Params: []string{"Strict-Transport-Security", hstsValue}, }) } if req.Http3 { @@ -1182,7 +1189,7 @@ func UpdateSSLConfig(websiteSSL model.WebsiteSSL) error { return nil } -func ChangeHSTSConfig(enable bool, http3Enable bool, website model.Website) error { +func ChangeHSTSConfig(enable bool, includeSubDomains bool, http3Enable bool, website model.Website) error { includeDir := GetSitePath(website, SiteProxyDir) fileOp := files.NewFileOp() if !fileOp.Stat(includeDir) { @@ -1208,11 +1215,19 @@ func ChangeHSTSConfig(enable bool, http3Enable bool, website model.Website) erro if !ok { return nil } + //前置移除HSTS配置 + location.RemoveDirective("add_header", []string{"Strict-Transport-Security", "\"max-age=31536000\""}) + location.RemoveDirective("add_header", []string{"Strict-Transport-Security", "\"max-age=31536000; includeSubDomains\""}) if enable { - location.UpdateDirective("add_header", []string{"Strict-Transport-Security", "\"max-age=31536000\""}) - } else { - location.RemoveDirective("add_header", []string{"Strict-Transport-Security", "\"max-age=31536000\""}) + var hstsValue string + if includeSubDomains { + hstsValue = "\"max-age=31536000; includeSubDomains\"" + } else { + hstsValue = "\"max-age=31536000\"" + } + location.UpdateDirective("add_header", []string{"Strict-Transport-Security", hstsValue}) } + if http3Enable { location.UpdateDirective("add_header", []string{"Alt-Svc", "'h3=\":443\"; ma=2592000'"}) } else { diff --git a/core/cmd/server/docs/docs.go b/core/cmd/server/docs/docs.go index a345f8c1b..e8abcfd5a 100644 --- a/core/cmd/server/docs/docs.go +++ b/core/cmd/server/docs/docs.go @@ -26932,6 +26932,9 @@ const docTemplate = `{ "hsts": { "type": "boolean" }, + "hstsIncludeSubDomains": { + "type": "boolean" + }, "http3": { "type": "boolean" }, @@ -28911,6 +28914,9 @@ const docTemplate = `{ "hsts": { "type": "boolean" }, + "hstsIncludeSubDomains": { + "type": "boolean" + }, "http3": { "type": "boolean" }, diff --git a/core/cmd/server/docs/swagger.json b/core/cmd/server/docs/swagger.json index 8590c011e..ed649e530 100644 --- a/core/cmd/server/docs/swagger.json +++ b/core/cmd/server/docs/swagger.json @@ -26928,6 +26928,9 @@ "hsts": { "type": "boolean" }, + "hstsIncludeSubDomains": { + "type": "boolean" + }, "http3": { "type": "boolean" }, @@ -28907,6 +28910,9 @@ "hsts": { "type": "boolean" }, + "hstsIncludeSubDomains": { + "type": "boolean" + }, "http3": { "type": "boolean" }, diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index bef6ff6aa..4e79f75ac 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -318,6 +318,7 @@ export namespace Website { SSLProtocol: string[]; algorithm: string; hsts: boolean; + hstsIncludeSubDomains: boolean; httpsPort?: string; http3: boolean; } diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 7df1dd4d3..6e578a54e 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -2435,6 +2435,9 @@ const message = { '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 site to be accessed normally.', hstsHelper: 'Enabling HSTS can increase website security', + includeSubDomains: 'SubDomains', + hstsIncludeSubDomainsHelper: + 'Once enabled, the HSTS policy will apply to all subdomains of the current domain.', defaultHtml: 'Set default page', website404: 'Website 404 error page', domain404: 'Website page does not exist', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 667642e6c..939a1c0ab 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -2350,6 +2350,8 @@ const message = { ipWebsiteWarn: 'ドメイン名としてIPを持つWebサイトは、正常にアクセスするデフォルトサイトとして設定する必要があります。', hstsHelper: 'HSTを有効にすると、Webサイトのセキュリティが向上する可能性があります', + includeSubDomains: 'サブドメイン', + hstsIncludeSubDomainsHelper: '有効化すると、HSTSポリシーが現在のドメインのすべてのサブドメインに適用されます。', defaultHtml: 'デフォルトページ', website404: 'ウェブサイト404エラーページ', domain404: 'ウェブサイトドメインは存在しません', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 94fe1a46f..f6ee68aaf 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -2310,6 +2310,8 @@ const message = { ipWebsiteWarn: 'IP를 도메인 이름으로 사용하는 웹사이트는 정상적으로 접속되기 위해 기본 사이트로 설정해야 합니다.', hstsHelper: 'HSTS 를 활성화하면 웹사이트 보안을 강화할 수 있습니다.', + includeSubDomains: '서브도메인', + hstsIncludeSubDomainsHelper: '활성화하면 HSTS 정책이 현재 도메인의 모든 서브도메인에 적용됩니다.', defaultHtml: '기본 페이지', website404: '웹사이트 404 오류 페이지', domain404: '웹사이트 도메인이 존재하지 않습니다.', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index ab5a73755..d17580ffa 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -2405,6 +2405,9 @@ const message = { ipWebsiteWarn: 'Laman web dengan IP sebagai nama domain perlu disetkan sebagai laman web lalai untuk diakses secara normal.', hstsHelper: 'Mengaktifkan HSTS boleh meningkatkan keselamatan laman web', + includeSubDomains: 'SubDomains', + hstsIncludeSubDomainsHelper: + 'Apabila diaktifkan, dasar HSTS akan digunakan pada semua subdomain bagi domain semasa.', defaultHtml: 'Halaman lalai', website404: 'Halaman ralat 404 laman web', domain404: 'Domain laman web tidak wujud', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 25a4d48f6..510107d04 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -2405,6 +2405,9 @@ const message = { ipWebsiteWarn: 'Sites com IP como nomes de domínio precisam ser configurados como site padrão para serem acessados normalmente', hstsHelper: 'Ativar HSTS pode aumentar a segurança do site', + includeSubDomains: 'SubDomains', + hstsIncludeSubDomainsHelper: + 'Quando ativado, a política HSTS será aplicada a todos os subdomínios do domínio atual.', defaultHtml: 'Página padrão', website404: 'Página de erro 404 do site', domain404: 'O domínio do site não existe', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 27bb17c81..f618ee6ff 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -2401,6 +2401,9 @@ const message = { ipWebsiteWarn: 'Веб-сайты с IP в качестве домена должны быть установлены как сайт по умолчанию для нормального доступа.', hstsHelper: 'Включение HSTS может повысить безопасность веб-сайта', + includeSubDomains: 'Поддомены', + hstsIncludeSubDomainsHelper: + 'После включения политика HSTS будет применяться ко всем поддоменам текущего домена.', defaultHtml: 'Страница по умолчанию', website404: 'Страница ошибки 404 веб-сайта', domain404: 'Домен веб-сайта не существует', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 38a7361d5..cab52856c 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -2464,6 +2464,9 @@ const message = { ipWebsiteWarn: 'IP alan adlarına sahip web sitelerinin normal şekilde erişilebilmesi için varsayılan site olarak ayarlanması gerekir.', hstsHelper: 'HSTS’nin etkinleştirilmesi web sitesi güvenliğini artırabilir', + includeSubDomains: 'Alt Alan Adları', + hstsIncludeSubDomainsHelper: + 'Etkinleştirildiğinde, HSTS politikası geçerli etki alanının tüm alt alan adlarına uygulanacaktır.', defaultHtml: 'Varsayılan sayfayı ayarla', website404: 'Web sitesi 404 hata sayfası', domain404: 'Web sitesi sayfası mevcut değil', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 87a329400..a86c4014d 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -2273,6 +2273,8 @@ const message = { websiteBackupWarn: '僅支援導入本機備份,導入其他機器備份可能會恢復失敗', ipWebsiteWarn: 'IP 為網域名稱的網站,需要設定為預設網站才能正常存取', hstsHelper: '開啟 HSTS 可以增加網站安全性', + includeSubDomains: '子域', + hstsIncludeSubDomainsHelper: '啟用後,HSTS策略將應用於目前域名的所有子域名', defaultHtml: '預設頁面', website404: '網站 404 錯誤頁', domain404: '網站不存在頁面', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 36d5b48ae..8da66746f 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -2262,6 +2262,8 @@ const message = { websiteBackupWarn: '仅支持导入本机备份,导入其他机器备份可能会恢复失败', ipWebsiteWarn: 'IP 为域名的网站,需要设置为默认站点才能正常访问', hstsHelper: '开启 HSTS 可以增加网站安全性', + includeSubDomains: '子域', + hstsIncludeSubDomainsHelper: '启用后,HSTS策略将应用于当前域名的所有子域名', defaultHtml: '默认页面', website404: '网站 404 错误页', domain404: '网站不存在页', 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 446d6f197..c8d228070 100644 --- a/frontend/src/views/website/website/config/basic/https/index.vue +++ b/frontend/src/views/website/website/config/basic/https/index.vue @@ -29,6 +29,18 @@ {{ $t('commons.button.enable') }} {{ $t('website.hstsHelper') }} + + + {{ $t('commons.button.enable') }} + + + {{ $t('website.hstsIncludeSubDomainsHelper') }} + + {{ $t('commons.button.enable') }} {{ $t('website.http3Helper') }} @@ -205,6 +217,7 @@ const form = reactive({ certificatePath: '', httpConfig: 'HTTPToHTTPS', hsts: true, + hstsIncludeSubDomains: false, 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'], @@ -314,6 +327,7 @@ const get = () => { form.acmeAccountID = data.SSL.acmeAccountId; } form.hsts = data.hsts; + form.hstsIncludeSubDomains = data.hstsIncludeSubDomains || false; form.http3 = data.http3; form.httpsPort = data.httpsPort; } @@ -344,6 +358,8 @@ const changeEnable = (enable: boolean) => { if (enable) { listSSLs(); form.hsts = true; + } else { + form.hstsIncludeSubDomains = false; } if (resData.value.enable && !enable) { ElMessageBox.confirm(i18n.global.t('website.disableHTTPSHelper'), i18n.global.t('website.disableHTTPS'), {