diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index e44cc90a4bfbf..c315cb38f0562 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -787,3 +787,13 @@ IS_INPUT_FILE = false ENABLED = false ; If you want to add authorization, specify a token here TOKEN = + +[hsts] +; Enables hsts. True or false, default is false. +ENABLED = false +; Max age of the time, default is 365 days. +MAX_AGE = 8760h +; require sub domains use hsts +INCLUDE_SUB_DOMAINS = false +; send preload +SEND_PRELOAD_DIRECTIVE = false \ No newline at end of file diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 61905f8ad8d9e..0833583f212b5 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -501,6 +501,13 @@ Two special environment variables are passed to the render command: - `GITEA_PREFIX_SRC`, which contains the current URL prefix in the `src` path tree. To be used as prefix for links. - `GITEA_PREFIX_RAW`, which contains the current URL prefix in the `raw` path tree. To be used as prefix for image paths. +## HSTS (`hsts`) + +- ENABLED: **false** Enables hsts. True or false, default is false. +- MAX_AGE: **8760h** Max age of the time, default is 365 days. +- INCLUDE_SUB_DOMAINS: **false** require sub domains use hsts, default is false. +- SEND_PRELOAD_DIRECTIVE: **false** send preload, default is false. + ## Other (`other`) - `SHOW_FOOTER_BRANDING`: **false**: Show Gitea branding in the footer. diff --git a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md index b9a16dd844caa..f2628248e8180 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md +++ b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md @@ -237,7 +237,12 @@ IS_INPUT_FILE = false - RENDER_COMMAND: 工具的命令行命令及参数。 - IS_INPUT_FILE: 输入方式是最后一个参数为文件路径还是从标准输入读取。 +## HSTS (`hsts`) +- ENABLED: **false** 是否启用 HSTS。默认为否。 +- MAX_AGE: **8760h** 最大时间,默认是 365 天。 +- INCLUDE_SUB_DOMAINS: **false** 是否包含子域名,默认为否。 +- SEND_PRELOAD_DIRECTIVE: **false** 是否预加载,默认为否。 ## Other (`other`) diff --git a/modules/setting/hsts.go b/modules/setting/hsts.go new file mode 100644 index 0000000000000..0fd2a0e89d2f4 --- /dev/null +++ b/modules/setting/hsts.go @@ -0,0 +1,36 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package setting + +import "time" + +const ( + defaultMaxAge = time.Hour * 24 * 365 +) + +// HSTS is the configuration of HSTS +var HSTS = struct { + Enabled bool + MaxAge time.Duration + IncludeSubDomains bool + SendPreloadDirective bool +}{ + Enabled: false, + MaxAge: defaultMaxAge, + IncludeSubDomains: false, + SendPreloadDirective: false, +} + +func configHSTS() { + sec := Cfg.Section("hsts") + if !sec.Key("ENABLED").MustBool() { + return + } + + HSTS.Enabled = true + HSTS.MaxAge = sec.Key("MAX_AGE").MustDuration(defaultMaxAge) + HSTS.IncludeSubDomains = sec.Key("INCLUDE_SUB_DOMAINS").MustBool() + HSTS.SendPreloadDirective = sec.Key("SEND_PRELOAD_DIRECTIVE").MustBool() +} diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 7201f0619d4d3..a175ce91ad512 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -910,6 +910,7 @@ func NewContext() { newCron() newGit() + configHSTS() sec = Cfg.Section("mirror") Mirror.MinInterval = sec.Key("MIN_INTERVAL").MustDuration(10 * time.Minute) diff --git a/routers/routes/routes.go b/routers/routes/routes.go index ec57e8f5fdac7..4dc6106ac151e 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -10,6 +10,7 @@ import ( "net/http" "os" "path" + "strconv" "text/template" "time" @@ -102,6 +103,18 @@ func RouterHandler(level log.Level) func(ctx *macaron.Context) { } } +func createHeaderValueNew(maxAge time.Duration, includeSubDomains, sendPreloadDirective bool) string { + buf := bytes.NewBufferString("max-age=") + buf.WriteString(strconv.Itoa(int(maxAge.Seconds()))) + if includeSubDomains { + buf.WriteString("; includeSubDomains") + } + if sendPreloadDirective { + buf.WriteString("; preload") + } + return buf.String() +} + // NewMacaron initializes Macaron instance. func NewMacaron() *macaron.Macaron { gob.Register(&u2f.Challenge{}) @@ -131,6 +144,14 @@ func NewMacaron() *macaron.Macaron { if setting.Protocol == setting.FCGI { m.SetURLPrefix(setting.AppSubURL) } + if setting.HSTS.Enabled { + m.Use(func() macaron.Handler { + return func(ctx *macaron.Context) { + ctx.Resp.Header().Set("Strict-Transport-Security", + createHeaderValueNew(setting.HSTS.MaxAge, setting.HSTS.IncludeSubDomains, setting.HSTS.SendPreloadDirective)) + } + }) + } m.Use(public.Custom( &public.Options{ SkipLogging: setting.DisableRouterLog,