添加启用HSTS子域名选项 (#9971)

* Add HSTS Subdomains

* Fix Prettier Error
This commit is contained in:
Snrat 2025-08-13 09:55:48 +08:00 committed by GitHub
parent c9d4e2a4b5
commit 9eee2d6b6f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 84 additions and 6 deletions

View file

@ -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"`
}

View file

@ -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"`

View file

@ -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

View file

@ -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 {

View file

@ -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"
},

View file

@ -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"
},

View file

@ -318,6 +318,7 @@ export namespace Website {
SSLProtocol: string[];
algorithm: string;
hsts: boolean;
hstsIncludeSubDomains: boolean;
httpsPort?: string;
http3: boolean;
}

View file

@ -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',

View file

@ -2350,6 +2350,8 @@ const message = {
ipWebsiteWarn:
'ドメイン名としてIPを持つWebサイトは正常にアクセスするデフォルトサイトとして設定する必要があります',
hstsHelper: 'HSTを有効にするとWebサイトのセキュリティが向上する可能性があります',
includeSubDomains: 'サブドメイン',
hstsIncludeSubDomainsHelper: '有効化するとHSTSポリシーが現在のドメインのすべてのサブドメインに適用されます',
defaultHtml: 'デフォルトページ',
website404: 'ウェブサイト404エラーページ',
domain404: 'ウェブサイトドメインは存在しません',

View file

@ -2310,6 +2310,8 @@ const message = {
ipWebsiteWarn:
'IP를 도메인 이름으로 사용하는 웹사이트는 정상적으로 접속되기 위해 기본 사이트로 설정해야 합니다.',
hstsHelper: 'HSTS 활성화하면 웹사이트 보안을 강화할 있습니다.',
includeSubDomains: '서브도메인',
hstsIncludeSubDomainsHelper: '활성화하면 HSTS 정책이 현재 도메인의 모든 서브도메인에 적용됩니다.',
defaultHtml: '기본 페이지',
website404: '웹사이트 404 오류 페이지',
domain404: '웹사이트 도메인이 존재하지 않습니다.',

View file

@ -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',

View file

@ -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',

View file

@ -2401,6 +2401,9 @@ const message = {
ipWebsiteWarn:
'Веб-сайты с IP в качестве домена должны быть установлены как сайт по умолчанию для нормального доступа.',
hstsHelper: 'Включение HSTS может повысить безопасность веб-сайта',
includeSubDomains: 'Поддомены',
hstsIncludeSubDomainsHelper:
'После включения политика HSTS будет применяться ко всем поддоменам текущего домена.',
defaultHtml: 'Страница по умолчанию',
website404: 'Страница ошибки 404 веб-сайта',
domain404: 'Домен веб-сайта не существует',

View file

@ -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: 'HSTSnin 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',

View file

@ -2273,6 +2273,8 @@ const message = {
websiteBackupWarn: '僅支援導入本機備份導入其他機器備份可能會恢復失敗',
ipWebsiteWarn: 'IP 為網域名稱的網站需要設定為預設網站才能正常存取',
hstsHelper: '開啟 HSTS 可以增加網站安全性',
includeSubDomains: '子域',
hstsIncludeSubDomainsHelper: '啟用後HSTS策略將應用於目前域名的所有子域名',
defaultHtml: '預設頁面',
website404: '網站 404 錯誤頁',
domain404: '網站不存在頁面',

View file

@ -2262,6 +2262,8 @@ const message = {
websiteBackupWarn: '仅支持导入本机备份导入其他机器备份可能会恢复失败',
ipWebsiteWarn: 'IP 为域名的网站需要设置为默认站点才能正常访问',
hstsHelper: '开启 HSTS 可以增加网站安全性',
includeSubDomains: '子域',
hstsIncludeSubDomainsHelper: '启用后HSTS策略将应用于当前域名的所有子域名',
defaultHtml: '默认页面',
website404: '网站 404 错误页',
domain404: '网站不存在页',

View file

@ -29,6 +29,18 @@
<el-checkbox v-model="form.hsts">{{ $t('commons.button.enable') }}</el-checkbox>
<span class="input-help">{{ $t('website.hstsHelper') }}</span>
</el-form-item>
<el-form-item
:label="'HSTS ' + $t('website.includeSubDomains')"
prop="hstsIncludeSubDomains"
v-if="form.hsts"
>
<el-checkbox v-model="form.hstsIncludeSubDomains">
{{ $t('commons.button.enable') }}
</el-checkbox>
<span class="input-help">
{{ $t('website.hstsIncludeSubDomainsHelper') }}
</span>
</el-form-item>
<el-form-item :label="'HTTP3'" prop="http3">
<el-checkbox v-model="form.http3">{{ $t('commons.button.enable') }}</el-checkbox>
<span class="input-help">{{ $t('website.http3Helper') }}</span>
@ -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'), {