lolly/internal/config/validate.go
xfy 80936ae66b feat(server,proxy,ssl,docs): 完成 Phase 7 功能完善
主要变更:
- WebSocket 代理支持 (internal/proxy/websocket.go)
- OCSP stapling 实现 (internal/ssl/ocsp.go)
- 监控状态端点 (internal/server/status.go)
- 新增 nginx 模块文档 (19-24)
- UDP 代理超时配置支持
- 多模块代码注释完善和功能增强

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 13:13:12 +08:00

419 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package config 提供 YAML 配置文件的解析、验证和默认配置生成功能。
//
// 该文件包含配置验证相关的核心逻辑,包括:
// - 服务器配置验证(监听地址、静态文件、代理)
// - SSL/TLS 配置验证(证书、协议、加密套件)
// - 安全配置验证(访问控制、认证、速率限制)
// - 压缩配置验证(类型、级别、最小大小)
//
// 主要用途:
// 用于验证用户提供的配置是否符合要求,确保服务器启动前配置有效。
//
// 注意事项:
// - 验证失败时返回详细的错误信息
// - 支持默认服务器和虚拟主机两种模式的验证
//
// 作者xfy
package config
import (
"errors"
"fmt"
"net"
"strings"
"rua.plus/lolly/internal/loadbalance"
)
// validateServer 验证服务器配置。
//
// 检查服务器配置的各项参数是否符合要求,包括监听地址、
// 静态文件、代理、SSL、安全和压缩等配置。
//
// 参数:
// - s: 服务器配置对象
// - isDefault: 是否为默认服务器,默认服务器可省略部分配置
//
// 返回值:
// - error: 验证失败时返回具体错误信息,成功返回 nil
//
// 注意事项:
// - 默认服务器可省略监听地址
// - 验证错误信息包含字段路径,便于定位问题
func validateServer(s *ServerConfig, isDefault bool) error {
// 监听地址必填(默认服务器可省略,使用默认值)
if s.Listen == "" && !isDefault {
return errors.New("listen 地址必填")
}
// 验证监听地址格式
if s.Listen != "" {
if _, err := net.ResolveTCPAddr("tcp", s.Listen); err != nil {
return fmt.Errorf("无效的监听地址 %s: %w", s.Listen, err)
}
}
// 验证静态文件配置
if err := validateStatic(&s.Static); err != nil {
return fmt.Errorf("static: %w", err)
}
// 验证代理配置
for i := range s.Proxy {
if err := validateProxy(&s.Proxy[i]); err != nil {
return fmt.Errorf("proxy[%d]: %w", i, err)
}
}
// 验证 SSL 配置
if err := validateSSL(&s.SSL); err != nil {
return fmt.Errorf("ssl: %w", err)
}
// 验证安全配置
if err := validateSecurity(&s.Security); err != nil {
return fmt.Errorf("security: %w", err)
}
// 验证压缩配置
if err := validateCompression(&s.Compression); err != nil {
return fmt.Errorf("compression: %w", err)
}
return nil
}
// validateStatic 验证静态文件配置。
//
// 检查静态文件根目录路径的安全性,防止路径遍历攻击。
//
// 参数:
// - s: 静态文件配置对象
//
// 返回值:
// - error: 验证失败时返回错误信息,成功返回 nil
func validateStatic(s *StaticConfig) error {
// 静态文件根目录非空时验证路径有效性
if s.Root != "" {
// 路径安全检查:不允许包含 ".."
if strings.Contains(s.Root, "..") {
return errors.New("根目录路径不能包含 '..'")
}
}
return nil
}
// validateProxy 验证代理配置。
//
// 检查代理路径、目标地址和负载均衡算法的有效性。
//
// 参数:
// - p: 代理配置对象
//
// 返回值:
// - error: 验证失败时返回错误信息,成功返回 nil
//
// 验证规则:
// - path 必填
// - targets 至少需要一个目标
// - 目标 URL 必须以 http:// 或 https:// 开头
// - load_balance 必须是有效的负载均衡算法
func validateProxy(p *ProxyConfig) error {
// 路径必填
if p.Path == "" {
return errors.New("path 必填")
}
// 至少需要一个目标
if len(p.Targets) == 0 {
return errors.New("targets 至少需要一个目标地址")
}
// 验证每个目标地址
for i, t := range p.Targets {
if t.URL == "" {
return fmt.Errorf("targets[%d].url 必填", i)
}
if !strings.HasPrefix(t.URL, "http://") && !strings.HasPrefix(t.URL, "https://") {
return fmt.Errorf("targets[%d].url 必须以 http:// 或 https:// 开头", i)
}
}
// 验证负载均衡算法
if !loadbalance.IsValidAlgorithm(p.LoadBalance) {
return fmt.Errorf("无效的负载均衡算法:%s", p.LoadBalance)
}
return nil
}
// validateSSL 验证 SSL 配置。
//
// 检查 SSL 证书、私钥、TLS 协议版本和加密套件的有效性。
//
// 参数:
// - s: SSL 配置对象
//
// 返回值:
// - error: 验证失败时返回错误信息,成功返回 nil
//
// 验证规则:
// - cert 和 key 必须同时配置或同时为空
// - TLS 协议仅允许 TLSv1.2 和 TLSv1.3
// - 拒绝不安全的加密套件RC4、DES、3DES、CBC
func validateSSL(s *SSLConfig) error {
// 未配置 SSL 时跳过验证
if s.Cert == "" && s.Key == "" {
return nil
}
// 证书和私钥必须同时配置
if s.Cert == "" || s.Key == "" {
return errors.New("cert 和 key 必须同时配置")
}
// 验证 TLS 版本
for _, proto := range s.Protocols {
if proto == "TLSv1.0" || proto == "TLSv1.1" {
return fmt.Errorf("不安全的 TLS 版本: %s仅允许 TLSv1.2 和 TLSv1.3", proto)
}
if proto != "TLSv1.2" && proto != "TLSv1.3" {
return fmt.Errorf("未知的 TLS 版本: %s", proto)
}
}
// 验证加密套件(拒绝不安全的)
insecureCiphers := []string{"RC4", "DES", "3DES", "CBC"}
for _, cipher := range s.Ciphers {
for _, insecure := range insecureCiphers {
if strings.Contains(cipher, insecure) {
return fmt.Errorf("不安全的加密套件: %s", cipher)
}
}
}
return nil
}
// validateSecurity 验证安全配置。
//
// 验证访问控制、认证和速率限制配置的有效性。
//
// 参数:
// - s: 安全配置对象
//
// 返回值:
// - error: 验证失败时返回错误信息,成功返回 nil
func validateSecurity(s *SecurityConfig) error {
// 验证访问控制配置
if err := validateAccess(&s.Access); err != nil {
return fmt.Errorf("access: %w", err)
}
// 验证认证配置
if err := validateAuth(&s.Auth); err != nil {
return fmt.Errorf("auth: %w", err)
}
// 验证速率限制配置
if err := validateRateLimit(&s.RateLimit); err != nil {
return fmt.Errorf("rate_limit: %w", err)
}
return nil
}
// validateAccess 验证访问控制配置。
//
// 检查允许和拒绝列表中的 CIDR/IP 格式,以及默认动作的有效性。
//
// 参数:
// - a: 访问控制配置对象
//
// 返回值:
// - error: 验证失败时返回错误信息,成功返回 nil
//
// 验证规则:
// - allow 和 deny 列表中的项必须是有效的 CIDR 或 IP 地址
// - default 动作仅允许 "allow" 或 "deny"
func validateAccess(a *AccessConfig) error {
// 验证 CIDR 格式
for _, cidr := range a.Allow {
if _, _, err := net.ParseCIDR(cidr); err != nil {
// 尝试作为单个 IP 解析
if ip := net.ParseIP(cidr); ip == nil {
return fmt.Errorf("无效的 allow CIDR/IP: %s", cidr)
}
}
}
for _, cidr := range a.Deny {
if _, _, err := net.ParseCIDR(cidr); err != nil {
if ip := net.ParseIP(cidr); ip == nil {
return fmt.Errorf("无效的 deny CIDR/IP: %s", cidr)
}
}
}
// 验证默认动作
if a.Default != "" && a.Default != "allow" && a.Default != "deny" {
return fmt.Errorf("无效的 default 动作: %s仅允许 allow 或 deny", a.Default)
}
return nil
}
// validateAuth 验证认证配置。
//
// 检查认证类型、哈希算法和用户列表的有效性。
//
// 参数:
// - a: 认证配置对象
//
// 返回值:
// - error: 验证失败时返回错误信息,成功返回 nil
//
// 验证规则:
// - type 目前仅支持 "basic"
// - algorithm 仅支持 bcrypt 或 argon2id
// - 启用认证时至少需要一个用户
func validateAuth(a *AuthConfig) error {
// 未配置认证时跳过
if a.Type == "" {
return nil
}
// 仅支持 basic 认证
if a.Type != "basic" {
return fmt.Errorf("不支持的认证类型: %s仅支持 basic", a.Type)
}
// 启用 Basic Auth 时检查是否强制 HTTPS
if a.RequireTLS {
// 注意SSL 配置在 ServerConfig 中,这里无法直接检查
// 需要在上层验证中检查 SSL 与 Auth 的关联
}
// 验证哈希算法
validAlgorithms := []string{"", "bcrypt", "argon2id"}
valid := false
for _, alg := range validAlgorithms {
if a.Algorithm == alg {
valid = true
break
}
}
if !valid {
return fmt.Errorf("不支持的哈希算法: %s仅支持 bcrypt 或 argon2id", a.Algorithm)
}
// 至少需要一个用户
if len(a.Users) == 0 {
return errors.New("启用认证时至少需要一个用户")
}
// 验证每个用户
for i, u := range a.Users {
if u.Name == "" {
return fmt.Errorf("users[%d].name 必填", i)
}
if u.Password == "" {
return fmt.Errorf("users[%d].password 必填", i)
}
}
return nil
}
// validateRateLimit 验证速率限制配置。
//
// 检查请求速率、突发容量和连接限制的有效性。
//
// 参数:
// - r: 速率限制配置对象
//
// 返回值:
// - error: 验证失败时返回错误信息,成功返回 nil
//
// 验证规则:
// - request_rate、burst、conn_limit 不能为负数
// - key 仅支持 "ip" 或 "header"
func validateRateLimit(r *RateLimitConfig) error {
// 未配置时跳过
if r.RequestRate == 0 && r.ConnLimit == 0 {
return nil
}
// 验证速率限制值
if r.RequestRate < 0 {
return errors.New("request_rate 不能为负数")
}
if r.Burst < 0 {
return errors.New("burst 不能为负数")
}
if r.ConnLimit < 0 {
return errors.New("conn_limit 不能为负数")
}
// 验证 key 来源
validKeys := []string{"", "ip", "header"}
valid := false
for _, k := range validKeys {
if r.Key == k {
valid = true
break
}
}
if !valid {
return fmt.Errorf("无效的 key 来源: %s仅支持 ip 或 header", r.Key)
}
return nil
}
// validateCompression 验证压缩配置。
//
// 检查压缩类型、压缩级别和最小压缩大小的有效性。
//
// 参数:
// - c: 压缩配置对象
//
// 返回值:
// - error: 验证失败时返回错误信息,成功返回 nil
//
// 验证规则:
// - type 仅支持 gzip、brotli 或 both
// - level 范围为 0-9
// - min_size 不能为负数
func validateCompression(c *CompressionConfig) error {
// 未配置时跳过
if c.Type == "" {
return nil
}
// 验证压缩类型
validTypes := []string{"gzip", "brotli", "both"}
valid := false
for _, t := range validTypes {
if c.Type == t {
valid = true
break
}
}
if !valid {
return fmt.Errorf("无效的压缩类型: %s仅支持 gzip, brotli 或 both", c.Type)
}
// 验证压缩级别
if c.Level < 0 || c.Level > 9 {
return fmt.Errorf("无效的压缩级别: %d范围 0-9", c.Level)
}
// 验证最小压缩大小
if c.MinSize < 0 {
return errors.New("min_size 不能为负数")
}
return nil
}