- config: 为 Config 和所有子配置结构添加完整文档,包含使用示例和注意事项 - stream: 为负载均衡器和服务器添加详细的参数、返回值和功能说明 - logging: 为日志格式化和输出函数添加文档,说明支持的变量替换 - handler: 为路由器、静态文件和 sendfile 处理器添加文档 - proxy: 为健康检查器和代理功能添加完整文档 - cache/server/ssl/middleware: 补充相关模块的文档注释 - config.example.yaml: 添加可信代理配置、加密套件示例,更新压缩级别说明 Co-Authored-By: Claude <noreply@anthropic.com>
258 lines
6.7 KiB
Go
258 lines
6.7 KiB
Go
// Package security 提供安全头部功能的测试。
|
||
//
|
||
// 该文件测试安全头部模块的各项功能,包括:
|
||
// - 安全头部中间件创建
|
||
// - 各种安全头部设置(CSP、HSTS、X-Frame-Options等)
|
||
// - 配置更新
|
||
// - 默认和严格配置
|
||
// - HSTS 值格式化
|
||
//
|
||
// 作者:xfy
|
||
package security
|
||
|
||
import (
|
||
"testing"
|
||
|
||
"github.com/valyala/fasthttp"
|
||
"rua.plus/lolly/internal/config"
|
||
)
|
||
|
||
func TestNewSecurityHeaders(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
cfg *config.SecurityHeaders
|
||
}{
|
||
{
|
||
name: "nil config uses defaults",
|
||
cfg: nil,
|
||
},
|
||
{
|
||
name: "custom config",
|
||
cfg: &config.SecurityHeaders{
|
||
XFrameOptions: "SAMEORIGIN",
|
||
XContentTypeOptions: "nosniff",
|
||
ContentSecurityPolicy: "default-src 'self'",
|
||
},
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
sh := NewSecurityHeaders(tt.cfg)
|
||
if sh == nil {
|
||
t.Error("Expected non-nil SecurityHeadersMiddleware")
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestSecurityHeadersName(t *testing.T) {
|
||
sh := NewSecurityHeaders(nil)
|
||
if sh.Name() != "security_headers" {
|
||
t.Errorf("Expected name 'security_headers', got %s", sh.Name())
|
||
}
|
||
}
|
||
|
||
func TestSecurityHeadersProcess(t *testing.T) {
|
||
cfg := &config.SecurityHeaders{
|
||
XFrameOptions: "DENY",
|
||
XContentTypeOptions: "nosniff",
|
||
ContentSecurityPolicy: "default-src 'self'",
|
||
ReferrerPolicy: "strict-origin-when-cross-origin",
|
||
PermissionsPolicy: "geolocation=()",
|
||
}
|
||
|
||
sh := NewSecurityHeaders(cfg)
|
||
|
||
handlerCalled := false
|
||
nextHandler := func(ctx *fasthttp.RequestCtx) {
|
||
handlerCalled = true
|
||
_, _ = ctx.WriteString("OK")
|
||
}
|
||
|
||
handler := sh.Process(nextHandler)
|
||
if handler == nil {
|
||
t.Fatal("Process() returned nil handler")
|
||
}
|
||
|
||
// Create request context and call handler
|
||
ctx := &fasthttp.RequestCtx{}
|
||
handler(ctx)
|
||
|
||
// Check headers were set
|
||
headers := &ctx.Response.Header
|
||
|
||
if string(headers.Peek("X-Frame-Options")) != "DENY" {
|
||
t.Errorf("X-Frame-Options not set correctly, got %s", headers.Peek("X-Frame-Options"))
|
||
}
|
||
|
||
if string(headers.Peek("X-Content-Type-Options")) != "nosniff" {
|
||
t.Errorf("X-Content-Type-Options not set correctly, got %s", headers.Peek("X-Content-Type-Options"))
|
||
}
|
||
|
||
if string(headers.Peek("Content-Security-Policy")) != "default-src 'self'" {
|
||
t.Errorf("Content-Security-Policy not set correctly, got %s", headers.Peek("Content-Security-Policy"))
|
||
}
|
||
|
||
if string(headers.Peek("Referrer-Policy")) != "strict-origin-when-cross-origin" {
|
||
t.Errorf("Referrer-Policy not set correctly, got %s", headers.Peek("Referrer-Policy"))
|
||
}
|
||
|
||
if string(headers.Peek("Permissions-Policy")) != "geolocation=()" {
|
||
t.Errorf("Permissions-Policy not set correctly, got %s", headers.Peek("Permissions-Policy"))
|
||
}
|
||
|
||
if !handlerCalled {
|
||
t.Error("Next handler was not called")
|
||
}
|
||
}
|
||
|
||
func TestSecurityHeadersHSTS(t *testing.T) {
|
||
cfg := &config.SecurityHeaders{
|
||
XFrameOptions: "DENY",
|
||
}
|
||
|
||
sh := NewSecurityHeaders(cfg)
|
||
|
||
nextHandler := func(ctx *fasthttp.RequestCtx) {
|
||
_, _ = ctx.WriteString("OK")
|
||
}
|
||
|
||
handler := sh.Process(nextHandler)
|
||
|
||
// Simulate TLS connection
|
||
ctx := &fasthttp.RequestCtx{}
|
||
ctx.Request.SetRequestURI("https://example.com/")
|
||
// Note: In actual testing, ctx.IsTLS() requires connection setup
|
||
|
||
handler(ctx)
|
||
|
||
// HSTS header would be set when TLS is active
|
||
// In this test we verify the handler doesn't panic
|
||
}
|
||
|
||
func TestSecurityHeadersUpdate(t *testing.T) {
|
||
sh := NewSecurityHeaders(nil)
|
||
|
||
// Update X-Frame-Options
|
||
sh.SetXFrameOptions("SAMEORIGIN")
|
||
cfg := sh.GetConfig()
|
||
if cfg.XFrameOptions != "SAMEORIGIN" {
|
||
t.Errorf("Expected X-Frame-Options 'SAMEORIGIN', got %s", cfg.XFrameOptions)
|
||
}
|
||
|
||
// Update CSP
|
||
sh.SetContentSecurityPolicy("default-src 'unsafe-inline'")
|
||
cfg = sh.GetConfig()
|
||
if cfg.ContentSecurityPolicy != "default-src 'unsafe-inline'" {
|
||
t.Errorf("Expected CSP update, got %s", cfg.ContentSecurityPolicy)
|
||
}
|
||
|
||
// Update Referrer-Policy
|
||
sh.SetReferrerPolicy("no-referrer")
|
||
cfg = sh.GetConfig()
|
||
if cfg.ReferrerPolicy != "no-referrer" {
|
||
t.Errorf("Expected Referrer-Policy 'no-referrer', got %s", cfg.ReferrerPolicy)
|
||
}
|
||
|
||
// Update Permissions-Policy
|
||
sh.SetPermissionsPolicy("camera=()")
|
||
cfg = sh.GetConfig()
|
||
if cfg.PermissionsPolicy != "camera=()" {
|
||
t.Errorf("Expected Permissions-Policy 'camera=()', got %s", cfg.PermissionsPolicy)
|
||
}
|
||
}
|
||
|
||
func TestUpdateConfig(t *testing.T) {
|
||
sh := NewSecurityHeaders(nil)
|
||
|
||
newCfg := &config.SecurityHeaders{
|
||
XFrameOptions: "DENY",
|
||
ReferrerPolicy: "no-referrer",
|
||
}
|
||
|
||
sh.UpdateConfig(newCfg)
|
||
|
||
cfg := sh.GetConfig()
|
||
if cfg.XFrameOptions != "DENY" {
|
||
t.Errorf("Expected X-Frame-Options 'DENY', got %s", cfg.XFrameOptions)
|
||
}
|
||
if cfg.ReferrerPolicy != "no-referrer" {
|
||
t.Errorf("Expected Referrer-Policy 'no-referrer', got %s", cfg.ReferrerPolicy)
|
||
}
|
||
}
|
||
|
||
func TestDefaultSecurityHeaders(t *testing.T) {
|
||
cfg := DefaultSecurityHeaders()
|
||
|
||
if cfg.XFrameOptions != "DENY" {
|
||
t.Errorf("Expected default X-Frame-Options 'DENY', got %s", cfg.XFrameOptions)
|
||
}
|
||
if cfg.XContentTypeOptions != "nosniff" {
|
||
t.Errorf("Expected default X-Content-Type-Options 'nosniff', got %s", cfg.XContentTypeOptions)
|
||
}
|
||
}
|
||
|
||
func TestStrictSecurityHeaders(t *testing.T) {
|
||
cfg := StrictSecurityHeaders()
|
||
|
||
if cfg.XFrameOptions != "DENY" {
|
||
t.Errorf("Expected X-Frame-Options 'DENY', got %s", cfg.XFrameOptions)
|
||
}
|
||
if cfg.ReferrerPolicy != "no-referrer" {
|
||
t.Errorf("Expected Referrer-Policy 'no-referrer', got %s", cfg.ReferrerPolicy)
|
||
}
|
||
if cfg.ContentSecurityPolicy == "" {
|
||
t.Error("Expected non-empty CSP for strict config")
|
||
}
|
||
}
|
||
|
||
func TestDevelopmentSecurityHeaders(t *testing.T) {
|
||
cfg := DevelopmentSecurityHeaders()
|
||
|
||
if cfg.XFrameOptions != "SAMEORIGIN" {
|
||
t.Errorf("Expected X-Frame-Options 'SAMEORIGIN' for dev, got %s", cfg.XFrameOptions)
|
||
}
|
||
}
|
||
|
||
func TestFormatHSTSValue(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
maxAge int
|
||
includeSubDomains bool
|
||
preload bool
|
||
expected string
|
||
}{
|
||
{
|
||
name: "basic HSTS",
|
||
maxAge: 31536000,
|
||
includeSubDomains: true,
|
||
preload: false,
|
||
expected: "max-age=31536000; includeSubDomains",
|
||
},
|
||
{
|
||
name: "HSTS with preload",
|
||
maxAge: 31536000,
|
||
includeSubDomains: true,
|
||
preload: true,
|
||
expected: "max-age=31536000; includeSubDomains; preload",
|
||
},
|
||
{
|
||
name: "HSTS without subdomains",
|
||
maxAge: 86400,
|
||
includeSubDomains: false,
|
||
preload: false,
|
||
expected: "max-age=86400",
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
result := formatHSTSValue(tt.maxAge, tt.includeSubDomains, tt.preload)
|
||
if result != tt.expected {
|
||
t.Errorf("formatHSTSValue() = %s, expected %s", result, tt.expected)
|
||
}
|
||
})
|
||
}
|
||
}
|