From 83b2a35715e60582475576d1860e6c6f14cf7d14 Mon Sep 17 00:00:00 2001 From: xfy Date: Thu, 9 Apr 2026 14:44:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(config):=20=E5=AE=8C=E5=96=84=20Resolver/S?= =?UTF-8?q?SL=20=E9=BB=98=E8=AE=A4=E5=80=BC=E5=8F=8A=20YAML=20=E8=BE=93?= =?UTF-8?q?=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 Resolver/SessionTickets/ClientVerify 默认配置 - GenerateConfigYAML 输出改为非注释格式 - 新增 Resolver、SSL 默认值、YAML 可加载性测试 Co-Authored-By: Claude Opus 4.6 --- internal/config/defaults.go | 70 +++++++++++++-------- internal/config/defaults_test.go | 101 +++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 25 deletions(-) diff --git a/internal/config/defaults.go b/internal/config/defaults.go index 0206139..e6e03b1 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -66,6 +66,19 @@ func DefaultConfig() *Config { PushEnabled: false, H2CEnabled: false, }, + SessionTickets: SessionTicketsConfig{ + Enabled: false, + KeyFile: "", + RotateInterval: 1 * time.Hour, + RetainKeys: 3, + }, + ClientVerify: ClientVerifyConfig{ + Enabled: false, + Mode: "none", + ClientCA: "", + VerifyDepth: 1, + CRL: "", + }, }, Security: SecurityConfig{ Access: AccessConfig{ @@ -157,6 +170,15 @@ func DefaultConfig() *Config { IdleTimeout: 60 * time.Second, Enable0RTT: false, }, + Resolver: ResolverConfig{ + Enabled: false, + Addresses: []string{"8.8.8.8:53", "8.8.4.4:53"}, + Valid: 30 * time.Second, + Timeout: 5 * time.Second, + IPv4: true, + IPv6: false, + CacheSize: 1024, + }, Variables: VariablesConfig{ Set: map[string]string{}, }, @@ -328,8 +350,8 @@ func GenerateConfigYAML(cfg *Config) ([]byte, error) { fmt.Fprintf(&buf, " x_frame_options: \"%s\" # 防止点击劫持(有效值: DENY, SAMEORIGIN, 空表示禁用)\n", cfg.Server.Security.Headers.XFrameOptions) fmt.Fprintf(&buf, " x_content_type_options: \"%s\" # 防止 MIME 嗅探(有效值:nosniff,空表示禁用)\n", cfg.Server.Security.Headers.XContentTypeOptions) fmt.Fprintf(&buf, " referrer_policy: \"%s\" # 引用策略(有效值: no-referrer, no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url)\n", cfg.Server.Security.Headers.ReferrerPolicy) - buf.WriteString(" # content_security_policy: \"default-src 'self'\" # 内容安全策略 CSP\n") - buf.WriteString(" # permissions_policy: \"geolocation=(), microphone=()\" # 权限策略\n") + buf.WriteString(" content_security_policy: \"\" # 内容安全策略 CSP(空表示禁用)\n") + buf.WriteString(" permissions_policy: \"\" # 权限策略(空表示禁用)\n") buf.WriteString("\n") buf.WriteString(" # 自定义错误页面\n") buf.WriteString(" error_page:\n") @@ -338,13 +360,13 @@ func GenerateConfigYAML(cfg *Config) ([]byte, error) { buf.WriteString(" response_code: 0 # 响应状态码覆盖(0 表示使用原始状态码)\n") buf.WriteString("\n") buf.WriteString(" # 外部认证子请求配置(将认证委托给外部服务)\n") - buf.WriteString(" # auth_request:\n") - buf.WriteString(" # enabled: false # 是否启用外部认证\n") - buf.WriteString(" # uri: \"/auth\" # 认证服务地址(支持相对路径或完整 URL)\n") - buf.WriteString(" # method: \"GET\" # 认证请求方法(有效值: GET, POST, HEAD)\n") - buf.WriteString(" # auth_timeout: 5s # 认证请求超时时间\n") - buf.WriteString(" # headers: {} # 自定义认证请求头,如 {X-Original-Uri: \"$request_uri\"}\n") - buf.WriteString(" # forward_headers: [] # 需要转发的原请求头,默认包含 Cookie, Authorization, X-Forwarded-For\n") + buf.WriteString(" auth_request:\n") + buf.WriteString(" enabled: false # 是否启用外部认证\n") + buf.WriteString(" uri: \"\" # 认证服务地址(支持相对路径或完整 URL)\n") + buf.WriteString(" method: \"GET\" # 认证请求方法(有效值: GET, POST, HEAD)\n") + buf.WriteString(" auth_timeout: 5s # 认证请求超时时间\n") + buf.WriteString(" headers: {} # 自定义认证请求头,如 {X-Original-Uri: \"$request_uri\"}\n") + buf.WriteString(" forward_headers: [] # 需要转发的原请求头,默认包含 Cookie, Authorization, X-Forwarded-For\n") buf.WriteString("\n") // rewrite 配置示例 @@ -520,26 +542,24 @@ func GenerateConfigYAML(cfg *Config) ([]byte, error) { // resolver 配置 buf.WriteString("# DNS 解析器配置(用于后端域名动态解析)\n") - buf.WriteString("# resolver:\n") - fmt.Fprintf(&buf, "# enabled: %v # 是否启用 DNS 解析器\n", cfg.Resolver.Enabled) - buf.WriteString("# addresses: # DNS 服务器地址列表\n") - buf.WriteString("# - \"8.8.8.8:53\"\n") - buf.WriteString("# - \"8.8.4.4:53\"\n") - fmt.Fprintf(&buf, "# valid: %ds # 缓存有效期(TTL),建议 30s-300s\n", int(cfg.Resolver.Valid.Seconds())) - fmt.Fprintf(&buf, "# timeout: %ds # DNS 查询超时\n", int(cfg.Resolver.Timeout.Seconds())) - fmt.Fprintf(&buf, "# ipv4: %v # 是否查询 IPv4 地址\n", cfg.Resolver.IPv4) - fmt.Fprintf(&buf, "# ipv6: %v # 是否查询 IPv6 地址\n", cfg.Resolver.IPv6) - fmt.Fprintf(&buf, "# cache_size: %d # 缓存最大条目数(0 表示不限制)\n", cfg.Resolver.CacheSize) + buf.WriteString("resolver:\n") + fmt.Fprintf(&buf, " enabled: %v # 是否启用 DNS 解析器\n", cfg.Resolver.Enabled) + buf.WriteString(" addresses: # DNS 服务器地址列表\n") + buf.WriteString(" - \"8.8.8.8:53\"\n") + buf.WriteString(" - \"8.8.4.4:53\"\n") + fmt.Fprintf(&buf, " valid: %ds # 缓存有效期(TTL),建议 30s-300s\n", int(cfg.Resolver.Valid.Seconds())) + fmt.Fprintf(&buf, " timeout: %ds # DNS 查询超时\n", int(cfg.Resolver.Timeout.Seconds())) + fmt.Fprintf(&buf, " ipv4: %v # 是否查询 IPv4 地址\n", cfg.Resolver.IPv4) + fmt.Fprintf(&buf, " ipv6: %v # 是否查询 IPv6 地址\n", cfg.Resolver.IPv6) + fmt.Fprintf(&buf, " cache_size: %d # 缓存最大条目数(0 表示不限制)\n", cfg.Resolver.CacheSize) buf.WriteString("\n") // variables 配置 buf.WriteString("# 自定义变量配置(全局变量,应用于所有虚拟主机)\n") - buf.WriteString("# variables:\n") - buf.WriteString("# set: # 自定义变量集合\n") - buf.WriteString("# app_name: \"lolly\" # 可在日志格式中通过 $var_app_name 引用\n") - buf.WriteString("# version: \"1.0.0\"\n") - buf.WriteString("# 注意:变量名只允许字母、数字、下划线,不能与内置变量冲突\n") - buf.WriteString("# 不能以 arg_、http_、cookie_ 开头(这些是动态变量前缀)\n") + buf.WriteString("variables:\n") + buf.WriteString(" set: {} # 自定义变量集合,如 {app_name: \"lolly\"}\n") + buf.WriteString(" # 注意:变量名只允许字母、数字、下划线,不能与内置变量冲突\n") + buf.WriteString(" # 不能以 arg_、http_、cookie_ 开头(这些是动态变量前缀)\n") return buf.Bytes(), nil } diff --git a/internal/config/defaults_test.go b/internal/config/defaults_test.go index 504bc15..20b4cf0 100644 --- a/internal/config/defaults_test.go +++ b/internal/config/defaults_test.go @@ -131,3 +131,104 @@ func TestDefaultConfigPerformance(t *testing.T) { t.Errorf("Transport.MaxConnsPerHost 期望 0 (不限制), 实际 %d", cfg.Performance.Transport.MaxConnsPerHost) } } + +func TestDefaultConfigResolver(t *testing.T) { + // TestDefaultConfigResolver 测试 Resolver 默认值。 + cfg := DefaultConfig() + + // 验证 Resolver 默认值 + if cfg.Resolver.Enabled { + t.Error("Resolver.Enabled 期望 false") + } + if len(cfg.Resolver.Addresses) != 2 { + t.Errorf("Resolver.Addresses 期望 2 个 DNS 服务器,实际 %d", len(cfg.Resolver.Addresses)) + } + if cfg.Resolver.Valid != 30*time.Second { + t.Errorf("Resolver.Valid 期望 30s,实际 %v", cfg.Resolver.Valid) + } + if cfg.Resolver.Timeout != 5*time.Second { + t.Errorf("Resolver.Timeout 期望 5s,实际 %v", cfg.Resolver.Timeout) + } + if !cfg.Resolver.IPv4 { + t.Error("Resolver.IPv4 期望 true") + } + if cfg.Resolver.IPv6 { + t.Error("Resolver.IPv6 期望 false") + } + if cfg.Resolver.CacheSize != 1024 { + t.Errorf("Resolver.CacheSize 期望 1024,实际 %d", cfg.Resolver.CacheSize) + } +} + +func TestDefaultConfigSSLDefaults(t *testing.T) { + // TestDefaultConfigSSLDefaults 测试 SSL 相关默认值。 + cfg := DefaultConfig() + + // 验证 SessionTickets 默认值 + if cfg.Server.SSL.SessionTickets.Enabled { + t.Error("SessionTickets.Enabled 期望 false") + } + if cfg.Server.SSL.SessionTickets.RetainKeys != 3 { + t.Errorf("SessionTickets.RetainKeys 期望 3,实际 %d", cfg.Server.SSL.SessionTickets.RetainKeys) + } + + // 验证 ClientVerify 默认值 + if cfg.Server.SSL.ClientVerify.Enabled { + t.Error("ClientVerify.Enabled 期望 false") + } + if cfg.Server.SSL.ClientVerify.Mode != "none" { + t.Errorf("ClientVerify.Mode 期望 none,实际 %s", cfg.Server.SSL.ClientVerify.Mode) + } + if cfg.Server.SSL.ClientVerify.VerifyDepth != 1 { + t.Errorf("ClientVerify.VerifyDepth 期望 1,实际 %d", cfg.Server.SSL.ClientVerify.VerifyDepth) + } +} + +func TestGenerateConfigYAMLContainsAllSections(t *testing.T) { + // TestGenerateConfigYAMLContainsAllSections 测试 YAML 包含所有配置块。 + cfg := DefaultConfig() + yamlData, err := GenerateConfigYAML(cfg) + if err != nil { + t.Fatalf("GenerateConfigYAML 失败: %v", err) + } + yamlStr := string(yamlData) + + // 验证包含非注释的配置块 + requiredSections := []string{ + "resolver:", + "variables:", + "content_security_policy:", + "permissions_policy:", + "auth_request:", + } + + for _, section := range requiredSections { + // 检查配置块存在 + if !strings.Contains(yamlStr, section) { + t.Errorf("配置块 %s 缺失", section) + } + } +} + +func TestGenerateConfigYAMLLoadable(t *testing.T) { + // TestGenerateConfigYAMLLoadable 测试生成的 YAML 可以被加载。 + cfg := DefaultConfig() + yamlData, err := GenerateConfigYAML(cfg) + if err != nil { + t.Fatalf("GenerateConfigYAML 失败: %v", err) + } + + // 尝试加载生成的 YAML + loadedCfg, err := LoadFromString(string(yamlData)) + if err != nil { + t.Fatalf("生成的 YAML 无法加载: %v", err) + } + + // 验证关键字段匹配 + if loadedCfg.Server.Listen != cfg.Server.Listen { + t.Errorf("Server.Listen 不匹配: 期望 %s, 实际 %s", cfg.Server.Listen, loadedCfg.Server.Listen) + } + if loadedCfg.Resolver.Enabled != cfg.Resolver.Enabled { + t.Errorf("Resolver.Enabled 不匹配") + } +}