新增功能: - stream 模块: 流式传输支持,优化大文件和实时数据传输 - Goroutine 池: 限制并发数量,减少调度开销 - 优雅升级: 零停机热升级,继承父进程监听器 - sendfile: 零拷贝文件传输,大文件直接从内核传输 重构改进: - App 结构体封装,支持热升级和信号处理 - 配置结构字段对齐和代码清理 - 完善错误处理和日志记录 Co-Authored-By: Claude <noreply@anthropic.com>
241 lines
6.7 KiB
Go
241 lines
6.7 KiB
Go
// Package security provides security-related middleware for the Lolly HTTP server.
|
|
//
|
|
// This file implements security headers middleware, adding standard security
|
|
// headers to responses to protect against common web vulnerabilities.
|
|
//
|
|
// Headers implemented:
|
|
// - X-Frame-Options: Prevent clickjacking
|
|
// - X-Content-Type-Options: Prevent MIME sniffing
|
|
// - Content-Security-Policy: Control resource loading (XSS protection)
|
|
// - Strict-Transport-Security: Enforce HTTPS (HSTS)
|
|
// - Referrer-Policy: Control referrer information
|
|
// - Permissions-Policy: Control browser features
|
|
//
|
|
// Example usage:
|
|
//
|
|
// cfg := &config.SecurityHeaders{
|
|
// XFrameOptions: "DENY",
|
|
// XContentTypeOptions: "nosniff",
|
|
// ContentSecurityPolicy: "default-src 'self'",
|
|
// }
|
|
//
|
|
// headers := security.NewSecurityHeaders(cfg)
|
|
// chain := middleware.NewChain(headers)
|
|
// handler := chain.Apply(finalHandler)
|
|
//
|
|
//go:generate go test -v ./...
|
|
package security
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/valyala/fasthttp"
|
|
"rua.plus/lolly/internal/config"
|
|
"rua.plus/lolly/internal/middleware"
|
|
)
|
|
|
|
// SecurityHeadersMiddleware adds security-related headers to responses.
|
|
type SecurityHeadersMiddleware struct {
|
|
config *config.SecurityHeaders
|
|
hsts string // Pre-formatted HSTS header value
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewSecurityHeaders creates a new security headers middleware.
|
|
//
|
|
// Parameters:
|
|
// - cfg: Security headers configuration (can be nil for defaults)
|
|
//
|
|
// Returns:
|
|
// - *SecurityHeadersMiddleware: Configured middleware with default safe values
|
|
func NewSecurityHeaders(cfg *config.SecurityHeaders) *SecurityHeadersMiddleware {
|
|
sh := &SecurityHeadersMiddleware{}
|
|
|
|
if cfg != nil {
|
|
sh.config = cfg
|
|
} else {
|
|
// Use secure defaults
|
|
sh.config = &config.SecurityHeaders{
|
|
XFrameOptions: "DENY",
|
|
XContentTypeOptions: "nosniff",
|
|
ReferrerPolicy: "strict-origin-when-cross-origin",
|
|
}
|
|
}
|
|
|
|
// Pre-format HSTS header if configured
|
|
sh.formatHSTS()
|
|
|
|
return sh
|
|
}
|
|
|
|
// Name returns the middleware name.
|
|
func (sh *SecurityHeadersMiddleware) Name() string {
|
|
return "security_headers"
|
|
}
|
|
|
|
// Process wraps the next handler, adding security headers to the response.
|
|
func (sh *SecurityHeadersMiddleware) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler {
|
|
return func(ctx *fasthttp.RequestCtx) {
|
|
// Call next handler first
|
|
next(ctx)
|
|
|
|
// Add security headers to response
|
|
sh.addHeaders(ctx)
|
|
}
|
|
}
|
|
|
|
// addHeaders adds all configured security headers to the response.
|
|
func (sh *SecurityHeadersMiddleware) 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 formats the HSTS header value from configuration.
|
|
func (sh *SecurityHeadersMiddleware) formatHSTS() {
|
|
// Default HSTS values
|
|
maxAge := 31536000 // 1 year
|
|
includeSubDomains := true
|
|
preload := false
|
|
|
|
// These would come from SSLConfig.HSTS in real usage
|
|
// For now, use defaults
|
|
sh.hsts = formatHSTSValue(maxAge, includeSubDomains, preload)
|
|
}
|
|
|
|
// formatHSTSValue formats HSTS header value components.
|
|
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 updates the security headers configuration.
|
|
func (sh *SecurityHeadersMiddleware) UpdateConfig(cfg *config.SecurityHeaders) {
|
|
sh.mu.Lock()
|
|
sh.config = cfg
|
|
sh.formatHSTS()
|
|
sh.mu.Unlock()
|
|
}
|
|
|
|
// SetXFrameOptions sets the X-Frame-Options header value.
|
|
func (sh *SecurityHeadersMiddleware) SetXFrameOptions(value string) {
|
|
sh.mu.Lock()
|
|
if sh.config != nil {
|
|
sh.config.XFrameOptions = value
|
|
}
|
|
sh.mu.Unlock()
|
|
}
|
|
|
|
// SetContentSecurityPolicy sets the CSP header value.
|
|
func (sh *SecurityHeadersMiddleware) SetContentSecurityPolicy(value string) {
|
|
sh.mu.Lock()
|
|
if sh.config != nil {
|
|
sh.config.ContentSecurityPolicy = value
|
|
}
|
|
sh.mu.Unlock()
|
|
}
|
|
|
|
// SetReferrerPolicy sets the Referrer-Policy header value.
|
|
func (sh *SecurityHeadersMiddleware) SetReferrerPolicy(value string) {
|
|
sh.mu.Lock()
|
|
if sh.config != nil {
|
|
sh.config.ReferrerPolicy = value
|
|
}
|
|
sh.mu.Unlock()
|
|
}
|
|
|
|
// SetPermissionsPolicy sets the Permissions-Policy header value.
|
|
func (sh *SecurityHeadersMiddleware) SetPermissionsPolicy(value string) {
|
|
sh.mu.Lock()
|
|
if sh.config != nil {
|
|
sh.config.PermissionsPolicy = value
|
|
}
|
|
sh.mu.Unlock()
|
|
}
|
|
|
|
// GetConfig returns the current configuration.
|
|
func (sh *SecurityHeadersMiddleware) GetConfig() *config.SecurityHeaders {
|
|
sh.mu.RLock()
|
|
defer sh.mu.RUnlock()
|
|
return sh.config
|
|
}
|
|
|
|
// DefaultSecurityHeaders returns a SecurityHeaders config with safe defaults.
|
|
func DefaultSecurityHeaders() *config.SecurityHeaders {
|
|
return &config.SecurityHeaders{
|
|
XFrameOptions: "DENY",
|
|
XContentTypeOptions: "nosniff",
|
|
ReferrerPolicy: "strict-origin-when-cross-origin",
|
|
}
|
|
}
|
|
|
|
// StrictSecurityHeaders returns a SecurityHeaders config with strict values.
|
|
// Suitable for high-security applications.
|
|
func StrictSecurityHeaders() *config.SecurityHeaders {
|
|
return &config.SecurityHeaders{
|
|
XFrameOptions: "DENY",
|
|
XContentTypeOptions: "nosniff",
|
|
ContentSecurityPolicy: "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'self'; frame-ancestors 'none'",
|
|
ReferrerPolicy: "no-referrer",
|
|
PermissionsPolicy: "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()",
|
|
}
|
|
}
|
|
|
|
// DevelopmentSecurityHeaders returns relaxed security headers for development.
|
|
// WARNING: Do not use in production.
|
|
func DevelopmentSecurityHeaders() *config.SecurityHeaders {
|
|
return &config.SecurityHeaders{
|
|
XFrameOptions: "SAMEORIGIN",
|
|
XContentTypeOptions: "nosniff",
|
|
ReferrerPolicy: "strict-origin-when-cross-origin",
|
|
}
|
|
}
|
|
|
|
// Verify interface compliance
|
|
var _ middleware.Middleware = (*SecurityHeadersMiddleware)(nil)
|