- Delete unused files: tempfile subsystem, matcher variants, server/internal - Remove 200+ unused functions across proxy, ssl, lua, http2/3, stream, variable - Fix proxy test type errors (backgroundRefresh ctx→Request) - Move bench/tools mock backend into internal/testutil - Remove corresponding test functions for all deleted code
280 lines
7.5 KiB
Go
280 lines
7.5 KiB
Go
// Package security 提供了 Lolly HTTP 服务器的安全相关中间件。
|
||
//
|
||
// 该文件实现了安全响应头中间件,为响应添加标准安全头部,
|
||
// 以防止常见的 Web 安全漏洞。
|
||
//
|
||
// 实现的安全头包括:
|
||
// - X-Frame-Options: 防止点击劫持攻击
|
||
// - X-Content-Type-Options: 防止 MIME 类型嗅探
|
||
// - Content-Security-Policy: 控制资源加载(XSS 防护)
|
||
// - Strict-Transport-Security: 强制使用 HTTPS(HSTS)
|
||
// - Referrer-Policy: 控制 Referer 信息泄露
|
||
// - Permissions-Policy: 控制浏览器功能权限
|
||
//
|
||
// 使用示例:
|
||
//
|
||
// cfg := &config.SecurityHeaders{
|
||
// XFrameOptions: "DENY",
|
||
// XContentTypeOptions: "nosniff",
|
||
// ContentSecurityPolicy: "default-src 'self'",
|
||
// }
|
||
//
|
||
// headers := security.NewSecurityHeaders(cfg)
|
||
// chain := middleware.NewChain(headers)
|
||
// handler := chain.Apply(finalHandler)
|
||
//
|
||
// 作者:xfy
|
||
package security
|
||
|
||
import (
|
||
"fmt"
|
||
"sync"
|
||
|
||
"github.com/valyala/fasthttp"
|
||
"rua.plus/lolly/internal/config"
|
||
"rua.plus/lolly/internal/middleware"
|
||
)
|
||
|
||
// HeadersMiddleware 安全响应头中间件。
|
||
//
|
||
// 为 HTTP 响应添加安全相关的头部字段,防止常见的 Web 安全漏洞。
|
||
// 支持配置各种安全头的值,并提供安全的默认配置。
|
||
//
|
||
// 注意事项:
|
||
// - 所有方法均为并发安全
|
||
// - HSTS 头仅在 TLS 连接时添加
|
||
type HeadersMiddleware struct {
|
||
config *config.SecurityHeaders // 安全头配置
|
||
hsts string // 预格式化的 HSTS 头值
|
||
mu sync.RWMutex // 读写锁,保护并发访问
|
||
}
|
||
|
||
// NewHeadersWithHSTS 创建新的安全响应头中间件,支持 HSTS 配置。
|
||
//
|
||
// 根据配置创建中间件实例,如果配置为 nil 则使用安全的默认值。
|
||
//
|
||
// 参数:
|
||
// - cfg: 安全头配置,可以为 nil 使用默认配置
|
||
// - hstsCfg: HSTS 配置,可以为 nil 使用默认值
|
||
//
|
||
// 返回值:
|
||
// - *HeadersMiddleware: 配置好的中间件实例
|
||
func NewHeadersWithHSTS(cfg *config.SecurityHeaders, hstsCfg *config.HSTSConfig) *HeadersMiddleware {
|
||
sh := &HeadersMiddleware{}
|
||
|
||
if cfg != nil {
|
||
sh.config = cfg
|
||
} else {
|
||
// 使用安全的默认配置
|
||
sh.config = &config.SecurityHeaders{
|
||
XFrameOptions: "DENY",
|
||
XContentTypeOptions: "nosniff",
|
||
ReferrerPolicy: "strict-origin-when-cross-origin",
|
||
}
|
||
}
|
||
|
||
// 预格式化 HSTS 头值
|
||
sh.formatHSTSFromConfig(hstsCfg)
|
||
|
||
return sh
|
||
}
|
||
|
||
// formatHSTSFromConfig 根据配置格式化 HSTS 头值。
|
||
func (sh *HeadersMiddleware) formatHSTSFromConfig(hstsCfg *config.HSTSConfig) {
|
||
if hstsCfg != nil {
|
||
maxAge := hstsCfg.MaxAge
|
||
if maxAge <= 0 {
|
||
maxAge = 31536000 // 默认 1 年
|
||
}
|
||
sh.hsts = formatHSTSValue(maxAge, hstsCfg.IncludeSubDomains, hstsCfg.Preload)
|
||
} else {
|
||
sh.formatHSTS() // 使用默认值
|
||
}
|
||
}
|
||
|
||
// Name 返回中间件名称。
|
||
//
|
||
// 返回值:
|
||
// - string: 中间件标识名 "security_headers"
|
||
func (sh *HeadersMiddleware) Name() string {
|
||
return "security_headers"
|
||
}
|
||
|
||
// Process 包装下一个处理器,为响应添加安全头。
|
||
//
|
||
// 该方法实现了中间件接口,在调用下一个处理器后添加安全响应头。
|
||
//
|
||
// 参数:
|
||
// - next: 下一个请求处理器
|
||
//
|
||
// 返回值:
|
||
// - fasthttp.RequestHandler: 包装后的处理器
|
||
func (sh *HeadersMiddleware) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||
return func(ctx *fasthttp.RequestCtx) {
|
||
// 先调用下一个处理器
|
||
next(ctx)
|
||
|
||
// 为响应添加安全头
|
||
sh.addHeaders(ctx)
|
||
}
|
||
}
|
||
|
||
// addHeaders 为响应添加所有配置的安全头。
|
||
//
|
||
// 遍历配置的安全头并设置到响应中,使用读锁保护并发访问。
|
||
//
|
||
// 参数:
|
||
// - ctx: FastHTTP 请求上下文
|
||
func (sh *HeadersMiddleware) addHeaders(ctx *fasthttp.RequestCtx) {
|
||
headers := &ctx.Response.Header
|
||
|
||
sh.mu.RLock()
|
||
cfg := sh.config
|
||
hstsValue := sh.hsts
|
||
sh.mu.RUnlock()
|
||
|
||
// X-Frame-Options
|
||
if cfg.XFrameOptions != "" {
|
||
headers.Set("X-Frame-Options", cfg.XFrameOptions)
|
||
}
|
||
|
||
// X-Content-Type-Options (default: nosniff)
|
||
if cfg.XContentTypeOptions != "" {
|
||
headers.Set("X-Content-Type-Options", cfg.XContentTypeOptions)
|
||
} else {
|
||
headers.Set("X-Content-Type-Options", "nosniff")
|
||
}
|
||
|
||
// Content-Security-Policy
|
||
if cfg.ContentSecurityPolicy != "" {
|
||
headers.Set("Content-Security-Policy", cfg.ContentSecurityPolicy)
|
||
}
|
||
|
||
// Strict-Transport-Security (HSTS) - only when TLS is used
|
||
if ctx.IsTLS() && hstsValue != "" {
|
||
headers.Set("Strict-Transport-Security", hstsValue)
|
||
}
|
||
|
||
// Referrer-Policy
|
||
if cfg.ReferrerPolicy != "" {
|
||
headers.Set("Referrer-Policy", cfg.ReferrerPolicy)
|
||
}
|
||
|
||
// Permissions-Policy (formerly Feature-Policy)
|
||
if cfg.PermissionsPolicy != "" {
|
||
headers.Set("Permissions-Policy", cfg.PermissionsPolicy)
|
||
}
|
||
}
|
||
|
||
// formatHSTS 根据配置格式化 HSTS 头值。
|
||
//
|
||
// HSTS(HTTP Strict Transport Security)用于强制浏览器使用 HTTPS 连接。
|
||
// 默认配置为 1 年有效期,包含子域名。
|
||
func (sh *HeadersMiddleware) formatHSTS() {
|
||
// 默认 HSTS 值
|
||
maxAge := 31536000 // 1 年有效期(秒)
|
||
includeSubDomains := true // 包含所有子域名
|
||
preload := false // 不预加载到浏览器列表
|
||
|
||
// 实际使用时应从 SSLConfig.HSTS 获取配置
|
||
// 当前使用默认值
|
||
sh.hsts = formatHSTSValue(maxAge, includeSubDomains, preload)
|
||
}
|
||
|
||
// formatHSTSValue 格式化 HSTS 头值组件。
|
||
//
|
||
// 参数:
|
||
// - maxAge: HSTS 有效期(秒)
|
||
// - includeSubDomains: 是否包含子域名
|
||
// - preload: 是否预加载到浏览器 HSTS 列表
|
||
//
|
||
// 返回值:
|
||
// - string: 格式化后的 HSTS 头值
|
||
func formatHSTSValue(maxAge int, includeSubDomains bool, preload bool) string {
|
||
value := fmt.Sprintf("max-age=%d", maxAge)
|
||
|
||
if includeSubDomains {
|
||
value += "; includeSubDomains"
|
||
}
|
||
|
||
if preload {
|
||
value += "; preload"
|
||
}
|
||
|
||
return value
|
||
}
|
||
|
||
// UpdateConfig 更新安全头配置。
|
||
//
|
||
// 使用写锁保护并发访问,同时更新 HSTS 格式化值。
|
||
//
|
||
// 参数:
|
||
// - cfg: 新的安全头配置
|
||
func (sh *HeadersMiddleware) UpdateConfig(cfg *config.SecurityHeaders) {
|
||
sh.mu.Lock()
|
||
sh.config = cfg
|
||
sh.formatHSTS()
|
||
sh.mu.Unlock()
|
||
}
|
||
|
||
// SetXFrameOptions 设置 X-Frame-Options 头值。
|
||
//
|
||
// 参数:
|
||
// - value: 新的 X-Frame-Options 值(如 "DENY"、"SAMEORIGIN")
|
||
func (sh *HeadersMiddleware) SetXFrameOptions(value string) {
|
||
sh.mu.Lock()
|
||
if sh.config != nil {
|
||
sh.config.XFrameOptions = value
|
||
}
|
||
sh.mu.Unlock()
|
||
}
|
||
|
||
// SetContentSecurityPolicy 设置 CSP 头值。
|
||
//
|
||
// 参数:
|
||
// - value: 新的 Content-Security-Policy 值
|
||
func (sh *HeadersMiddleware) SetContentSecurityPolicy(value string) {
|
||
sh.mu.Lock()
|
||
if sh.config != nil {
|
||
sh.config.ContentSecurityPolicy = value
|
||
}
|
||
sh.mu.Unlock()
|
||
}
|
||
|
||
// SetReferrerPolicy 设置 Referrer-Policy 头值。
|
||
//
|
||
// 参数:
|
||
// - value: 新的 Referrer-Policy 值(如 "no-referrer"、"strict-origin")
|
||
func (sh *HeadersMiddleware) SetReferrerPolicy(value string) {
|
||
sh.mu.Lock()
|
||
if sh.config != nil {
|
||
sh.config.ReferrerPolicy = value
|
||
}
|
||
sh.mu.Unlock()
|
||
}
|
||
|
||
// SetPermissionsPolicy 设置 Permissions-Policy 头值。
|
||
//
|
||
// 参数:
|
||
// - value: 新的 Permissions-Policy 值
|
||
func (sh *HeadersMiddleware) SetPermissionsPolicy(value string) {
|
||
sh.mu.Lock()
|
||
if sh.config != nil {
|
||
sh.config.PermissionsPolicy = value
|
||
}
|
||
sh.mu.Unlock()
|
||
}
|
||
|
||
// GetConfig 返回当前的安全头配置。
|
||
//
|
||
// 返回值:
|
||
// - *config.SecurityHeaders: 当前配置的副本
|
||
func (sh *HeadersMiddleware) GetConfig() *config.SecurityHeaders {
|
||
sh.mu.RLock()
|
||
defer sh.mu.RUnlock()
|
||
return sh.config
|
||
}
|
||
|
||
// 验证接口实现
|
||
var _ middleware.Middleware = (*HeadersMiddleware)(nil)
|