From 179090fa346564a1ea305a574c91c0e7f7d9f57a Mon Sep 17 00:00:00 2001 From: xfy Date: Tue, 28 Apr 2026 10:13:47 +0800 Subject: [PATCH] =?UTF-8?q?fix(security):=20=E4=BF=AE=E5=A4=8D=202=20?= =?UTF-8?q?=E4=B8=AA=20CRITICAL=20+=206=20=E4=B8=AA=20HIGH=20=E5=AE=89?= =?UTF-8?q?=E5=85=A8=E4=B8=8E=E4=BB=A3=E7=A0=81=E8=B4=A8=E9=87=8F=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 安全修复: - ConnLimiter Acquire() TOCTOU 竞态: atomic.AddInt64 替代 loadInt64+addInt64 - Cache Purge token 时序侧信道: 改用 subtle.ConstantTimeCompare - Lua Cosocket SSRF: 新增 ip_guard 两层 IP 检查(字面量+解析后),拒绝私有/回环地址 - X-Accel-Redirect 路径遍历: urlpath.Clean + 前缀拒绝(/internal/ /admin/) - CRLF 注入: containsCRLF 校验变量展开后的 header 值,logging.Warn 可观测 - Proxy URI 注入: bytes.ContainsAny 检查 path 中的 @\r\n 危险字符 代码质量: - disk_cache.go Set() 7 处静默 return 改为 logging.Error 日志记录 - config.go 从 2392 行拆分为 9 个按域文件(config/server/proxy/security/ssl/cache/performance/monitoring/variable) 验证: go build + vet + golangci-lint(0 issues) + test(83.2% 无回归) + race detector 全部通过 Co-Authored-By: Claude Opus 4.7 --- internal/cache/disk_cache.go | 8 + internal/cache/purge.go | 5 +- internal/config/cache_config.go | 152 ++ internal/config/config.go | 2038 +-------------------- internal/config/monitoring_config.go | 190 ++ internal/config/performance_config.go | 258 +++ internal/config/proxy_config.go | 438 +++++ internal/config/security_config.go | 346 ++++ internal/config/server_config.go | 344 ++++ internal/config/ssl_config.go | 123 ++ internal/config/variable_config.go | 211 +++ internal/lua/api_socket_tcp.go | 26 +- internal/lua/ip_guard.go | 11 + internal/lua/ip_guard_test.go | 51 + internal/lua/socket_manager.go | 22 +- internal/lua/socket_test.go | 6 + internal/middleware/security/ratelimit.go | 13 +- internal/proxy/proxy.go | 25 +- internal/proxy/validate.go | 10 + internal/proxy/validate_test.go | 28 + 20 files changed, 2248 insertions(+), 2057 deletions(-) create mode 100644 internal/config/cache_config.go create mode 100644 internal/config/monitoring_config.go create mode 100644 internal/config/performance_config.go create mode 100644 internal/config/proxy_config.go create mode 100644 internal/config/security_config.go create mode 100644 internal/config/server_config.go create mode 100644 internal/config/ssl_config.go create mode 100644 internal/config/variable_config.go create mode 100644 internal/lua/ip_guard.go create mode 100644 internal/lua/ip_guard_test.go create mode 100644 internal/proxy/validate.go create mode 100644 internal/proxy/validate_test.go diff --git a/internal/cache/disk_cache.go b/internal/cache/disk_cache.go index 14e2636..54bdc9f 100644 --- a/internal/cache/disk_cache.go +++ b/internal/cache/disk_cache.go @@ -22,6 +22,8 @@ import ( "sync" "sync/atomic" "time" + + "rua.plus/lolly/internal/logging" ) // DiskCacheConfig 磁盘缓存配置。 @@ -346,6 +348,7 @@ func (dc *DiskCache) Set(hashKey uint64, origKey string, data []byte, headers ma // 确保目录存在 dir := filepath.Dir(dataPath) if err := os.MkdirAll(dir, 0o755); err != nil { + logging.Error().Err(err).Str("dir", dir).Msg("disk cache mkdir failed") return } @@ -367,24 +370,29 @@ func (dc *DiskCache) Set(hashKey uint64, origKey string, data []byte, headers ma // 原子写入数据文件:先写临时文件,再重命名 tmpDataPath := dataPath + ".tmp" if err := os.WriteFile(tmpDataPath, data, 0o644); err != nil { + logging.Error().Err(err).Str("path", tmpDataPath).Msg("disk cache write failed") return } if err := os.Rename(tmpDataPath, dataPath); err != nil { _ = os.Remove(tmpDataPath) + logging.Error().Err(err).Str("from", tmpDataPath).Str("to", dataPath).Msg("disk cache rename failed") return } // 写入元数据文件 metaData, err := json.Marshal(meta) if err != nil { + logging.Error().Err(err).Msg("disk cache json marshal failed") return } tmpMetaPath := metaPath + ".tmp" if err := os.WriteFile(tmpMetaPath, metaData, 0o644); err != nil { + logging.Error().Err(err).Str("path", tmpMetaPath).Msg("disk cache write meta failed") return } if err := os.Rename(tmpMetaPath, metaPath); err != nil { _ = os.Remove(tmpMetaPath) + logging.Error().Err(err).Str("from", tmpMetaPath).Str("to", metaPath).Msg("disk cache rename meta failed") return } diff --git a/internal/cache/purge.go b/internal/cache/purge.go index b9ff496..5256e70 100644 --- a/internal/cache/purge.go +++ b/internal/cache/purge.go @@ -12,6 +12,7 @@ package cache import ( + "crypto/subtle" "encoding/json" "hash/fnv" "net" @@ -196,11 +197,11 @@ func (p *PurgeAPI) checkAuth(ctx *fasthttp.RequestCtx) bool { // 支持 Bearer token 格式 authStr := string(authHeader) if token, ok := strings.CutPrefix(authStr, "Bearer "); ok { - return token == p.auth.Token + return subtle.ConstantTimeCompare([]byte(token), []byte(p.auth.Token)) == 1 } // 也支持直接传递 token - return authStr == p.auth.Token + return subtle.ConstantTimeCompare([]byte(authStr), []byte(p.auth.Token)) == 1 } return false diff --git a/internal/config/cache_config.go b/internal/config/cache_config.go new file mode 100644 index 0000000..a5d5f5f --- /dev/null +++ b/internal/config/cache_config.go @@ -0,0 +1,152 @@ +package config + +import "time" + +// ProxyCachePathConfig 缓存路径配置(磁盘持久化)。 +// +// 配置磁盘缓存路径和相关参数,支持 L1/L2 分层缓存架构。 +// 配置后,代理缓存将持久化到磁盘,服务重启后可恢复。 +// +// 注意事项: +// - Path 为必填项,指定缓存根目录 +// - Levels 支持最多 3 级目录(如 "1:2:2") +// - MaxSize 为 0 表示不限制大小 +// - L1MaxEntries/L1MaxSize 为 0 时使用默认值 +// +// 使用示例: +// +// cache_path: +// path: "/var/cache/lolly" +// levels: "1:2" +// max_size: "1GB" +// inactive: "60m" +// l1_max_entries: 10000 +type ProxyCachePathConfig struct { + // Path 缓存根目录 + Path string `yaml:"path"` + + // Levels 目录层级,如 "1:2" 表示两级目录 + Levels string `yaml:"levels"` + + // MaxSize 最大缓存大小(字节) + MaxSize int64 `yaml:"max_size"` + + // Inactive 未访问淘汰时间 + Inactive time.Duration `yaml:"inactive"` + + // Purger 是否启用后台清理 + Purger bool `yaml:"purger"` + + // PurgerInterval 清理间隔 + PurgerInterval time.Duration `yaml:"purger_interval"` + + // L1MaxEntries L1 最大条目数 + L1MaxEntries int64 `yaml:"l1_max_entries"` + + // L1MaxSize L1 最大内存大小 + L1MaxSize int64 `yaml:"l1_max_size"` + + // PromoteThreshold 提升到 L1 的访问阈值 + PromoteThreshold int `yaml:"promote_threshold"` +} + +// ProxyCacheConfig 代理缓存配置。 +// +// 缓存后端响应,减少重复请求,提高响应速度。 +// +// 注意事项: +// - 仅缓存 GET 和 HEAD 请求 +// - 后端响应中 Cache-Control 头会覆盖 MaxAge 设置 +// - CacheLock 可防止缓存击穿,但会增加首次请求延迟 +// - 谨慎缓存动态内容,避免返回过期数据 +// +// 使用示例: +// +// cache: +// enabled: true +// max_age: 5m +// cache_lock: true +// stale_while_revalidate: 1m +type ProxyCacheConfig struct { + MaxAge time.Duration `yaml:"max_age"` + StaleWhileRevalidate time.Duration `yaml:"stale_while_revalidate"` + StaleIfError time.Duration `yaml:"stale_if_error"` // 错误时使用过期缓存 + StaleIfTimeout time.Duration `yaml:"stale_if_timeout"` // 超时时使用过期缓存 + Enabled bool `yaml:"enabled"` + CacheLock bool `yaml:"cache_lock"` + Methods []string `yaml:"methods"` + MinUses int `yaml:"min_uses"` // 缓存阈值,请求次数达到此值才缓存 + CacheLockTimeout time.Duration `yaml:"cache_lock_timeout"` // 缓存锁超时时间 + BackgroundUpdateDisable bool `yaml:"background_update_disable"` // 禁用后台更新(默认 false = 启用后台更新) + CacheIgnoreHeaders []string `yaml:"cache_ignore_headers"` // 缓存时忽略的响应头 + Revalidate bool `yaml:"revalidate"` // 启用条件请求(If-Modified-Since/If-None-Match) +} + +// ProxyCacheValidConfig 缓存有效期分段配置。 +// +// 按 HTTP 状态码配置不同的缓存有效期,提供更精细的缓存控制。 +// 未配置 CacheValid 时,使用 ProxyCacheConfig.MaxAge 作为统一缓存时间。 +// +// 注意事项: +// - OK=0 时继承 MaxAge(向后兼容) +// - 其他字段为 0 表示不缓存该类响应 +// - NotFound 缓存需谨慎,避免缓存错误页面 +// +// 使用示例: +// +// cache_valid: +// ok: 10m # 200-299 缓存 10 分钟 +// redirect: 1h # 301/302 缓存 1 小时 +// not_found: 1m # 404 缓存 1 分钟 +// client_error: 0 # 其他客户端错误不缓存 +// server_error: 0 # 服务端错误不缓存 +type ProxyCacheValidConfig struct { + // OK 200-299 状态码缓存时间 + // 0 表示继承 MaxAge + OK time.Duration `yaml:"ok"` + + // Redirect 301/302 重定向缓存时间 + // 0 表示不缓存 + Redirect time.Duration `yaml:"redirect"` + + // NotFound 404 缓存时间 + // 0 表示不缓存 + NotFound time.Duration `yaml:"not_found"` + + // ClientError 400-499(除 404)缓存时间 + // 0 表示不缓存 + ClientError time.Duration `yaml:"client_error"` + + // ServerError 500-599 缓存时间 + // 0 表示不缓存 + ServerError time.Duration `yaml:"server_error"` +} + +// FileCacheConfig 文件缓存配置。 +// +// 缓存静态文件内容减少磁盘 IO。 +// +// 注意事项: +// - MaxEntries 限制最大缓存文件数量 +// - MaxSize 限制缓存总内存使用量(字节) +// - Inactive 超过此时间未访问的文件将被淘汰 +// +// 使用示例: +// +// file_cache: +// max_entries: 10000 +// max_size: 1073741824 +// inactive: 60s +type FileCacheConfig struct { + // MaxEntries 最大缓存条目数 + // 缓存文件的最大数量限制 + MaxEntries int64 `yaml:"max_entries"` + + // MaxSize 内存上限(字节) + // 缓存占用的最大内存限制 + MaxSize int64 `yaml:"max_size"` + + // Inactive 未访问淘汰时间 + // 超过此时间未被访问的缓存将被清除 + Inactive time.Duration `yaml:"inactive"` +} diff --git a/internal/config/config.go b/internal/config/config.go index 33f9dc3..10a6e14 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,8 +1,7 @@ // Package config 提供 YAML 配置文件的解析、验证和默认配置生成功能。 // -// 该文件包含配置结构体定义和加载/保存功能,包括: -// - 根配置和服务器配置结构体 -// - SSL、安全、代理、压缩等子配置结构体 +// 该文件包含根配置结构体和加载/保存功能,包括: +// - 根配置结构体 // - 配置文件的加载、保存和验证方法 // // 主要用途: @@ -20,11 +19,9 @@ package config import ( "errors" "fmt" - "net" "os" "strconv" "strings" - "time" "gopkg.in/yaml.v3" ) @@ -88,377 +85,6 @@ type Config struct { CachePath *ProxyCachePathConfig `yaml:"cache_path"` // 缓存路径配置(磁盘持久化) } -// IncludeConfig 配置引入配置。 -// -// 用于从其他文件加载配置片段并合并到当前配置。 -// 支持 glob 模式展开多个文件。 -// -// 使用示例: -// -// include: -// - path: "conf.d/*.yaml" -type IncludeConfig struct { - Path string `yaml:"path"` -} - -// VariablesConfig 自定义变量配置。 -// -// 用于定义全局自定义变量,可在日志格式和请求头中引用。 -// 变量作用于所有虚拟主机。 -// -// 注意事项: -// - 变量名只允许字母、数字、下划线 -// - 变量名不能与内置变量冲突 -// - 变量名不能以 arg_、http_、cookie_ 开头(动态变量前缀) -// -// 使用示例: -// -// variables: -// set: -// app_name: "lolly" -// version: "1.0.0" -type VariablesConfig struct { - // Set 自定义变量集合 - // 键值对形式,可在日志格式和请求头模板中使用 $var_name 引用 - Set map[string]string `yaml:"set"` -} - -// HTTP2Config HTTP/2 配置。 -// -// HTTP/2 提供多路复用、头部压缩和服务器推送等功能, -// 需要服务器配置 SSL/TLS 证书才能正常工作。 -// -// 注意事项: -// - 必须配置有效的 SSL 证书(TLS 1.2 或更高版本) -// - http2.enabled 仅在配置了 SSL/TLS 时生效 -// - 客户端可以通过 ALPN 协商使用 HTTP/2 或 HTTP/1.1 -// -// 使用示例: -// -// server: -// ssl: -// cert: "/etc/ssl/server.crt" -// key: "/etc/ssl/server.key" -// http2: -// enabled: true -// max_concurrent_streams: 128 -// max_header_list_size: "16KB" -type HTTP2Config struct { - MaxConcurrentStreams int `yaml:"max_concurrent_streams"` - MaxHeaderListSize int `yaml:"max_header_list_size"` - IdleTimeout time.Duration `yaml:"idle_timeout"` - Enabled bool `yaml:"enabled"` - PushEnabled bool `yaml:"push_enabled"` - H2CEnabled bool `yaml:"h2c_enabled"` - GracefulShutdownTimeout time.Duration `yaml:"graceful_shutdown_timeout"` -} - -// HTTP3Config HTTP/3 (QUIC) 配置。 -// -// HTTP/3 基于 QUIC 协议,提供更快的连接建立和更低的延迟。 -// 需要服务器配置 SSL/TLS 证书才能正常工作。 -// -// 注意事项: -// - 必须配置有效的 SSL 证书 -// - UDP 监听地址不能与 HTTP/1.1 或 HTTP/2 冲突 -// - 0-RTT 特性可能带来重放攻击风险,需评估安全性 -// - 部分网络环境可能限制 UDP 流量 -// -// 使用示例: -// -// http3: -// enabled: true -// listen: ":443" -// max_streams: 1000 -// idle_timeout: 30s -// enable_0rtt: true -type HTTP3Config struct { - Listen string `yaml:"listen"` - MaxStreams int `yaml:"max_streams"` - IdleTimeout time.Duration `yaml:"idle_timeout"` - Enabled bool `yaml:"enabled"` - Enable0RTT bool `yaml:"enable_0rtt"` -} - -// ServerConfig 服务器配置,包含监听地址、静态文件、代理、SSL 等设置。 -// -// 用于定义单个服务器的完整行为,包括网络监听、请求处理、 -// 安全防护和性能控制等方面。 -// -// 注意事项: -// - Listen 字段为必填项,格式为 "host:port" 或 ":port" -// - Name 字段用于虚拟主机匹配,多服务器模式下建议配置 -// - SSL 配置为可选,但生产环境强烈建议启用 -// - 超时设置需根据实际业务场景调整 -// -// 使用示例: -// -// server: -// listen: ":8080" -// name: "api.example.com" -// read_timeout: 30s -// write_timeout: 30s -type ServerConfig struct { - // 指针类型字段(按大小排列,减少 padding) - CacheAPI *CacheAPIConfig `yaml:"cache_api"` - Lua *LuaMiddlewareConfig `yaml:"lua"` - // 切片字段 - Static []StaticConfig `yaml:"static"` - Proxy []ProxyConfig `yaml:"proxy"` - Rewrite []RewriteRule `yaml:"rewrite"` - // 字符串字段 - ClientMaxBodySize string `yaml:"client_max_body_size"` - Name string `yaml:"name"` - Listen string `yaml:"listen"` - // 结构体字段(嵌入类型) - Security SecurityConfig `yaml:"security"` - Compression CompressionConfig `yaml:"compression"` - SSL SSLConfig `yaml:"ssl"` - UnixSocket UnixSocketConfig `yaml:"unix_socket"` // Unix socket 配置 - LimitRate LimitRateConfig `yaml:"limit_rate"` // 响应速率限制配置 - Types TypesConfig `yaml:"types"` // MIME 类型配置 - // 切片字段 - ServerNames []string `yaml:"server_names"` // 支持多个 server_name - // time.Duration 字段(int64) - ReadTimeout time.Duration `yaml:"read_timeout"` - IdleTimeout time.Duration `yaml:"idle_timeout"` - WriteTimeout time.Duration `yaml:"write_timeout"` - // 基本类型字段(int 按大小排列) - MaxRequestsPerConn int `yaml:"max_requests_per_conn"` - MaxConnsPerIP int `yaml:"max_conns_per_ip"` - Concurrency int `yaml:"concurrency"` // 最大并发连接数(默认 256 * 1024) - ReadBufferSize int `yaml:"read_buffer_size"` // 读缓冲区大小(字节,默认 16KB) - WriteBufferSize int `yaml:"write_buffer_size"` // 写缓冲区大小(字节,默认 16KB) - // 布尔字段(放在一起减少 padding) - Default bool `yaml:"default,omitempty"` // VHost 默认主机标记 - ReduceMemoryUsage bool `yaml:"reduce_memory_usage"` // 是否优先减少内存使用(默认 false,优先性能) - ServerTokens bool `yaml:"server_tokens"` // false 隐藏版本号,默认 true(零值表示显示版本) -} - -// StaticConfig 静态文件服务配置。 -// -// 用于配置静态文件服务器的行为,包括路径匹配、根目录和索引文件。 -// -// 注意事项: -// - Path 为路径前缀,匹配的请求将被该静态处理器处理 -// - Root 路径可以是相对路径或绝对路径 -// - 索引文件按顺序查找,第一个存在的文件将被使用 -// - 目录路径需要确保有读取权限 -// -// 使用示例: -// -// static: -// - path: "/" -// root: "/var/www/html" -// index: ["index.html", "index.htm"] -// - path: "/assets/" -// root: "/var/www/assets" -type StaticConfig struct { - // Path 匹配路径前缀 - // 以此前缀开头的请求将被该静态处理器处理 - // 默认为 "/",匹配所有路径 - Path string `yaml:"path"` - - // Root 静态文件根目录 - // 所有静态文件请求都将以此目录为基础解析 - // 请求路径追加到 root 后面 - // 示例: root=/var/www, path=/static/ → /static/img.png → /var/www/static/img.png - Root string `yaml:"root"` - - // Alias 替换路径(与 root 互斥) - // 将 location 路径替换为 alias 路径(nginx alias 语义) - // 示例: alias=/var/www/files/, path=/images/ → /images/logo.png → /var/www/files/logo.png - Alias string `yaml:"alias"` - - // Index 索引文件列表 - // 访问目录时依次查找这些文件作为默认页面 - // 默认为 ["index.html", "index.htm"] - Index []string `yaml:"index"` - - // TryFiles 按顺序尝试查找的文件列表 - // 支持以下模式: - // - $uri: 请求路径 - // - $uri/: 请求路径加斜杠(目录) - // - $uri.: 请求路径加扩展名(如 $uri.html, $uri.json) - // - /path: 绝对路径回退(如 /index.html) - // - filename: 相对路径回退(如 fallback.html) - // - // nginx 兼容性: - // - $uri 变量语义与 nginx try_files 指令一致 - // - 配置语法可从 nginx 直接迁移 - // - // 安全限制(附加于 nginx 基础): - // - 扩展名仅允许字母、数字、点、下划线、连字符 - // - 禁止危险后缀(.php, .exe, .bat 等) - // - 禁止 null byte 和路径分隔符 - // - // 根路径边界情况: - // - 当 relPath="/" 且模式为 "$uri." 时,返回空字符串 - // - 此设计避免生成 "/.html" 这样的隐藏文件名 - // - 建议使用绝对路径回退(如 /index.html)处理根路径 - // - // 示例: - // try_files: ["$uri", "$uri.html", "/index.html"] - // try_files: ["$uri", "$uri/", "/app.html"] - TryFiles []string `yaml:"try_files"` - - // TryFilesPass 内部重定向是否触发中间件 - // 默认为 false,内部重定向不触发中间件 - // 设置为 true 时,try_files 回退会重新进入中间件链 - TryFilesPass bool `yaml:"try_files_pass"` - - // SymlinkCheck 是否启用符号链接安全检查 - // 默认为 false,启用后会验证符号链接指向的文件是否在允许的路径范围内 - // 防止通过符号链接访问敏感文件(如 /etc/passwd) - SymlinkCheck bool `yaml:"symlink_check"` - - // LocationType 位置匹配类型 - // 可选值:exact、prefix、regex、regex_caseless、prefix_priority、named - LocationType string `yaml:"location_type"` - - // Internal 仅允许内部访问 - // 设置为 true 时,该位置仅允许内部重定向访问 - Internal bool `yaml:"internal"` - - // Expires 缓存过期时间 - // 支持 nginx 兼容格式:30d, 1h, 1m, max, epoch, off - // 设置 Cache-Control: max-age 和 Expires 响应头 - // 示例:expires: 30d → Cache-Control: max-age=2592000 - Expires string `yaml:"expires"` -} - -// ProxyConfig 反向代理配置,支持负载均衡和健康检查。 -// -// 用于将请求转发到后端服务器,支持多种负载均衡算法 -// 和健康检查机制。 -// -// 注意事项: -// - Path 使用前缀匹配,较长路径优先匹配 -// - 至少配置一个 Target 才能正常工作 -// - 负载均衡算法支持:round_robin、weighted_round_robin、least_conn、ip_hash、consistent_hash、random -// - 一致性哈希需要配置 HashKey -// -// 使用示例: -// -// proxy: -// - path: "/api/" -// targets: -// - url: "http://backend1:8080" -// weight: 3 -// - url: "http://backend2:8080" -// weight: 1 -// load_balance: "weighted_round_robin" -// health_check: -// interval: 10s -// path: "/health" -type ProxyConfig struct { - // 指针类型字段(按大小排列) - RedirectRewrite *RedirectRewriteConfig `yaml:"redirect_rewrite"` - ProxySSL *ProxySSLConfig `yaml:"proxy_ssl"` - CacheValid *ProxyCacheValidConfig `yaml:"cache_valid"` - Buffering *ProxyBufferingConfig `yaml:"buffering"` - // 切片字段 - Targets []ProxyTarget `yaml:"targets"` - // 字符串字段 - Path string `yaml:"path"` - LoadBalance string `yaml:"load_balance"` - HashKey string `yaml:"hash_key"` - ClientMaxBodySize string `yaml:"client_max_body_size"` - ProxyBind string `yaml:"proxy_bind"` - // 结构体字段 - Headers ProxyHeaders `yaml:"headers"` - BalancerByLua BalancerByLuaConfig `yaml:"balancer_by_lua"` - HealthCheck HealthCheckConfig `yaml:"health_check"` - NextUpstream NextUpstreamConfig `yaml:"next_upstream"` - Cache ProxyCacheConfig `yaml:"cache"` - Timeout ProxyTimeout `yaml:"timeout"` - // 基本类型字段 - VirtualNodes int `yaml:"virtual_nodes"` - - // LocationType 位置匹配类型 - // 可选值:exact、prefix_priority、regex、regex_caseless、prefix、named - LocationType string `yaml:"location_type"` - - // LocationName 位置名称 - // 仅当 LocationType 为 named 时使用,用于命名位置块 - LocationName string `yaml:"location_name"` - - // Internal 仅允许内部访问 - // 设置为 true 时,该位置仅允许内部重定向访问 - Internal bool `yaml:"internal"` -} - -// ProxyBufferingConfig 代理缓冲配置。 -// -// 控制代理响应的缓冲行为: -// - "default" 或 "on": 缓冲响应到内存/临时文件 -// - "off": 流式转发响应,不缓冲 -// -// 使用示例: -// -// buffering: -// mode: "off" -type ProxyBufferingConfig struct { - // Mode 缓冲模式 - // 可选值:"default"(默认缓冲), "on"(强制缓冲), "off"(关闭缓冲) - Mode string `yaml:"mode"` - - // BufferSize 响应缓冲区大小(字节) - // 0 表示使用默认值 - BufferSize int `yaml:"buffer_size"` - - // Buffers 多缓冲区配置字符串 - // 格式:"数量 大小" 或 "数量1 大小1 数量2 大小2 ..." - // 例如:"8 16k" 表示 8 个 16KB 缓冲区 - // 例如:"4 4k 8 16k" 表示 4 个 4KB + 8 个 16KB 缓冲区 - Buffers string `yaml:"buffers"` - - // BufferCount 缓冲区数量(解析后) - BufferCount int `yaml:"-"` - - // BufferSizeEach 每个缓冲区大小(字节,解析后) - BufferSizeEach int `yaml:"-"` -} - -// ParseBuffers 解析 Buffers 配置字符串。 -// -// 支持格式: -// - "8 16k" → 8 个 16KB 缓冲区 -// - "4 4k" → 4 个 4KB 缓冲区 -// -// 大小单位: -// - k 或 K: KB (1024 字节) -// - m 或 M: MB (1024 * 1024 字节) -// - 无单位: 字节 -func (c *ProxyBufferingConfig) ParseBuffers() { - if c.Buffers == "" { - // 向后兼容:使用 BufferSize - if c.BufferSize > 0 { - c.BufferCount = 1 - c.BufferSizeEach = c.BufferSize - } - return - } - - parts := strings.Fields(c.Buffers) - if len(parts) < 2 { - return // 无效格式 - } - - count, err := strconv.Atoi(parts[0]) - if err != nil || count <= 0 { - return // 无效数量 - } - - sizeEach, err := parseSize(parts[1]) - if err != nil || sizeEach <= 0 { - return // 无效大小 - } - - c.BufferCount = count - c.BufferSizeEach = sizeEach -} - // parseSize 解析大小字符串(支持 k, m 单位)。 func parseSize(s string) (int, error) { s = strings.TrimSpace(s) @@ -488,1592 +114,6 @@ func parseSize(s string) (int, error) { return value * multiplier, nil } -// BalancerByLuaConfig Lua 负载均衡配置 -// -// 使用 Lua 脚本动态选择后端目标,支持自定义负载均衡逻辑。 -// -// 注意事项: -// - Script 为 Lua 脚本文件路径 -// - Timeout 控制脚本执行超时 -// - Fallback 指定 Lua 失败时的备用算法 -// -// 使用示例: -// -// balancer_by_lua: -// enabled: true -// script: "/etc/lolly/scripts/balancer.lua" -// timeout: 100ms -// fallback: "round_robin" -type BalancerByLuaConfig struct { - // Script Lua 脚本路径 - Script string `yaml:"script"` - - // Fallback 失败时使用的默认负载均衡算法 - // 默认值: "round_robin" - Fallback string `yaml:"fallback"` - - // Timeout 执行超时 - // 默认值: 100ms - Timeout time.Duration `yaml:"timeout"` - - // Enabled 是否启用 - Enabled bool `yaml:"enabled"` -} - -// ProxyTarget 后端目标配置。 -// -// 定义单个后端服务器的地址和权重。 -// -// 注意事项: -// - URL 必须包含协议(http:// 或 https://) -// - Weight 仅在 weighted_round_robin 算法下生效 -// -// 使用示例: -// -// targets: -// - url: "http://backend1:8080" -// weight: 3 -// - url: "http://backend2:8080" -// weight: 1 -type ProxyTarget struct { - // URL 后端地址 - // 格式:"http://host:port" 或 "https://host:port" - URL string `yaml:"url"` - - // Weight 权重 - // 用于加权轮询算法,值越大分配的请求越多 - Weight int `yaml:"weight"` - - // MaxConns 最大并发连接数 - // 0 表示不限制 - MaxConns int `yaml:"max_conns"` - - // MaxFails 最大失败次数 - // 在 FailTimeout 期间失败次数达到此值后标记为不可用 - // 0 表示不进行被动失败检测 - MaxFails int `yaml:"max_fails"` - - // FailTimeout 失败超时时间 - // 达到 MaxFails 后,目标在此时间内被视为不可用 - FailTimeout time.Duration `yaml:"fail_timeout"` - - // Backup 备份服务器 - // 仅当所有非备份服务器不可用时才使用 - Backup bool `yaml:"backup"` - - // Down 标记服务器为永久不可用 - Down bool `yaml:"down"` - - // ProxyURI 代理传递的 URI 路径 - // 设置后替换请求路径,支持 nginx proxy_pass URI 语义 - ProxyURI string `yaml:"proxy_uri"` -} - -// HealthCheckConfig 健康检查配置。 -// -// 定期检查后端服务器的健康状态,自动剔除不健康的节点。 -// -// 注意事项: -// - Interval 不宜设置过小,避免增加后端负担 -// - Path 应该是轻量级的健康检查端点 -// - 超时时间应小于检查间隔 -// -// 使用示例: -// -// health_check: -// interval: 10s -// path: "/health" -// timeout: 5s -type HealthCheckConfig struct { - Path string `yaml:"path"` - Interval time.Duration `yaml:"interval"` - Timeout time.Duration `yaml:"timeout"` - Match *HealthMatchConfig `yaml:"match"` // 健康检查匹配配置 - SlowStart time.Duration `yaml:"slow_start"` // 慢启动时间 -} - -// HealthMatchConfig 健康检查匹配配置。 -type HealthMatchConfig struct { - Status []string `yaml:"status"` // 状态码范围列表 - Body string `yaml:"body"` // 响应体正则表达式 - Headers map[string]string `yaml:"headers"` // 响应头匹配 -} - -// ProxyTimeout 代理超时配置。 -// -// 控制代理请求的各个阶段超时。 -// -// 注意事项: -// - Connect 超时包括 DNS 解析和 TCP 连接建立 -// - Read 和 Write 超时分别控制响应读取和请求发送 -// - 超时时间需要根据后端服务响应时间调整 -// -// 使用示例: -// -// timeout: -// connect: 5s -// read: 30s -// write: 30s -type ProxyTimeout struct { - // Connect 连接超时 - // 建立到后端服务器的连接超时 - Connect time.Duration `yaml:"connect"` - - // Read 读取超时 - // 从后端读取响应的超时 - Read time.Duration `yaml:"read"` - - // Write 写入超时 - // 向后端发送请求的超时 - Write time.Duration `yaml:"write"` -} - -// ProxyHeaders 代理请求/响应头配置。 -// -// 在代理转发过程中修改 HTTP 头部。 -// -// 注意事项: -// - SetRequest 添加/修改发送到后端的请求头 -// - SetResponse 添加/修改返回给客户端的响应头 -// - Remove 会删除指定的请求头(在发送到后端之前) -// -// 使用示例: -// -// headers: -// set_request: -// X-Forwarded-For: "$remote_addr" -// X-Real-IP: "$remote_addr" -// set_response: -// X-Proxy-By: "lolly" -// remove: -// - "X-Internal-Header" -type ProxyHeaders struct { - // SetRequest 设置请求头 - // 发送到后端的请求中添加或覆盖的头部 - SetRequest map[string]string `yaml:"set_request"` - - // SetResponse 设置响应头 - // 返回给客户端的响应中添加或覆盖的头部 - SetResponse map[string]string `yaml:"set_response"` - - // Remove 移除的头部 - // 从发送到后端的请求中移除的头部列表 - Remove []string `yaml:"remove"` - - // HideResponse 隐藏的响应头 - // 从返回给客户端的响应中移除的头部列表 - HideResponse []string `yaml:"hide_response"` - - // PassResponse 允许传递的响应头 - // 仅传递列出的头部,其他全部隐藏(白名单模式) - PassResponse []string `yaml:"pass_response"` - - // IgnoreHeaders 忽略的头部 - // 代理时完全忽略这些头部,不转发到后端也不返回给客户端 - IgnoreHeaders []string `yaml:"ignore_headers"` - - // CookieDomain Cookie 域重写 - // 将响应中 Set-Cookie 的 domain 替换为此值 - CookieDomain string `yaml:"cookie_domain"` - - // CookiePath Cookie 路径重写 - // 将响应中 Set-Cookie 的 path 替换为此值 - CookiePath string `yaml:"cookie_path"` -} - -// ProxyCachePathConfig 缓存路径配置(磁盘持久化)。 -// -// 配置磁盘缓存路径和相关参数,支持 L1/L2 分层缓存架构。 -// 配置后,代理缓存将持久化到磁盘,服务重启后可恢复。 -// -// 注意事项: -// - Path 为必填项,指定缓存根目录 -// - Levels 支持最多 3 级目录(如 "1:2:2") -// - MaxSize 为 0 表示不限制大小 -// - L1MaxEntries/L1MaxSize 为 0 时使用默认值 -// -// 使用示例: -// -// cache_path: -// path: "/var/cache/lolly" -// levels: "1:2" -// max_size: "1GB" -// inactive: "60m" -// l1_max_entries: 10000 -type ProxyCachePathConfig struct { - // Path 缓存根目录 - Path string `yaml:"path"` - - // Levels 目录层级,如 "1:2" 表示两级目录 - Levels string `yaml:"levels"` - - // MaxSize 最大缓存大小(字节) - MaxSize int64 `yaml:"max_size"` - - // Inactive 未访问淘汰时间 - Inactive time.Duration `yaml:"inactive"` - - // Purger 是否启用后台清理 - Purger bool `yaml:"purger"` - - // PurgerInterval 清理间隔 - PurgerInterval time.Duration `yaml:"purger_interval"` - - // L1MaxEntries L1 最大条目数 - L1MaxEntries int64 `yaml:"l1_max_entries"` - - // L1MaxSize L1 最大内存大小 - L1MaxSize int64 `yaml:"l1_max_size"` - - // PromoteThreshold 提升到 L1 的访问阈值 - PromoteThreshold int `yaml:"promote_threshold"` -} - -// ProxyCacheConfig 代理缓存配置。 -// -// 缓存后端响应,减少重复请求,提高响应速度。 -// -// 注意事项: -// - 仅缓存 GET 和 HEAD 请求 -// - 后端响应中 Cache-Control 头会覆盖 MaxAge 设置 -// - CacheLock 可防止缓存击穿,但会增加首次请求延迟 -// - 谨慎缓存动态内容,避免返回过期数据 -// -// 使用示例: -// -// cache: -// enabled: true -// max_age: 5m -// cache_lock: true -// stale_while_revalidate: 1m -type ProxyCacheConfig struct { - MaxAge time.Duration `yaml:"max_age"` - StaleWhileRevalidate time.Duration `yaml:"stale_while_revalidate"` - StaleIfError time.Duration `yaml:"stale_if_error"` // 错误时使用过期缓存 - StaleIfTimeout time.Duration `yaml:"stale_if_timeout"` // 超时时使用过期缓存 - Enabled bool `yaml:"enabled"` - CacheLock bool `yaml:"cache_lock"` - Methods []string `yaml:"methods"` - MinUses int `yaml:"min_uses"` // 缓存阈值,请求次数达到此值才缓存 - CacheLockTimeout time.Duration `yaml:"cache_lock_timeout"` // 缓存锁超时时间 - BackgroundUpdateDisable bool `yaml:"background_update_disable"` // 禁用后台更新(默认 false = 启用后台更新) - CacheIgnoreHeaders []string `yaml:"cache_ignore_headers"` // 缓存时忽略的响应头 - Revalidate bool `yaml:"revalidate"` // 启用条件请求(If-Modified-Since/If-None-Match) -} - -// ProxyCacheValidConfig 缓存有效期分段配置。 -// -// 按 HTTP 状态码配置不同的缓存有效期,提供更精细的缓存控制。 -// 未配置 CacheValid 时,使用 ProxyCacheConfig.MaxAge 作为统一缓存时间。 -// -// 注意事项: -// - OK=0 时继承 MaxAge(向后兼容) -// - 其他字段为 0 表示不缓存该类响应 -// - NotFound 缓存需谨慎,避免缓存错误页面 -// -// 使用示例: -// -// cache_valid: -// ok: 10m # 200-299 缓存 10 分钟 -// redirect: 1h # 301/302 缓存 1 小时 -// not_found: 1m # 404 缓存 1 分钟 -// client_error: 0 # 其他客户端错误不缓存 -// server_error: 0 # 服务端错误不缓存 -type ProxyCacheValidConfig struct { - // OK 200-299 状态码缓存时间 - // 0 表示继承 MaxAge - OK time.Duration `yaml:"ok"` - - // Redirect 301/302 重定向缓存时间 - // 0 表示不缓存 - Redirect time.Duration `yaml:"redirect"` - - // NotFound 404 缓存时间 - // 0 表示不缓存 - NotFound time.Duration `yaml:"not_found"` - - // ClientError 400-499(除 404)缓存时间 - // 0 表示不缓存 - ClientError time.Duration `yaml:"client_error"` - - // ServerError 500-599 缓存时间 - // 0 表示不缓存 - ServerError time.Duration `yaml:"server_error"` -} - -// ProxySSLConfig 上游 SSL/TLS 配置。 -// -// 配置代理连接上游服务器时的 TLS 行为,支持自定义 CA、客户端证书(mTLS)、 -// SNI 和 TLS 版本控制。 -// -// 注意事项: -// - Enabled 为 true 时启用自定义 TLS 配置 -// - TrustedCA 用于验证上游服务器证书 -// - ClientCert + ClientKey 用于 mTLS 客户端认证 -// - InsecureSkipVerify 仅用于测试,生产环境禁用 -// -// 使用示例: -// -// proxy_ssl: -// enabled: true -// server_name: "api.internal" -// trusted_ca: "/etc/ssl/ca/upstream-ca.crt" -// client_cert: "/etc/ssl/client.crt" -// client_key: "/etc/ssl/client.key" -// min_version: "TLSv1.2" -type ProxySSLConfig struct { - // 字符串字段 - ServerName string `yaml:"server_name"` - TrustedCA string `yaml:"trusted_ca"` - ClientCert string `yaml:"client_cert"` - ClientKey string `yaml:"client_key"` - MinVersion string `yaml:"min_version"` - MaxVersion string `yaml:"max_version"` - // 布尔字段 - Enabled bool `yaml:"enabled"` - InsecureSkipVerify bool `yaml:"insecure_skip_verify"` -} - -// RedirectRewriteConfig Location/Refresh 头改写配置 -// -// 用于配置代理响应中 Location 和 Refresh 头的改写行为。 -// -// 注意事项: -// - Mode 支持 "default"、"off"、"custom" 三种模式 -// - 未配置或空字符串时默认为 "default" 模式 -// - "custom" 模式必须配置至少一条规则 -// -// 使用示例: -// -// redirect_rewrite: -// mode: "default" # 或 "off" 或 "custom" -// rules: -// - pattern: "http://backend:8000/" -// replacement: "$scheme://$host:$server_port/" -type RedirectRewriteConfig struct { - // Mode 运行模式: "default" | "off" | "custom" - // default: 自动从选中的 target URL 生成规则(运行时) - // off: 禁用改写 - // custom: 使用 Rules 列表(预编译) - // 未配置或空字符串时默认为 "default" - Mode string `yaml:"mode"` - - // Rules 改写规则列表,仅在 Mode="custom" 时使用 - Rules []RedirectRewriteRule `yaml:"rules"` -} - -// RedirectRewriteRule 单条改写规则 -// -// 定义 Location/Refresh 头改写的匹配模式和替换目标。 -// -// 注意事项: -// - Pattern 以 ~ 开头表示正则,~* 表示大小写不敏感 -// - 无 ~ 前缀时使用前缀匹配语义 -// - Replacement 支持变量展开($host, $scheme, $server_port 等) -// -// 使用示例: -// -// rules: -// - pattern: "http://backend:8000/" -// replacement: "$scheme://$host:$server_port/" -// - pattern: "~^http://[^/]+:8000/(.*)$" -// replacement: "$scheme://$host/$1" -type RedirectRewriteRule struct { - // Pattern 匹配模式,支持正则(以 ~ 开头)或精确匹配 - // 示例: "http://localhost:8000/" 或 "~^http://[^/]+:8000/" - Pattern string `yaml:"pattern"` - - // Replacement 替换目标,支持变量展开 - // 示例: "$scheme://$host:$server_port/" 或 "/" - Replacement string `yaml:"replacement"` -} - -// NextUpstreamConfig 故障转移配置,定义后端失败时的自动重试行为。 -// -// 当后端返回特定错误状态码或连接失败时,自动尝试下一个可用后端。 -// -// 注意事项: -// - Tries 为 1 时禁用故障转移 -// - 空 NextUpstream 使用默认值(Tries=1,禁用故障转移) -// - 建议根据后端数量合理设置 Tries 值 -// -// 使用示例: -// -// next_upstream: -// tries: 3 -// http_codes: [502, 503, 504] -type NextUpstreamConfig struct { - HTTPCodes []int `yaml:"http_codes"` - Tries int `yaml:"tries"` -} - -// SSLConfig SSL/TLS 配置。 -// -// 用于配置 HTTPS 服务所需的证书和加密参数。 -// 支持 TLS 1.2 和 TLS 1.3 协议,可自定义加密套件。 -// -// 注意事项: -// - Cert 和 Key 为必需字段,分别指向证书和私钥文件 -// - CertChain 可选,用于配置完整的证书链 -// - Protocols 建议使用默认值,避免使用不安全的 TLS 1.0/1.1 -// - Ciphers 仅对 TLS 1.2 有效,TLS 1.3 有固定加密套件 -// - 启用 OCSPStapling 可提升握手性能 -// -// 使用示例: -// -// ssl: -// cert: "/etc/ssl/certs/server.crt" -// key: "/etc/ssl/private/server.key" -// cert_chain: "/etc/ssl/certs/chain.crt" -// protocols: ["TLSv1.2", "TLSv1.3"] -// ocsp_stapling: true -// hsts: -// max_age: 31536000 -// include_sub_domains: true -type SSLConfig struct { - ClientVerify ClientVerifyConfig `yaml:"client_verify"` - Cert string `yaml:"cert"` - Key string `yaml:"key"` - CertChain string `yaml:"cert_chain"` - Protocols []string `yaml:"protocols"` - Ciphers []string `yaml:"ciphers"` - SessionTickets SessionTicketsConfig `yaml:"session_tickets"` - HTTP2 HTTP2Config `yaml:"http2"` - HSTS HSTSConfig `yaml:"hsts"` - OCSPStapling bool `yaml:"ocsp_stapling"` -} - -// HSTSConfig HTTP Strict Transport Security 配置。 -// -// 强制浏览器使用 HTTPS 访问,防止中间人攻击和协议降级攻击。 -// -// 注意事项: -// - MaxAge 单位为秒,建议至少设置为 1 年(31536000) -// - IncludeSubDomains 为 true 时策略应用于所有子域名 -// - Preload 为 true 表示申请加入浏览器预加载列表 -// - 启用前确保所有站点资源都支持 HTTPS -// -// 使用示例: -// -// hsts: -// max_age: 31536000 -// include_sub_domains: true -// preload: false -type HSTSConfig struct { - // MaxAge 过期时间(秒) - // 默认 31536000(1年),建议至少 6 个月 - MaxAge int `yaml:"max_age"` - - // IncludeSubDomains 包含子域名 - // 为 true 时策略应用于当前域名及其所有子域名 - IncludeSubDomains bool `yaml:"include_sub_domains"` - - // Preload 加入 HSTS 预加载列表 - // 申请加入浏览器内置的 HSTS 列表 - Preload bool `yaml:"preload"` -} - -// SessionTicketsConfig TLS Session Ticket 配置。 -// -// Session Tickets 允许 TLS 1.3 会话恢复,避免完整握手,显著提升性能。 -// 密钥定期轮换增强安全性,同时保留旧密钥确保已发放的票据仍可解密。 -// -// 注意事项: -// - KeyFile 为密钥存储文件路径,用于持久化密钥 -// - RotateInterval 为密钥轮换间隔,建议 1-24 小时 -// - RetainKeys 为保留的历史密钥数量,至少保留 2 个 -// - 密钥文件权限应为 0600(仅所有者可读写) -// -// 使用示例: -// -// ssl: -// session_tickets: -// enabled: true -// key_file: "/var/lib/lolly/session_tickets.key" -// rotate_interval: 1h -// retain_keys: 3 -type SessionTicketsConfig struct { - KeyFile string `yaml:"key_file"` - RotateInterval time.Duration `yaml:"rotate_interval"` - RetainKeys int `yaml:"retain_keys"` - Enabled bool `yaml:"enabled"` -} - -// ClientVerifyConfig mTLS 客户端证书验证配置。 -// -// 配置双向 TLS 认证,要求客户端提供有效证书才能建立连接。 -// 适用于需要强身份验证的场景,如 API 服务、内部系统通信。 -// -// 注意事项: -// - Mode 可选值:none、request、require、optional_no_ca -// - ClientCA 为客户端 CA 证书文件路径(必需) -// - VerifyDepth 为证书链验证深度,默认 1 -// - CRL 为证书撤销列表文件路径(可选) -// -// 使用示例: -// -// ssl: -// client_verify: -// enabled: true -// mode: "require" -// client_ca: "/etc/ssl/ca/client-ca.crt" -// verify_depth: 2 -// crl: "/etc/ssl/ca/client-ca.crl" -type ClientVerifyConfig struct { - Mode string `yaml:"mode"` - ClientCA string `yaml:"client_ca"` - CRL string `yaml:"crl"` - VerifyDepth int `yaml:"verify_depth"` - Enabled bool `yaml:"enabled"` -} - -// SecurityConfig 安全配置,包含访问控制、限流、认证和安全头部。 -// -// 用于保护服务器免受各种网络攻击和滥用。 -// -// 注意事项: -// - Access 配置 IP 黑白名单控制访问来源 -// - RateLimit 配置请求频率限制防止 DDoS 攻击 -// - Auth 配置 HTTP Basic 认证保护敏感资源 -// - Headers 配置安全响应头部增强浏览器安全 -// - 各项配置可以组合使用,增强安全性 -// -// 使用示例: -// -// security: -// access: -// allow: ["192.168.1.0/24"] -// deny: ["10.0.0.0/8"] -// rate_limit: -// request_rate: 100 -// burst: 150 -// auth: -// type: "basic" -// users: -// - name: "admin" -// password: "$2y$10$..." -// headers: -// x_frame_options: "DENY" -type SecurityConfig struct { - Headers SecurityHeaders `yaml:"headers"` - Access AccessConfig `yaml:"access"` - ErrorPage ErrorPageConfig `yaml:"error_page"` - Auth AuthConfig `yaml:"auth"` - AuthRequest AuthRequestConfig `yaml:"auth_request"` - RateLimit RateLimitConfig `yaml:"rate_limit"` -} - -// AccessConfig IP 访问控制配置。 -// -// 通过 IP 地址或 CIDR 范围控制访问权限,支持基于 GeoIP 的国家代码访问控制。 -// -// 注意事项: -// - Allow 和 Deny 列表按配置顺序匹配 -// - Default 指定未匹配时的默认动作 -// - TrustedProxies 用于正确获取客户端真实 IP -// - GeoIP 配置启用后,会基于国家代码进行二次检查 -// - 支持 IPv4 和 IPv6 地址格式 -// -// 使用示例: -// -// access: -// allow: ["192.168.1.0/24", "10.0.0.0/8"] -// deny: ["192.168.1.100"] -// default: "deny" -// trusted_proxies: ["172.16.0.0/16"] -// geoip: -// enabled: true -// database: "/var/lib/geoip/GeoIP2-Country.mmdb" -// allow_countries: ["US", "JP", "GB"] -// deny_countries: ["CN", "RU"] -// default: "deny" -// cache_size: 10000 -// cache_ttl: 1h -// private_ip_behavior: "allow" -type AccessConfig struct { - // Allow 允许的 IP/CIDR 列表 - // 配置允许访问的 IP 地址或网段 - Allow []string `yaml:"allow"` - - // Deny 拒绝的 IP/CIDR 列表 - // 配置拒绝访问的 IP 地址或网段 - Deny []string `yaml:"deny"` - - // TrustedProxies 可信代理 CIDR 列表 - // 用于正确解析 X-Forwarded-For 头部获取真实客户端 IP - TrustedProxies []string `yaml:"trusted_proxies"` - - // Default 默认动作 - // 未匹配任何规则时的处理方式:allow 或 deny - Default string `yaml:"default"` - - // GeoIP GeoIP 国家代码访问控制配置 - GeoIP GeoIPConfig `yaml:"geoip"` -} - -// GeoIPConfig GeoIP 访问控制配置。 -// -// 通过 MaxMind GeoIP2 数据库查询 IP 所属国家,实现基于国家代码的访问控制。 -// -// 注意事项: -// - Database 为 GeoIP2 数据库文件路径(.mmdb 格式) -// - AllowCountries 和 DenyCountries 使用 ISO 3166-1 alpha-2 国家代码 -// - CacheSize 设置 LRU 缓存最大条目数,0 表示使用默认值 10000 -// - CacheTTL 设置缓存有效期,0 表示使用默认值 1 小时 -// - PrivateIPBehavior 控制私有 IP 的处理策略 -// -// 使用示例: -// -// geoip: -// enabled: true -// database: "/var/lib/geoip/GeoIP2-Country.mmdb" -// allow_countries: ["US", "JP", "GB"] -// deny_countries: ["CN", "RU"] -// default: "deny" -// cache_size: 10000 -// cache_ttl: 1h -// private_ip_behavior: "allow" -type GeoIPConfig struct { - Database string `yaml:"database"` - Default string `yaml:"default"` - PrivateIPBehavior string `yaml:"private_ip_behavior"` - AllowCountries []string `yaml:"allow_countries"` - DenyCountries []string `yaml:"deny_countries"` - CacheSize int `yaml:"cache_size"` - CacheTTL time.Duration `yaml:"cache_ttl"` - Enabled bool `yaml:"enabled"` -} - -// RateLimitConfig 速率限制配置。 -// -// 限制请求频率防止 DDoS 攻击和资源滥用。 -// -// 注意事项: -// - RequestRate 为每秒允许的最大请求数 -// - Burst 为突发流量允许的最大请求数 -// - ConnLimit 为单个 IP 的最大并发连接数 -// - Algorithm 支持 token_bucket 和 sliding_window 两种算法 -// - SlidingWindow 仅在 sliding_window 算法下生效 -// -// 使用示例: -// -// rate_limit: -// request_rate: 100 -// burst: 150 -// conn_limit: 50 -// algorithm: "token_bucket" -// key: "ip" -type RateLimitConfig struct { - Key string `yaml:"key"` - Algorithm string `yaml:"algorithm"` - SlidingWindowMode string `yaml:"sliding_window_mode"` - RequestRate int `yaml:"request_rate"` - Burst int `yaml:"burst"` - ConnLimit int `yaml:"conn_limit"` - SlidingWindow int `yaml:"sliding_window"` -} - -// LimitRateConfig 响应速率限制配置。 -// -// 控制响应数据的发送速率,防止单个连接占用过多带宽。 -// -// 注意事项: -// - Rate 为每秒发送的字节数,0 表示不限速 -// - Burst 为突发流量允许的字节数 -// - LargeFileThreshold 为大文件阈值,超过此大小的文件采用特殊策略 -// - LargeFileStrategy 为大文件策略:skip(跳过限速)或 coarse(粗粒度限速) -// -// 使用示例: -// -// limit_rate: -// rate: 1048576 # 1MB/s -// burst: 524288 # 512KB 突发 -// large_file_threshold: 10485760 # 10MB -// large_file_strategy: "skip" -type LimitRateConfig struct { - // Rate 字节/秒,0 表示不限速 - Rate int64 `yaml:"rate"` - - // Burst 突发流量字节数 - Burst int64 `yaml:"burst"` - - // LargeFileThreshold 大文件阈值(字节),默认 10MB - LargeFileThreshold int64 `yaml:"large_file_threshold"` - - // LargeFileStrategy 大文件策略:skip(跳过限速)或 coarse(粗粒度限速) - LargeFileStrategy string `yaml:"large_file_strategy"` -} - -// AuthConfig 认证配置。 -// -// 配置 HTTP Basic 认证保护敏感资源。 -// -// 注意事项: -// - Type 目前仅支持 basic -// - RequireTLS 默认为 true,强制 HTTPS 传输 -// - Algorithm 支持 bcrypt 和 argon2id -// - Users 中 Password 字段存储的是密码哈希而非明文 -// - MinPasswordLength 控制密码最小长度要求 -// -// 使用示例: -// -// auth: -// type: "basic" -// require_tls: true -// algorithm: "bcrypt" -// realm: "Secure Area" -// min_password_length: 8 -// users: -// - name: "admin" -// password: "$2y$10$..." -type AuthConfig struct { - Type string `yaml:"type"` - Algorithm string `yaml:"algorithm"` - Realm string `yaml:"realm"` - Users []User `yaml:"users"` - MinPasswordLength int `yaml:"min_password_length"` - RequireTLS bool `yaml:"require_tls"` -} - -// User 认证用户配置。 -// -// 定义单个认证用户的凭据。 -// -// 注意事项: -// - Name 为用户标识,区分大小写 -// - Password 存储的是哈希值而非明文密码 -// - 支持的哈希格式取决于 Algorithm 设置 -// -// 使用示例: -// -// users: -// - name: "admin" -// password: "$2y$10$N9qo8uLOickgx2ZMRZoMy..." -type User struct { - // Name 用户名 - // 认证时使用的用户标识 - Name string `yaml:"name"` - - // Password 密码哈希 - // bcrypt 或 argon2id 哈希值,非明文密码 - Password string `yaml:"password"` -} - -// SecurityHeaders 安全头部配置。 -// -// 配置 HTTP 安全响应头部增强浏览器安全。 -// -// 注意事项: -// - XFrameOptions 防止点击劫持攻击 -// - XContentTypeOptions 防止 MIME 类型嗅探 -// - ContentSecurityPolicy 控制资源加载策略 -// - ReferrerPolicy 控制 Referer 头发送策略 -// - PermissionsPolicy 控制浏览器功能权限 -// -// 使用示例: -// -// headers: -// x_frame_options: "DENY" -// x_content_type_options: "nosniff" -// content_security_policy: "default-src 'self'" -// referrer_policy: "strict-origin-when-cross-origin" -type SecurityHeaders struct { - // XFrameOptions X-Frame-Options 头部 - // 可选值:DENY、SAMEORIGIN,防止页面被嵌入 iframe - XFrameOptions string `yaml:"x_frame_options"` - - // XContentTypeOptions X-Content-Type-Options 头部 - // 建议值:nosniff,防止浏览器 MIME 类型嗅探 - XContentTypeOptions string `yaml:"x_content_type_options"` - - // ContentSecurityPolicy Content-Security-Policy 头部 - // 控制页面可以加载的资源来源 - ContentSecurityPolicy string `yaml:"content_security_policy"` - - // ReferrerPolicy Referrer-Policy 头部 - // 控制 Referer 头的发送策略 - ReferrerPolicy string `yaml:"referrer_policy"` - - // PermissionsPolicy Permissions-Policy 头部 - // 控制浏览器功能权限(原 Feature-Policy) - PermissionsPolicy string `yaml:"permissions_policy"` -} - -// ErrorPageConfig 自定义错误页面配置。 -// -// 允许为特定 HTTP 状态码配置自定义错误页面。 -// 错误页面文件在启动时预加载到内存中,运行时不进行文件 I/O。 -// -// 注意事项: -// - 错误页面文件路径可以是相对路径或绝对路径 -// - 所有错误页面加载失败时会阻止服务器启动 -// - 部分错误页面加载失败会记录警告但允许启动 -// - 支持可选的响应状态码覆盖 -// -// 使用示例: -// -// error_page: -// pages: -// 404: "/var/www/errors/404.html" -// 500: "/var/www/errors/500.html" -// 503: "/var/www/errors/503.html" -// default: "/var/www/errors/error.html" -// response_code: 200 # 可选:覆盖响应状态码 -type ErrorPageConfig struct { - // Pages 状态码到错误页面文件的映射 - // key 为 HTTP 状态码(如 404, 500),value 为文件路径 - Pages map[int]string `yaml:"pages"` - - // Default 默认错误页面 - // 当特定状态码没有配置时使用 - Default string `yaml:"default"` - - // ResponseCode 响应状态码覆盖 - // 如果不为 0,所有错误页面响应将使用此状态码 - // 例如设置为 200 时,即使发生错误也返回 200 OK - ResponseCode int `yaml:"response_code"` -} - -// AuthRequestConfig 外部认证子请求配置。 -// -// 将认证委托给外部服务,根据子请求的响应状态码决定是否允许原请求继续。 -// 适用于需要复杂认证逻辑或与现有认证系统集成的场景。 -// -// 行为规则: -// - 2xx 响应:认证通过,原请求继续处理 -// - 401/403 响应:认证失败,返回相应状态码 -// - 其他响应或超时:返回 500 内部服务器错误 -// - 认证服务不可用时:返回 500 内部服务器错误 -// -// 注意事项: -// - 认证请求使用独立的连接池,避免影响主服务 -// - 支持变量展开(如 $host, $uri, $request_uri) -// - 建议配置合理的超时时间,避免长时间阻塞 -// - 认证请求会携带原请求的头信息(如 Cookie, Authorization) -// -// 使用示例: -// -// security: -// auth_request: -// uri: /auth -// method: GET -// auth_timeout: 5s -// headers: -// X-Original-Uri: $request_uri -// X-Original-Host: $host -type AuthRequestConfig struct { - Headers map[string]string `yaml:"headers"` - URI string `yaml:"uri"` - Method string `yaml:"method"` - ForwardHeaders []string `yaml:"forward_headers"` - Timeout time.Duration `yaml:"auth_timeout"` - Enabled bool `yaml:"enabled"` -} - -// RewriteRule URL 重写规则。 -// -// 用于在代理或静态文件服务前修改请求 URL。 -// -// 注意事项: -// - Pattern 为正则表达式,用于匹配原始 URL -// - Replacement 为替换后的目标 URL,支持捕获组 -// - Flag 控制重写行为:last、redirect、permanent、break -// - 规则按顺序执行,匹配后根据 Flag 决定是否继续 -// -// 使用示例: -// -// rewrite: -// - pattern: "^/old/(.*)$" -// replacement: "/new/$1" -// flag: "permanent" -// - pattern: "^/api/(.*)$" -// replacement: "/v1/$1" -// flag: "last" -type RewriteRule struct { - // Pattern 匹配模式 - // 正则表达式,用于匹配请求 URL - Pattern string `yaml:"pattern"` - - // Replacement 替换目标 - // 替换后的 URL 路径,支持 $1、$2 等捕获组引用 - Replacement string `yaml:"replacement"` - - // Flag 标志 - // 可选值: - // - last:停止后续规则匹配 - // - redirect:返回 302 临时重定向 - // - permanent:返回 301 永久重定向 - // - break:停止规则匹配但继续处理 - Flag string `yaml:"flag"` -} - -// CompressionConfig 响应压缩配置。 -// -// 配置响应内容压缩,减少传输数据量。 -// -// 注意事项: -// - Type 支持 gzip、brotli 或 both(同时使用两种) -// - Level 压缩级别 1-9,越高压缩率越好但 CPU 消耗越大 -// - MinSize 低于此大小的响应不压缩 -// - Types 指定哪些 MIME 类型进行压缩 -// - GzipStatic 启用后优先使用预压缩文件 -// -// 使用示例: -// -// compression: -// type: "gzip" -// level: 6 -// min_size: 1024 -// types: ["text/html", "text/css", "application/json"] -// gzip_static: true -// gzip_static_extensions: [".gz"] -type CompressionConfig struct { - Type string `yaml:"type"` - Types []string `yaml:"types"` - GzipStaticExtensions []string `yaml:"gzip_static_extensions"` - Level int `yaml:"level"` - MinSize int `yaml:"min_size"` - GzipStatic bool `yaml:"gzip_static"` -} - -// LoggingConfig 日志配置。 -// -// 配置访问日志和错误日志的输出行为。 -// -// 注意事项: -// - Format 控制日志格式:text 或 json -// - Access 配置访问日志(记录每个请求) -// - Error 配置错误日志(记录错误信息) -// - Path 为空时日志输出到标准输出/标准错误 -// -// 使用示例: -// -// logging: -// format: "json" -// access: -// path: "/var/log/lolly/access.log" -// format: "combined" -// error: -// path: "/var/log/lolly/error.log" -// level: "warn" -type LoggingConfig struct { - // Format 全局格式 - // 可选值:text(默认)、json - Format string `yaml:"format"` - - // Access 访问日志配置 - Access AccessLogConfig `yaml:"access"` - - // Error 错误日志配置 - Error ErrorLogConfig `yaml:"error"` -} - -// ShutdownConfig 服务器关闭配置。 -// -// 用于配置服务器在接收到不同信号时的关闭超时行为。 -// 优雅停止会等待正在处理的请求完成,快速停止会立即中断连接。 -// -// 注意事项: -// - graceful_timeout = 0 表示使用默认值(30s) -// - fast_timeout = 0 表示使用默认值(5s) -// - graceful_timeout 应显著大于 fast_timeout -// - 两个值都必须 >= 0,负数在验证时会报错 -// -// 使用示例: -// -// shutdown: -// graceful_timeout: 30s # SIGQUIT 优雅停止超时 -// fast_timeout: 5s # SIGINT/SIGTERM 快速停止超时 -type ShutdownConfig struct { - // GracefulTimeout 优雅停止超时(SIGQUIT) - // 接收到 SIGQUIT 信号后,等待活跃请求完成的最大时间 - // 默认: 30s(当值为 0 时使用默认值) - GracefulTimeout time.Duration `yaml:"graceful_timeout"` - - // FastTimeout 快速停止超时(SIGINT/SIGTERM) - // 接收到 SIGINT 或 SIGTERM 信号后,等待服务器关闭的最大时间 - // 默认: 5s(当值为 0 时使用默认值) - FastTimeout time.Duration `yaml:"fast_timeout"` -} - -// AccessLogConfig 访问日志配置。 -// -// 配置访问日志的输出位置和格式。 -// -// 注意事项: -// - Path 为日志文件路径,为空则输出到 stdout -// - Format 支持预设格式或自定义格式 -// - 常用预设格式:common、combined -// -// 使用示例: -// -// access: -// path: "/var/log/lolly/access.log" -// format: "combined" -type AccessLogConfig struct { - // Path 日志文件路径 - // 访问日志的输出文件,为空则输出到标准输出 - Path string `yaml:"path"` - - // Format 日志格式 - // 预设格式或自定义日志格式字符串 - Format string `yaml:"format"` -} - -// ErrorLogConfig 错误日志配置。 -// -// 配置错误日志的输出位置和级别。 -// -// 注意事项: -// - Path 为日志文件路径,为空则输出到 stderr -// - Level 控制记录的日志级别阈值 -// - 可选级别:debug、info、warn、error -// -// 使用示例: -// -// error: -// path: "/var/log/lolly/error.log" -// level: "error" -type ErrorLogConfig struct { - // Path 日志文件路径 - // 错误日志的输出文件,为空则输出到标准错误 - Path string `yaml:"path"` - - // Level 日志级别 - // 可选值:debug、info、warn、error - Level string `yaml:"level"` -} - -// PerformanceConfig 性能配置。 -// -// 配置服务器性能优化相关参数。 -// -// 注意事项: -// - GoroutinePool 复用 goroutine 减少创建开销 -// - FileCache 缓存静态文件内容提升响应速度 -// - Transport 配置代理连接的连接池参数 -// -// 使用示例: -// -// performance: -// goroutine_pool: -// enabled: true -// max_workers: 1000 -// file_cache: -// max_entries: 10000 -// max_size: 1073741824 -// transport: -// max_idle_conns: 100 -type PerformanceConfig struct { - // GoroutinePool Goroutine 池配置 - // 控制 worker goroutine 的复用行为 - GoroutinePool GoroutinePoolConfig `yaml:"goroutine_pool"` - - // FileCache 文件缓存配置 - // 缓存静态文件内容避免重复磁盘 IO - FileCache FileCacheConfig `yaml:"file_cache"` - - // Transport HTTP Transport 配置 - // 代理连接池的参数设置 - Transport TransportConfig `yaml:"transport"` -} - -// GoroutinePoolConfig Goroutine 池配置。 -// -// 复用 goroutine 减少创建和销毁开销。 -// -// 注意事项: -// - Enabled 为 true 时启用 goroutine 池 -// - MaxWorkers 限制最大并发 worker 数 -// - MinWorkers 预热 worker 数量 -// - IdleTimeout 空闲 worker 回收时间 -// -// 使用示例: -// -// goroutine_pool: -// enabled: true -// max_workers: 1000 -// min_workers: 100 -// idle_timeout: 60s -type GoroutinePoolConfig struct { - // Enabled 是否启用 - Enabled bool `yaml:"enabled"` - - // MaxWorkers 最大 worker 数 - // 限制同时运行的最大 goroutine 数量 - MaxWorkers int `yaml:"max_workers"` - - // MinWorkers 最小 worker 数(预热) - // 启动时预创建的 goroutine 数量 - MinWorkers int `yaml:"min_workers"` - - // IdleTimeout 空闲超时 - // 空闲 worker 超过此时间将被回收 - IdleTimeout time.Duration `yaml:"idle_timeout"` -} - -// FileCacheConfig 文件缓存配置。 -// -// 缓存静态文件内容减少磁盘 IO。 -// -// 注意事项: -// - MaxEntries 限制最大缓存文件数量 -// - MaxSize 限制缓存总内存使用量(字节) -// - Inactive 超过此时间未访问的文件将被淘汰 -// -// 使用示例: -// -// file_cache: -// max_entries: 10000 -// max_size: 1073741824 -// inactive: 60s -type FileCacheConfig struct { - // MaxEntries 最大缓存条目数 - // 缓存文件的最大数量限制 - MaxEntries int64 `yaml:"max_entries"` - - // MaxSize 内存上限(字节) - // 缓存占用的最大内存限制 - MaxSize int64 `yaml:"max_size"` - - // Inactive 未访问淘汰时间 - // 超过此时间未被访问的缓存将被清除 - Inactive time.Duration `yaml:"inactive"` -} - -// TransportConfig HTTP Transport 配置。 -// -// 配置代理后端连接的连接池参数。 -// -// 注意事项: -// - IdleConnTimeout 控制空闲连接的保持时间 -// - MaxConnsPerHost 限制每个后端主机的总连接数(含活跃和空闲) -// -// 使用示例: -// -// transport: -// idle_conn_timeout: 90s -// max_conns_per_host: 100 -type TransportConfig struct { - // IdleConnTimeout 空闲连接超时 - // 空闲连接的最大存活时间 - IdleConnTimeout time.Duration `yaml:"idle_conn_timeout"` - - // MaxConnsPerHost 每主机最大连接数 - // 单个后端主机的总连接数上限(包括活跃连接和空闲连接) - MaxConnsPerHost int `yaml:"max_conns_per_host"` -} - -// MonitoringConfig 监控配置。 -// -// 配置服务状态监控和健康检查端点。 -// -// 注意事项: -// - Status 配置状态检查端点 -// - 监控端点建议限制访问 IP 防止信息泄露 -// -// 使用示例: -// -// monitoring: -// status: -// path: "/status" -// allow: ["127.0.0.1", "10.0.0.0/8"] -type MonitoringConfig struct { - // Status 状态端点配置 - // 服务健康状态检查端点 - Status StatusConfig `yaml:"status"` - - // Pprof pprof 性能分析端点配置 - // 用于收集 CPU、内存等性能数据,支持 PGO 优化 - Pprof PprofConfig `yaml:"pprof"` -} - -// PprofConfig pprof 性能分析端点配置。 -// -// 配置 pprof 端点用于收集运行时性能数据。 -// 收集的 profile 可用于 PGO (Profile-Guided Optimization) 构建。 -// -// 注意事项: -// - 生产环境仅在收集 profile 时启用,完成后关闭 -// - 建议严格限制访问 IP,防止性能数据泄露 -// - CPU profile 收集需要代表性 workload -// -// 使用示例: -// -// pprof: -// enabled: true -// path: "/debug/pprof" -// allow: ["127.0.0.1"] -type PprofConfig struct { - Path string `yaml:"path"` - Allow []string `yaml:"allow"` - Enabled bool `yaml:"enabled"` -} - -// StatusConfig 状态监控端点配置。 -// -// 配置服务状态检查端点的路径和访问控制。 -// -// 注意事项: -// - Enabled 默认为 false,需显式启用 -// - Path 为状态端点的 URL 路径 -// - Format 支持 json、text、html、prometheus 格式 -// - Allow 限制可访问的 IP 地址列表 -// - 生产环境建议严格限制访问来源 -// -// 使用示例: -// -// status: -// enabled: true -// path: "/_status" -// format: "json" -// allow: ["127.0.0.1", "192.168.0.0/16"] -type StatusConfig struct { - Path string `yaml:"path"` - Format string `yaml:"format"` - Allow []string `yaml:"allow"` - Enabled bool `yaml:"enabled"` -} - -// CacheAPIConfig 缓存 API 配置。 -// -// 配置缓存清理 API 端点,支持主动清理代理缓存。 -// -// 注意事项: -// - Enabled 默认为 false,需显式启用 -// - Allow 限制可访问的 IP 地址列表 -// - Auth 配置认证方式,推荐使用 token 认证 -// -// 使用示例: -// -// cache_api: -// enabled: true -// path: "/_cache/purge" -// allow: ["127.0.0.1", "10.0.0.0/8"] -// auth: -// type: "token" -// token: "${CACHE_API_TOKEN}" -type CacheAPIConfig struct { - Auth CacheAPIAuthConfig `yaml:"auth"` - Path string `yaml:"path"` - Allow []string `yaml:"allow"` - Enabled bool `yaml:"enabled"` -} - -// CacheAPIAuthConfig 缓存 API 认证配置。 -type CacheAPIAuthConfig struct { - // Type 认证类型 - // 支持 "none" 和 "token" 两种类型 - // 默认为 "none" - Type string `yaml:"type"` - - // Token 认证令牌 - // 当 Type 为 "token" 时使用 - // 支持环境变量替换,如 "${CACHE_API_TOKEN}" - Token string `yaml:"token"` -} - -// LuaMiddlewareConfig Lua 中间件配置(配置文件格式) -// -// 用于配置 Lua 中间件的行为,包括脚本路径、执行阶段和全局设置。 -// -// 注意事项: -// - Enabled 为 true 时启用 Lua 中间件 -// - Scripts 配置要执行的脚本列表 -// - GlobalSettings 控制 Lua 引擎的全局行为 -// -// 使用示例: -// -// lua: -// enabled: true -// scripts: -// - path: "/scripts/auth.lua" -// phase: "access" -// timeout: 10s -// global_settings: -// max_concurrent_coroutines: 1000 -// coroutine_timeout: 30s -type LuaMiddlewareConfig struct { - Scripts []LuaScriptConfig `yaml:"scripts"` - GlobalSettings LuaGlobalSettings `yaml:"global_settings"` - Enabled bool `yaml:"enabled"` -} - -// LuaScriptConfig 单个脚本配置 -// -// 定义单个 Lua 脚本的执行参数。 -// -// 注意事项: -// - Path 为脚本文件路径,必需字段 -// - Phase 为执行阶段,必需字段 -// - Timeout 控制脚本执行超时 -// -// 使用示例: -// -// scripts: -// - path: "/scripts/auth.lua" -// phase: "access" -// timeout: 10s -// enabled: true -type LuaScriptConfig struct { - // Path 脚本路径 - Path string `yaml:"path"` - - // Phase 执行阶段 - // 可选值:rewrite、access、content、log、header_filter、body_filter - Phase string `yaml:"phase"` - - // Timeout 执行超时 - Timeout time.Duration `yaml:"timeout"` - - // Enabled 是否启用此脚本(默认 true) - Enabled bool `yaml:"enabled"` -} - -// LuaGlobalSettings 全局 Lua 设置 -// -// 控制 Lua 引擎的全局行为。 -// -// 注意事项: -// - MaxConcurrentCoroutines 控制最大并发协程数 -// - CoroutineTimeout 控制协程执行超时 -// - CodeCacheSize 控制字节码缓存大小 -// - CoroutineStackSize 控制协程栈大小(默认64) -// - MinimizeStackMemory 启用栈内存自动收缩 -// - CoroutinePoolWarmup 协程池预热数量 -// -// 使用示例: -// -// global_settings: -// max_concurrent_coroutines: 1000 -// coroutine_timeout: 30s -// code_cache_size: 1000 -// enable_file_watch: true -// max_execution_time: 30s -// coroutine_stack_size: 64 -// minimize_stack_memory: true -// coroutine_pool_warmup: 4 -type LuaGlobalSettings struct { - // MaxConcurrentCoroutines 最大并发协程数 - MaxConcurrentCoroutines int `yaml:"max_concurrent_coroutines"` - - // CoroutineTimeout 协程执行超时 - CoroutineTimeout time.Duration `yaml:"coroutine_timeout"` - - // CodeCacheSize 字节码缓存条目数 - CodeCacheSize int `yaml:"code_cache_size"` - - // MaxExecutionTime 单脚本最大执行时间 - MaxExecutionTime time.Duration `yaml:"max_execution_time"` - - // CoroutineStackSize 协程栈大小(默认64,最大256) - // 较小的栈减少内存分配,适用于简单脚本 - CoroutineStackSize int `yaml:"coroutine_stack_size"` - - // CoroutinePoolWarmup 协程池预热数量,启动时预创建 - CoroutinePoolWarmup int `yaml:"coroutine_pool_warmup"` - - // EnableFileWatch 启用文件变更检测 - EnableFileWatch bool `yaml:"enable_file_watch"` - - // MinimizeStackMemory 启用栈内存自动收缩以减少内存占用 - MinimizeStackMemory bool `yaml:"minimize_stack_memory"` -} - -// StreamConfig TCP/UDP Stream 代理配置。 -// -// 用于四层网络代理,如数据库、Redis 等 TCP/UDP 服务。 -// -// 注意事项: -// - Listen 配置监听地址 -// - Protocol 支持 tcp 或 udp -// - Upstream 配置后端目标列表 -// - Stream 代理工作在传输层,不解析应用层协议 -// -// 使用示例: -// -// stream: -// - listen: ":3306" -// protocol: "tcp" -// upstream: -// targets: -// - addr: "mysql1:3306" -// weight: 3 -// - addr: "mysql2:3306" -// weight: 1 -// load_balance: "round_robin" -type StreamConfig struct { - Listen string `yaml:"listen"` - Protocol string `yaml:"protocol"` - Upstream StreamUpstream `yaml:"upstream"` - ProxySSL StreamProxySSLConfig `yaml:"proxy_ssl"` - SSL StreamSSLConfig `yaml:"ssl"` -} - -// StreamUpstream Stream 上游配置。 -// -// 配置 Stream 代理的后端服务器列表。 -// -// 注意事项: -// - Targets 配置后端服务器地址 -// - LoadBalance 配置负载均衡算法 -// -// 使用示例: -// -// upstream: -// targets: -// - addr: "backend1:3306" -// weight: 3 -// load_balance: "round_robin" -type StreamUpstream struct { - LoadBalance string `yaml:"load_balance"` - Targets []StreamTarget `yaml:"targets"` -} - -// StreamTarget Stream 目标配置。 -// -// 定义单个 Stream 后端服务器。 -// -// 注意事项: -// - Addr 为后端服务器地址 -// - Weight 在加权轮询算法下生效 -// -// 使用示例: -// -// targets: -// - addr: "mysql1:3306" -// weight: 3 -// - addr: "mysql2:3306" -// weight: 1 -type StreamTarget struct { - // Addr 目标地址 - // 后端服务器地址,如 "host:port" - Addr string `yaml:"addr"` - - // Weight 权重 - // 用于加权轮询负载均衡 - Weight int `yaml:"weight"` -} - -// StreamSSLConfig Stream SSL 服务端配置。 -// -// 配置 Stream 模块的 TLS 终端功能,用于加密 TCP 流量。 -// -// 注意事项: -// - 仅对 TCP 协议有效,UDP 不支持 TLS -// - 证书文件需要 PEM 格式 -// - 支持配置客户端证书验证(mTLS) -// -// 使用示例: -// -// stream: -// - listen: ":3306" -// protocol: "tcp" -// ssl: -// enabled: true -// cert: "/etc/ssl/server.crt" -// key: "/etc/ssl/server.key" -// upstream: -// targets: -// - addr: "mysql:3306" -type StreamSSLConfig struct { - Cert string `yaml:"cert"` - Key string `yaml:"key"` - ClientCA string `yaml:"client_ca"` - Protocols []string `yaml:"protocols"` - Ciphers []string `yaml:"ciphers"` - VerifyDepth int `yaml:"verify_depth"` - Enabled bool `yaml:"enabled"` -} - -// StreamProxySSLConfig Stream 上游 SSL 配置。 -// -// 配置到上游服务器的 TLS 连接,用于加密代理到后端的流量。 -// -// 注意事项: -// - 启用后,代理将使用 TLS 连接到上游 -// - 支持客户端证书(mTLS)和服务器证书验证 -// - ServerName 用于 SNI 和证书验证 -// -// 使用示例: -// -// stream: -// - listen: ":3306" -// protocol: "tcp" -// proxy_ssl: -// enabled: true -// verify: true -// trusted_ca: "/etc/ssl/ca.crt" -// server_name: "mysql.internal" -// upstream: -// targets: -// - addr: "mysql:3306" -type StreamProxySSLConfig struct { - TrustedCA string `yaml:"trusted_ca"` - ServerName string `yaml:"server_name"` - Cert string `yaml:"cert"` - Key string `yaml:"key"` - Protocols []string `yaml:"protocols"` - Enabled bool `yaml:"enabled"` - Verify bool `yaml:"verify"` - SessionReuse bool `yaml:"session_reuse"` -} - -// TypesConfig MIME 类型配置 -// -// 用于配置静态文件的 MIME 类型映射。 -// -// 注意事项: -// - DefaultType 为默认 MIME 类型 -// - Map 为扩展名到 MIME 类型的映射 -// -// 使用示例: -// -// types: -// default_type: "application/octet-stream" -// map: -// ".html": "text/html" -// ".css": "text/css" -// ".js": "application/javascript" -type TypesConfig struct { - // DefaultType 默认 MIME 类型 - // 当无法识别文件扩展名时使用 - DefaultType string `yaml:"default_type"` - - // Map 扩展名到 MIME 类型的映射 - // 键为文件扩展名(如 ".html"),值为 MIME 类型 - Map map[string]string `yaml:"map"` -} - -// UnixSocketConfig Unix socket 特定配置。 -// -// 用于配置服务器监听 Unix domain socket 时的文件权限和所有权。 -// -// 注意事项: -// - Mode 为 socket 文件权限,默认 0666 -// - User 为 socket 文件所有者用户名 -// - Group 为 socket 文件所属用户组 -// -// 使用示例: -// -// unix_socket: -// mode: 0660 -// user: "www-data" -// group: "www-data" -type UnixSocketConfig struct { - // Mode 文件权限 - // Unix socket 文件的访问权限,默认 0666 - Mode int `yaml:"mode"` - - // User 文件所有者 - // Unix socket 文件的所有者用户名 - User string `yaml:"user"` - - // Group 文件组 - // Unix socket 文件的所属用户组 - Group string `yaml:"group"` -} - // Load 从文件加载配置。 // // 读取指定路径的 YAML 配置文件,解析并验证配置内容。 @@ -2316,77 +356,3 @@ func validateShutdown(cfg *ShutdownConfig) error { // 0 值表示使用默认值,在应用层处理 return nil } - -// ResolverConfig DNS 解析器配置。 -// -// 配置 DNS 解析器的行为,包括服务器地址、缓存 TTL、超时等。 -// 启用后可实现动态 DNS 解析和缓存,支持后端域名的动态解析。 -// -// 注意事项: -// - Enabled 为 true 时启用 DNS 解析器 -// - Addresses 配置 DNS 服务器地址,如 "8.8.8.8:53" -// - Valid 为缓存有效期(TTL),建议 30s-300s -// - Timeout 为单次查询超时时间 -// -// 使用示例: -// -// resolver: -// enabled: true -// addresses: -// - "8.8.8.8:53" -// - "8.8.4.4:53" -// valid: 30s -// timeout: 5s -// ipv4: true -// ipv6: false -// cache_size: 1024 -type ResolverConfig struct { - Addresses []string `yaml:"addresses"` - Valid time.Duration `yaml:"valid"` - Timeout time.Duration `yaml:"timeout"` - CacheSize int `yaml:"cache_size"` - Enabled bool `yaml:"enabled"` - IPv4 bool `yaml:"ipv4"` - IPv6 bool `yaml:"ipv6"` -} - -// TTL 返回缓存有效期(Valid 的别名,便于代码理解)。 -func (c *ResolverConfig) TTL() time.Duration { - return c.Valid -} - -// Validate 验证 Resolver 配置。 -// -// 检查 DNS 服务器地址格式、TTL 和超时设置的有效性。 -// -// 返回值: -// - error: 验证失败时的错误信息 -func (c *ResolverConfig) Validate() error { - if !c.Enabled { - return nil - } - - if len(c.Addresses) == 0 { - return errors.New("resolver.addresses is required when enabled") - } - - for _, addr := range c.Addresses { - if _, err := net.ResolveUDPAddr("udp", addr); err != nil { - return fmt.Errorf("invalid DNS address %s: %w", addr, err) - } - } - - if c.Valid > 0 && c.Valid < time.Second { - return errors.New("resolver.valid must be at least 1s") - } - - if c.Timeout > 0 && c.Timeout < time.Second { - return errors.New("resolver.timeout must be at least 1s") - } - - if !c.IPv4 && !c.IPv6 { - return errors.New("at least one of ipv4 or ipv6 must be enabled") - } - - return nil -} diff --git a/internal/config/monitoring_config.go b/internal/config/monitoring_config.go new file mode 100644 index 0000000..1aa16fc --- /dev/null +++ b/internal/config/monitoring_config.go @@ -0,0 +1,190 @@ +package config + +// MonitoringConfig 监控配置。 +// +// 配置服务状态监控和健康检查端点。 +// +// 注意事项: +// - Status 配置状态检查端点 +// - 监控端点建议限制访问 IP 防止信息泄露 +// +// 使用示例: +// +// monitoring: +// status: +// path: "/status" +// allow: ["127.0.0.1", "10.0.0.0/8"] +type MonitoringConfig struct { + // Status 状态端点配置 + // 服务健康状态检查端点 + Status StatusConfig `yaml:"status"` + + // Pprof pprof 性能分析端点配置 + // 用于收集 CPU、内存等性能数据,支持 PGO 优化 + Pprof PprofConfig `yaml:"pprof"` +} + +// PprofConfig pprof 性能分析端点配置。 +// +// 配置 pprof 端点用于收集运行时性能数据。 +// 收集的 profile 可用于 PGO (Profile-Guided Optimization) 构建。 +// +// 注意事项: +// - 生产环境仅在收集 profile 时启用,完成后关闭 +// - 建议严格限制访问 IP,防止性能数据泄露 +// - CPU profile 收集需要代表性 workload +// +// 使用示例: +// +// pprof: +// enabled: true +// path: "/debug/pprof" +// allow: ["127.0.0.1"] +type PprofConfig struct { + Path string `yaml:"path"` + Allow []string `yaml:"allow"` + Enabled bool `yaml:"enabled"` +} + +// StatusConfig 状态监控端点配置。 +// +// 配置服务状态检查端点的路径和访问控制。 +// +// 注意事项: +// - Enabled 默认为 false,需显式启用 +// - Path 为状态端点的 URL 路径 +// - Format 支持 json、text、html、prometheus 格式 +// - Allow 限制可访问的 IP 地址列表 +// - 生产环境建议严格限制访问来源 +// +// 使用示例: +// +// status: +// enabled: true +// path: "/_status" +// format: "json" +// allow: ["127.0.0.1", "192.168.0.0/16"] +type StatusConfig struct { + Path string `yaml:"path"` + Format string `yaml:"format"` + Allow []string `yaml:"allow"` + Enabled bool `yaml:"enabled"` +} + +// CacheAPIConfig 缓存 API 配置。 +// +// 配置缓存清理 API 端点,支持主动清理代理缓存。 +// +// 注意事项: +// - Enabled 默认为 false,需显式启用 +// - Allow 限制可访问的 IP 地址列表 +// - Auth 配置认证方式,推荐使用 token 认证 +// +// 使用示例: +// +// cache_api: +// enabled: true +// path: "/_cache/purge" +// allow: ["127.0.0.1", "10.0.0.0/8"] +// auth: +// type: "token" +// token: "${CACHE_API_TOKEN}" +type CacheAPIConfig struct { + Auth CacheAPIAuthConfig `yaml:"auth"` + Path string `yaml:"path"` + Allow []string `yaml:"allow"` + Enabled bool `yaml:"enabled"` +} + +// CacheAPIAuthConfig 缓存 API 认证配置。 +type CacheAPIAuthConfig struct { + // Type 认证类型 + // 支持 "none" 和 "token" 两种类型 + // 默认为 "none" + Type string `yaml:"type"` + + // Token 认证令牌 + // 当 Type 为 "token" 时使用 + // 支持环境变量替换,如 "${CACHE_API_TOKEN}" + Token string `yaml:"token"` +} + +// LoggingConfig 日志配置。 +// +// 配置访问日志和错误日志的输出行为。 +// +// 注意事项: +// - Format 控制日志格式:text 或 json +// - Access 配置访问日志(记录每个请求) +// - Error 配置错误日志(记录错误信息) +// - Path 为空时日志输出到标准输出/标准错误 +// +// 使用示例: +// +// logging: +// format: "json" +// access: +// path: "/var/log/lolly/access.log" +// format: "combined" +// error: +// path: "/var/log/lolly/error.log" +// level: "warn" +type LoggingConfig struct { + // Format 全局格式 + // 可选值:text(默认)、json + Format string `yaml:"format"` + + // Access 访问日志配置 + Access AccessLogConfig `yaml:"access"` + + // Error 错误日志配置 + Error ErrorLogConfig `yaml:"error"` +} + +// AccessLogConfig 访问日志配置。 +// +// 配置访问日志的输出位置和格式。 +// +// 注意事项: +// - Path 为日志文件路径,为空则输出到 stdout +// - Format 支持预设格式或自定义格式 +// - 常用预设格式:common、combined +// +// 使用示例: +// +// access: +// path: "/var/log/lolly/access.log" +// format: "combined" +type AccessLogConfig struct { + // Path 日志文件路径 + // 访问日志的输出文件,为空则输出到标准输出 + Path string `yaml:"path"` + + // Format 日志格式 + // 预设格式或自定义日志格式字符串 + Format string `yaml:"format"` +} + +// ErrorLogConfig 错误日志配置。 +// +// 配置错误日志的输出位置和级别。 +// +// 注意事项: +// - Path 为日志文件路径,为空则输出到 stderr +// - Level 控制记录的日志级别阈值 +// - 可选级别:debug、info、warn、error +// +// 使用示例: +// +// error: +// path: "/var/log/lolly/error.log" +// level: "error" +type ErrorLogConfig struct { + // Path 日志文件路径 + // 错误日志的输出文件,为空则输出到标准错误 + Path string `yaml:"path"` + + // Level 日志级别 + // 可选值:debug、info、warn、error + Level string `yaml:"level"` +} diff --git a/internal/config/performance_config.go b/internal/config/performance_config.go new file mode 100644 index 0000000..a644264 --- /dev/null +++ b/internal/config/performance_config.go @@ -0,0 +1,258 @@ +package config + +import ( + "errors" + "fmt" + "net" + "time" +) + +// HTTP2Config HTTP/2 配置。 +// +// HTTP/2 提供多路复用、头部压缩和服务器推送等功能, +// 需要服务器配置 SSL/TLS 证书才能正常工作。 +// +// 注意事项: +// - 必须配置有效的 SSL 证书(TLS 1.2 或更高版本) +// - http2.enabled 仅在配置了 SSL/TLS 时生效 +// - 客户端可以通过 ALPN 协商使用 HTTP/2 或 HTTP/1.1 +// +// 使用示例: +// +// server: +// ssl: +// cert: "/etc/ssl/server.crt" +// key: "/etc/ssl/server.key" +// http2: +// enabled: true +// max_concurrent_streams: 128 +// max_header_list_size: "16KB" +type HTTP2Config struct { + MaxConcurrentStreams int `yaml:"max_concurrent_streams"` + MaxHeaderListSize int `yaml:"max_header_list_size"` + IdleTimeout time.Duration `yaml:"idle_timeout"` + Enabled bool `yaml:"enabled"` + PushEnabled bool `yaml:"push_enabled"` + H2CEnabled bool `yaml:"h2c_enabled"` + GracefulShutdownTimeout time.Duration `yaml:"graceful_shutdown_timeout"` +} + +// HTTP3Config HTTP/3 (QUIC) 配置。 +// +// HTTP/3 基于 QUIC 协议,提供更快的连接建立和更低的延迟。 +// 需要服务器配置 SSL/TLS 证书才能正常工作。 +// +// 注意事项: +// - 必须配置有效的 SSL 证书 +// - UDP 监听地址不能与 HTTP/1.1 或 HTTP/2 冲突 +// - 0-RTT 特性可能带来重放攻击风险,需评估安全性 +// - 部分网络环境可能限制 UDP 流量 +// +// 使用示例: +// +// http3: +// enabled: true +// listen: ":443" +// max_streams: 1000 +// idle_timeout: 30s +// enable_0rtt: true +type HTTP3Config struct { + Listen string `yaml:"listen"` + MaxStreams int `yaml:"max_streams"` + IdleTimeout time.Duration `yaml:"idle_timeout"` + Enabled bool `yaml:"enabled"` + Enable0RTT bool `yaml:"enable_0rtt"` +} + +// PerformanceConfig 性能配置。 +// +// 配置服务器性能优化相关参数。 +// +// 注意事项: +// - GoroutinePool 复用 goroutine 减少创建开销 +// - FileCache 缓存静态文件内容提升响应速度 +// - Transport 配置代理连接的连接池参数 +// +// 使用示例: +// +// performance: +// goroutine_pool: +// enabled: true +// max_workers: 1000 +// file_cache: +// max_entries: 10000 +// max_size: 1073741824 +// transport: +// max_idle_conns: 100 +type PerformanceConfig struct { + // GoroutinePool Goroutine 池配置 + // 控制 worker goroutine 的复用行为 + GoroutinePool GoroutinePoolConfig `yaml:"goroutine_pool"` + + // FileCache 文件缓存配置 + // 缓存静态文件内容避免重复磁盘 IO + FileCache FileCacheConfig `yaml:"file_cache"` + + // Transport HTTP Transport 配置 + // 代理连接池的参数设置 + Transport TransportConfig `yaml:"transport"` +} + +// GoroutinePoolConfig Goroutine 池配置。 +// +// 复用 goroutine 减少创建和销毁开销。 +// +// 注意事项: +// - Enabled 为 true 时启用 goroutine 池 +// - MaxWorkers 限制最大并发 worker 数 +// - MinWorkers 预热 worker 数量 +// - IdleTimeout 空闲 worker 回收时间 +// +// 使用示例: +// +// goroutine_pool: +// enabled: true +// max_workers: 1000 +// min_workers: 100 +// idle_timeout: 60s +type GoroutinePoolConfig struct { + // Enabled 是否启用 + Enabled bool `yaml:"enabled"` + + // MaxWorkers 最大 worker 数 + // 限制同时运行的最大 goroutine 数量 + MaxWorkers int `yaml:"max_workers"` + + // MinWorkers 最小 worker 数(预热) + // 启动时预创建的 goroutine 数量 + MinWorkers int `yaml:"min_workers"` + + // IdleTimeout 空闲超时 + // 空闲 worker 超过此时间将被回收 + IdleTimeout time.Duration `yaml:"idle_timeout"` +} + +// TransportConfig HTTP Transport 配置。 +// +// 配置代理后端连接的连接池参数。 +// +// 注意事项: +// - IdleConnTimeout 控制空闲连接的保持时间 +// - MaxConnsPerHost 限制每个后端主机的总连接数(含活跃和空闲) +// +// 使用示例: +// +// transport: +// idle_conn_timeout: 90s +// max_conns_per_host: 100 +type TransportConfig struct { + // IdleConnTimeout 空闲连接超时 + // 空闲连接的最大存活时间 + IdleConnTimeout time.Duration `yaml:"idle_conn_timeout"` + + // MaxConnsPerHost 每主机最大连接数 + // 单个后端主机的总连接数上限(包括活跃连接和空闲连接) + MaxConnsPerHost int `yaml:"max_conns_per_host"` +} + +// ShutdownConfig 服务器关闭配置。 +// +// 用于配置服务器在接收到不同信号时的关闭超时行为。 +// 优雅停止会等待正在处理的请求完成,快速停止会立即中断连接。 +// +// 注意事项: +// - graceful_timeout = 0 表示使用默认值(30s) +// - fast_timeout = 0 表示使用默认值(5s) +// - graceful_timeout 应显著大于 fast_timeout +// - 两个值都必须 >= 0,负数在验证时会报错 +// +// 使用示例: +// +// shutdown: +// graceful_timeout: 30s # SIGQUIT 优雅停止超时 +// fast_timeout: 5s # SIGINT/SIGTERM 快速停止超时 +type ShutdownConfig struct { + // GracefulTimeout 优雅停止超时(SIGQUIT) + // 接收到 SIGQUIT 信号后,等待活跃请求完成的最大时间 + // 默认: 30s(当值为 0 时使用默认值) + GracefulTimeout time.Duration `yaml:"graceful_timeout"` + + // FastTimeout 快速停止超时(SIGINT/SIGTERM) + // 接收到 SIGINT 或 SIGTERM 信号后,等待服务器关闭的最大时间 + // 默认: 5s(当值为 0 时使用默认值) + FastTimeout time.Duration `yaml:"fast_timeout"` +} + +// ResolverConfig DNS 解析器配置。 +// +// 配置 DNS 解析器的行为,包括服务器地址、缓存 TTL、超时等。 +// 启用后可实现动态 DNS 解析和缓存,支持后端域名的动态解析。 +// +// 注意事项: +// - Enabled 为 true 时启用 DNS 解析器 +// - Addresses 配置 DNS 服务器地址,如 "8.8.8.8:53" +// - Valid 为缓存有效期(TTL),建议 30s-300s +// - Timeout 为单次查询超时时间 +// +// 使用示例: +// +// resolver: +// enabled: true +// addresses: +// - "8.8.8.8:53" +// - "8.8.4.4:53" +// valid: 30s +// timeout: 5s +// ipv4: true +// ipv6: false +// cache_size: 1024 +type ResolverConfig struct { + Addresses []string `yaml:"addresses"` + Valid time.Duration `yaml:"valid"` + Timeout time.Duration `yaml:"timeout"` + CacheSize int `yaml:"cache_size"` + Enabled bool `yaml:"enabled"` + IPv4 bool `yaml:"ipv4"` + IPv6 bool `yaml:"ipv6"` +} + +// TTL 返回缓存有效期(Valid 的别名,便于代码理解)。 +func (c *ResolverConfig) TTL() time.Duration { + return c.Valid +} + +// Validate 验证 Resolver 配置。 +// +// 检查 DNS 服务器地址格式、TTL 和超时设置的有效性。 +// +// 返回值: +// - error: 验证失败时的错误信息 +func (c *ResolverConfig) Validate() error { + if !c.Enabled { + return nil + } + + if len(c.Addresses) == 0 { + return errors.New("resolver.addresses is required when enabled") + } + + for _, addr := range c.Addresses { + if _, err := net.ResolveUDPAddr("udp", addr); err != nil { + return fmt.Errorf("invalid DNS address %s: %w", addr, err) + } + } + + if c.Valid > 0 && c.Valid < time.Second { + return errors.New("resolver.valid must be at least 1s") + } + + if c.Timeout > 0 && c.Timeout < time.Second { + return errors.New("resolver.timeout must be at least 1s") + } + + if !c.IPv4 && !c.IPv6 { + return errors.New("at least one of ipv4 or ipv6 must be enabled") + } + + return nil +} diff --git a/internal/config/proxy_config.go b/internal/config/proxy_config.go new file mode 100644 index 0000000..fdda253 --- /dev/null +++ b/internal/config/proxy_config.go @@ -0,0 +1,438 @@ +package config + +import ( + "strconv" + "strings" + "time" +) + +// ProxyConfig 反向代理配置,支持负载均衡和健康检查。 +// +// 用于将请求转发到后端服务器,支持多种负载均衡算法 +// 和健康检查机制。 +// +// 注意事项: +// - Path 使用前缀匹配,较长路径优先匹配 +// - 至少配置一个 Target 才能正常工作 +// - 负载均衡算法支持:round_robin、weighted_round_robin、least_conn、ip_hash、consistent_hash、random +// - 一致性哈希需要配置 HashKey +// +// 使用示例: +// +// proxy: +// - path: "/api/" +// targets: +// - url: "http://backend1:8080" +// weight: 3 +// - url: "http://backend2:8080" +// weight: 1 +// load_balance: "weighted_round_robin" +// health_check: +// interval: 10s +// path: "/health" +type ProxyConfig struct { + // 指针类型字段(按大小排列) + RedirectRewrite *RedirectRewriteConfig `yaml:"redirect_rewrite"` + ProxySSL *ProxySSLConfig `yaml:"proxy_ssl"` + CacheValid *ProxyCacheValidConfig `yaml:"cache_valid"` + Buffering *ProxyBufferingConfig `yaml:"buffering"` + // 切片字段 + Targets []ProxyTarget `yaml:"targets"` + // 字符串字段 + Path string `yaml:"path"` + LoadBalance string `yaml:"load_balance"` + HashKey string `yaml:"hash_key"` + ClientMaxBodySize string `yaml:"client_max_body_size"` + ProxyBind string `yaml:"proxy_bind"` + // 结构体字段 + Headers ProxyHeaders `yaml:"headers"` + BalancerByLua BalancerByLuaConfig `yaml:"balancer_by_lua"` + HealthCheck HealthCheckConfig `yaml:"health_check"` + NextUpstream NextUpstreamConfig `yaml:"next_upstream"` + Cache ProxyCacheConfig `yaml:"cache"` + Timeout ProxyTimeout `yaml:"timeout"` + // 基本类型字段 + VirtualNodes int `yaml:"virtual_nodes"` + + // LocationType 位置匹配类型 + // 可选值:exact、prefix_priority、regex、regex_caseless、prefix、named + LocationType string `yaml:"location_type"` + + // LocationName 位置名称 + // 仅当 LocationType 为 named 时使用,用于命名位置块 + LocationName string `yaml:"location_name"` + + // Internal 仅允许内部访问 + // 设置为 true 时,该位置仅允许内部重定向访问 + Internal bool `yaml:"internal"` +} + +// ProxyBufferingConfig 代理缓冲配置。 +// +// 控制代理响应的缓冲行为: +// - "default" 或 "on": 缓冲响应到内存/临时文件 +// - "off": 流式转发响应,不缓冲 +// +// 使用示例: +// +// buffering: +// mode: "off" +type ProxyBufferingConfig struct { + // Mode 缓冲模式 + // 可选值:"default"(默认缓冲), "on"(强制缓冲), "off"(关闭缓冲) + Mode string `yaml:"mode"` + + // BufferSize 响应缓冲区大小(字节) + // 0 表示使用默认值 + BufferSize int `yaml:"buffer_size"` + + // Buffers 多缓冲区配置字符串 + // 格式:"数量 大小" 或 "数量1 大小1 数量2 大小2 ..." + // 例如:"8 16k" 表示 8 个 16KB 缓冲区 + // 例如:"4 4k 8 16k" 表示 4 个 4KB + 8 个 16KB 缓冲区 + Buffers string `yaml:"buffers"` + + // BufferCount 缓冲区数量(解析后) + BufferCount int `yaml:"-"` + + // BufferSizeEach 每个缓冲区大小(字节,解析后) + BufferSizeEach int `yaml:"-"` +} + +// ParseBuffers 解析 Buffers 配置字符串。 +// +// 支持格式: +// - "8 16k" → 8 个 16KB 缓冲区 +// - "4 4k" → 4 个 4KB 缓冲区 +// +// 大小单位: +// - k 或 K: KB (1024 字节) +// - m 或 M: MB (1024 * 1024 字节) +// - 无单位: 字节 +func (c *ProxyBufferingConfig) ParseBuffers() { + if c.Buffers == "" { + // 向后兼容:使用 BufferSize + if c.BufferSize > 0 { + c.BufferCount = 1 + c.BufferSizeEach = c.BufferSize + } + return + } + + parts := strings.Fields(c.Buffers) + if len(parts) < 2 { + return // 无效格式 + } + + count, err := strconv.Atoi(parts[0]) + if err != nil || count <= 0 { + return // 无效数量 + } + + sizeEach, err := parseSize(parts[1]) + if err != nil || sizeEach <= 0 { + return // 无效大小 + } + + c.BufferCount = count + c.BufferSizeEach = sizeEach +} + +// BalancerByLuaConfig Lua 负载均衡配置 +// +// 使用 Lua 脚本动态选择后端目标,支持自定义负载均衡逻辑。 +// +// 注意事项: +// - Script 为 Lua 脚本文件路径 +// - Timeout 控制脚本执行超时 +// - Fallback 指定 Lua 失败时的备用算法 +// +// 使用示例: +// +// balancer_by_lua: +// enabled: true +// script: "/etc/lolly/scripts/balancer.lua" +// timeout: 100ms +// fallback: "round_robin" +type BalancerByLuaConfig struct { + // Script Lua 脚本路径 + Script string `yaml:"script"` + + // Fallback 失败时使用的默认负载均衡算法 + // 默认值: "round_robin" + Fallback string `yaml:"fallback"` + + // Timeout 执行超时 + // 默认值: 100ms + Timeout time.Duration `yaml:"timeout"` + + // Enabled 是否启用 + Enabled bool `yaml:"enabled"` +} + +// ProxyTarget 后端目标配置。 +// +// 定义单个后端服务器的地址和权重。 +// +// 注意事项: +// - URL 必须包含协议(http:// 或 https://) +// - Weight 仅在 weighted_round_robin 算法下生效 +// +// 使用示例: +// +// targets: +// - url: "http://backend1:8080" +// weight: 3 +// - url: "http://backend2:8080" +// weight: 1 +type ProxyTarget struct { + // URL 后端地址 + // 格式:"http://host:port" 或 "https://host:port" + URL string `yaml:"url"` + + // Weight 权重 + // 用于加权轮询算法,值越大分配的请求越多 + Weight int `yaml:"weight"` + + // MaxConns 最大并发连接数 + // 0 表示不限制 + MaxConns int `yaml:"max_conns"` + + // MaxFails 最大失败次数 + // 在 FailTimeout 期间失败次数达到此值后标记为不可用 + // 0 表示不进行被动失败检测 + MaxFails int `yaml:"max_fails"` + + // FailTimeout 失败超时时间 + // 达到 MaxFails 后,目标在此时间内被视为不可用 + FailTimeout time.Duration `yaml:"fail_timeout"` + + // Backup 备份服务器 + // 仅当所有非备份服务器不可用时才使用 + Backup bool `yaml:"backup"` + + // Down 标记服务器为永久不可用 + Down bool `yaml:"down"` + + // ProxyURI 代理传递的 URI 路径 + // 设置后替换请求路径,支持 nginx proxy_pass URI 语义 + ProxyURI string `yaml:"proxy_uri"` +} + +// HealthCheckConfig 健康检查配置。 +// +// 定期检查后端服务器的健康状态,自动剔除不健康的节点。 +// +// 注意事项: +// - Interval 不宜设置过小,避免增加后端负担 +// - Path 应该是轻量级的健康检查端点 +// - 超时时间应小于检查间隔 +// +// 使用示例: +// +// health_check: +// interval: 10s +// path: "/health" +// timeout: 5s +type HealthCheckConfig struct { + Path string `yaml:"path"` + Interval time.Duration `yaml:"interval"` + Timeout time.Duration `yaml:"timeout"` + Match *HealthMatchConfig `yaml:"match"` // 健康检查匹配配置 + SlowStart time.Duration `yaml:"slow_start"` // 慢启动时间 +} + +// HealthMatchConfig 健康检查匹配配置。 +type HealthMatchConfig struct { + Status []string `yaml:"status"` // 状态码范围列表 + Body string `yaml:"body"` // 响应体正则表达式 + Headers map[string]string `yaml:"headers"` // 响应头匹配 +} + +// ProxyTimeout 代理超时配置。 +// +// 控制代理请求的各个阶段超时。 +// +// 注意事项: +// - Connect 超时包括 DNS 解析和 TCP 连接建立 +// - Read 和 Write 超时分别控制响应读取和请求发送 +// - 超时时间需要根据后端服务响应时间调整 +// +// 使用示例: +// +// timeout: +// connect: 5s +// read: 30s +// write: 30s +type ProxyTimeout struct { + // Connect 连接超时 + // 建立到后端服务器的连接超时 + Connect time.Duration `yaml:"connect"` + + // Read 读取超时 + // 从后端读取响应的超时 + Read time.Duration `yaml:"read"` + + // Write 写入超时 + // 向后端发送请求的超时 + Write time.Duration `yaml:"write"` +} + +// ProxyHeaders 代理请求/响应头配置。 +// +// 在代理转发过程中修改 HTTP 头部。 +// +// 注意事项: +// - SetRequest 添加/修改发送到后端的请求头 +// - SetResponse 添加/修改返回给客户端的响应头 +// - Remove 会删除指定的请求头(在发送到后端之前) +// +// 使用示例: +// +// headers: +// set_request: +// X-Forwarded-For: "$remote_addr" +// X-Real-IP: "$remote_addr" +// set_response: +// X-Proxy-By: "lolly" +// remove: +// - "X-Internal-Header" +type ProxyHeaders struct { + // SetRequest 设置请求头 + // 发送到后端的请求中添加或覆盖的头部 + SetRequest map[string]string `yaml:"set_request"` + + // SetResponse 设置响应头 + // 返回给客户端的响应中添加或覆盖的头部 + SetResponse map[string]string `yaml:"set_response"` + + // Remove 移除的头部 + // 从发送到后端的请求中移除的头部列表 + Remove []string `yaml:"remove"` + + // HideResponse 隐藏的响应头 + // 从返回给客户端的响应中移除的头部列表 + HideResponse []string `yaml:"hide_response"` + + // PassResponse 允许传递的响应头 + // 仅传递列出的头部,其他全部隐藏(白名单模式) + PassResponse []string `yaml:"pass_response"` + + // IgnoreHeaders 忽略的头部 + // 代理时完全忽略这些头部,不转发到后端也不返回给客户端 + IgnoreHeaders []string `yaml:"ignore_headers"` + + // CookieDomain Cookie 域重写 + // 将响应中 Set-Cookie 的 domain 替换为此值 + CookieDomain string `yaml:"cookie_domain"` + + // CookiePath Cookie 路径重写 + // 将响应中 Set-Cookie 的 path 替换为此值 + CookiePath string `yaml:"cookie_path"` +} + +// ProxySSLConfig 上游 SSL/TLS 配置。 +// +// 配置代理连接上游服务器时的 TLS 行为,支持自定义 CA、客户端证书(mTLS)、 +// SNI 和 TLS 版本控制。 +// +// 注意事项: +// - Enabled 为 true 时启用自定义 TLS 配置 +// - TrustedCA 用于验证上游服务器证书 +// - ClientCert + ClientKey 用于 mTLS 客户端认证 +// - InsecureSkipVerify 仅用于测试,生产环境禁用 +// +// 使用示例: +// +// proxy_ssl: +// enabled: true +// server_name: "api.internal" +// trusted_ca: "/etc/ssl/ca/upstream-ca.crt" +// client_cert: "/etc/ssl/client.crt" +// client_key: "/etc/ssl/client.key" +// min_version: "TLSv1.2" +type ProxySSLConfig struct { + // 字符串字段 + ServerName string `yaml:"server_name"` + TrustedCA string `yaml:"trusted_ca"` + ClientCert string `yaml:"client_cert"` + ClientKey string `yaml:"client_key"` + MinVersion string `yaml:"min_version"` + MaxVersion string `yaml:"max_version"` + // 布尔字段 + Enabled bool `yaml:"enabled"` + InsecureSkipVerify bool `yaml:"insecure_skip_verify"` +} + +// RedirectRewriteConfig Location/Refresh 头改写配置 +// +// 用于配置代理响应中 Location 和 Refresh 头的改写行为。 +// +// 注意事项: +// - Mode 支持 "default"、"off"、"custom" 三种模式 +// - 未配置或空字符串时默认为 "default" 模式 +// - "custom" 模式必须配置至少一条规则 +// +// 使用示例: +// +// redirect_rewrite: +// mode: "default" # 或 "off" 或 "custom" +// rules: +// - pattern: "http://backend:8000/" +// replacement: "$scheme://$host:$server_port/" +type RedirectRewriteConfig struct { + // Mode 运行模式: "default" | "off" | "custom" + // default: 自动从选中的 target URL 生成规则(运行时) + // off: 禁用改写 + // custom: 使用 Rules 列表(预编译) + // 未配置或空字符串时默认为 "default" + Mode string `yaml:"mode"` + + // Rules 改写规则列表,仅在 Mode="custom" 时使用 + Rules []RedirectRewriteRule `yaml:"rules"` +} + +// RedirectRewriteRule 单条改写规则 +// +// 定义 Location/Refresh 头改写的匹配模式和替换目标。 +// +// 注意事项: +// - Pattern 以 ~ 开头表示正则,~* 表示大小写不敏感 +// - 无 ~ 前缀时使用前缀匹配语义 +// - Replacement 支持变量展开($host, $scheme, $server_port 等) +// +// 使用示例: +// +// rules: +// - pattern: "http://backend:8000/" +// replacement: "$scheme://$host:$server_port/" +// - pattern: "~^http://[^/]+:8000/(.*)$" +// replacement: "$scheme://$host/$1" +type RedirectRewriteRule struct { + // Pattern 匹配模式,支持正则(以 ~ 开头)或精确匹配 + // 示例: "http://localhost:8000/" 或 "~^http://[^/]+:8000/" + Pattern string `yaml:"pattern"` + + // Replacement 替换目标,支持变量展开 + // 示例: "$scheme://$host:$server_port/" 或 "/" + Replacement string `yaml:"replacement"` +} + +// NextUpstreamConfig 故障转移配置,定义后端失败时的自动重试行为。 +// +// 当后端返回特定错误状态码或连接失败时,自动尝试下一个可用后端。 +// +// 注意事项: +// - Tries 为 1 时禁用故障转移 +// - 空 NextUpstream 使用默认值(Tries=1,禁用故障转移) +// - 建议根据后端数量合理设置 Tries 值 +// +// 使用示例: +// +// next_upstream: +// tries: 3 +// http_codes: [502, 503, 504] +type NextUpstreamConfig struct { + HTTPCodes []int `yaml:"http_codes"` + Tries int `yaml:"tries"` +} diff --git a/internal/config/security_config.go b/internal/config/security_config.go new file mode 100644 index 0000000..b0bbfdc --- /dev/null +++ b/internal/config/security_config.go @@ -0,0 +1,346 @@ +package config + +import "time" + +// SecurityConfig 安全配置,包含访问控制、限流、认证和安全头部。 +// +// 用于保护服务器免受各种网络攻击和滥用。 +// +// 注意事项: +// - Access 配置 IP 黑白名单控制访问来源 +// - RateLimit 配置请求频率限制防止 DDoS 攻击 +// - Auth 配置 HTTP Basic 认证保护敏感资源 +// - Headers 配置安全响应头部增强浏览器安全 +// - 各项配置可以组合使用,增强安全性 +// +// 使用示例: +// +// security: +// access: +// allow: ["192.168.1.0/24"] +// deny: ["10.0.0.0/8"] +// rate_limit: +// request_rate: 100 +// burst: 150 +// auth: +// type: "basic" +// users: +// - name: "admin" +// password: "$2y$10$..." +// headers: +// x_frame_options: "DENY" +type SecurityConfig struct { + Headers SecurityHeaders `yaml:"headers"` + Access AccessConfig `yaml:"access"` + ErrorPage ErrorPageConfig `yaml:"error_page"` + Auth AuthConfig `yaml:"auth"` + AuthRequest AuthRequestConfig `yaml:"auth_request"` + RateLimit RateLimitConfig `yaml:"rate_limit"` +} + +// AccessConfig IP 访问控制配置。 +// +// 通过 IP 地址或 CIDR 范围控制访问权限,支持基于 GeoIP 的国家代码访问控制。 +// +// 注意事项: +// - Allow 和 Deny 列表按配置顺序匹配 +// - Default 指定未匹配时的默认动作 +// - TrustedProxies 用于正确获取客户端真实 IP +// - GeoIP 配置启用后,会基于国家代码进行二次检查 +// - 支持 IPv4 和 IPv6 地址格式 +// +// 使用示例: +// +// access: +// allow: ["192.168.1.0/24", "10.0.0.0/8"] +// deny: ["192.168.1.100"] +// default: "deny" +// trusted_proxies: ["172.16.0.0/16"] +// geoip: +// enabled: true +// database: "/var/lib/geoip/GeoIP2-Country.mmdb" +// allow_countries: ["US", "JP", "GB"] +// deny_countries: ["CN", "RU"] +// default: "deny" +// cache_size: 10000 +// cache_ttl: 1h +// private_ip_behavior: "allow" +type AccessConfig struct { + // Allow 允许的 IP/CIDR 列表 + // 配置允许访问的 IP 地址或网段 + Allow []string `yaml:"allow"` + + // Deny 拒绝的 IP/CIDR 列表 + // 配置拒绝访问的 IP 地址或网段 + Deny []string `yaml:"deny"` + + // TrustedProxies 可信代理 CIDR 列表 + // 用于正确解析 X-Forwarded-For 头部获取真实客户端 IP + TrustedProxies []string `yaml:"trusted_proxies"` + + // Default 默认动作 + // 未匹配任何规则时的处理方式:allow 或 deny + Default string `yaml:"default"` + + // GeoIP GeoIP 国家代码访问控制配置 + GeoIP GeoIPConfig `yaml:"geoip"` +} + +// GeoIPConfig GeoIP 访问控制配置。 +// +// 通过 MaxMind GeoIP2 数据库查询 IP 所属国家,实现基于国家代码的访问控制。 +// +// 注意事项: +// - Database 为 GeoIP2 数据库文件路径(.mmdb 格式) +// - AllowCountries 和 DenyCountries 使用 ISO 3166-1 alpha-2 国家代码 +// - CacheSize 设置 LRU 缓存最大条目数,0 表示使用默认值 10000 +// - CacheTTL 设置缓存有效期,0 表示使用默认值 1 小时 +// - PrivateIPBehavior 控制私有 IP 的处理策略 +// +// 使用示例: +// +// geoip: +// enabled: true +// database: "/var/lib/geoip/GeoIP2-Country.mmdb" +// allow_countries: ["US", "JP", "GB"] +// deny_countries: ["CN", "RU"] +// default: "deny" +// cache_size: 10000 +// cache_ttl: 1h +// private_ip_behavior: "allow" +type GeoIPConfig struct { + Database string `yaml:"database"` + Default string `yaml:"default"` + PrivateIPBehavior string `yaml:"private_ip_behavior"` + AllowCountries []string `yaml:"allow_countries"` + DenyCountries []string `yaml:"deny_countries"` + CacheSize int `yaml:"cache_size"` + CacheTTL time.Duration `yaml:"cache_ttl"` + Enabled bool `yaml:"enabled"` +} + +// RateLimitConfig 速率限制配置。 +// +// 限制请求频率防止 DDoS 攻击和资源滥用。 +// +// 注意事项: +// - RequestRate 为每秒允许的最大请求数 +// - Burst 为突发流量允许的最大请求数 +// - ConnLimit 为单个 IP 的最大并发连接数 +// - Algorithm 支持 token_bucket 和 sliding_window 两种算法 +// - SlidingWindow 仅在 sliding_window 算法下生效 +// +// 使用示例: +// +// rate_limit: +// request_rate: 100 +// burst: 150 +// conn_limit: 50 +// algorithm: "token_bucket" +// key: "ip" +type RateLimitConfig struct { + Key string `yaml:"key"` + Algorithm string `yaml:"algorithm"` + SlidingWindowMode string `yaml:"sliding_window_mode"` + RequestRate int `yaml:"request_rate"` + Burst int `yaml:"burst"` + ConnLimit int `yaml:"conn_limit"` + SlidingWindow int `yaml:"sliding_window"` +} + +// LimitRateConfig 响应速率限制配置。 +// +// 控制响应数据的发送速率,防止单个连接占用过多带宽。 +// +// 注意事项: +// - Rate 为每秒发送的字节数,0 表示不限速 +// - Burst 为突发流量允许的字节数 +// - LargeFileThreshold 为大文件阈值,超过此大小的文件采用特殊策略 +// - LargeFileStrategy 为大文件策略:skip(跳过限速)或 coarse(粗粒度限速) +// +// 使用示例: +// +// limit_rate: +// rate: 1048576 # 1MB/s +// burst: 524288 # 512KB 突发 +// large_file_threshold: 10485760 # 10MB +// large_file_strategy: "skip" +type LimitRateConfig struct { + // Rate 字节/秒,0 表示不限速 + Rate int64 `yaml:"rate"` + + // Burst 突发流量字节数 + Burst int64 `yaml:"burst"` + + // LargeFileThreshold 大文件阈值(字节),默认 10MB + LargeFileThreshold int64 `yaml:"large_file_threshold"` + + // LargeFileStrategy 大文件策略:skip(跳过限速)或 coarse(粗粒度限速) + LargeFileStrategy string `yaml:"large_file_strategy"` +} + +// AuthConfig 认证配置。 +// +// 配置 HTTP Basic 认证保护敏感资源。 +// +// 注意事项: +// - Type 目前仅支持 basic +// - RequireTLS 默认为 true,强制 HTTPS 传输 +// - Algorithm 支持 bcrypt 和 argon2id +// - Users 中 Password 字段存储的是密码哈希而非明文 +// - MinPasswordLength 控制密码最小长度要求 +// +// 使用示例: +// +// auth: +// type: "basic" +// require_tls: true +// algorithm: "bcrypt" +// realm: "Secure Area" +// min_password_length: 8 +// users: +// - name: "admin" +// password: "$2y$10$..." +type AuthConfig struct { + Type string `yaml:"type"` + Algorithm string `yaml:"algorithm"` + Realm string `yaml:"realm"` + Users []User `yaml:"users"` + MinPasswordLength int `yaml:"min_password_length"` + RequireTLS bool `yaml:"require_tls"` +} + +// User 认证用户配置。 +// +// 定义单个认证用户的凭据。 +// +// 注意事项: +// - Name 为用户标识,区分大小写 +// - Password 存储的是哈希值而非明文密码 +// - 支持的哈希格式取决于 Algorithm 设置 +// +// 使用示例: +// +// users: +// - name: "admin" +// password: "$2y$10$N9qo8uLOickgx2ZMRZoMy..." +type User struct { + // Name 用户名 + // 认证时使用的用户标识 + Name string `yaml:"name"` + + // Password 密码哈希 + // bcrypt 或 argon2id 哈希值,非明文密码 + Password string `yaml:"password"` +} + +// SecurityHeaders 安全头部配置。 +// +// 配置 HTTP 安全响应头部增强浏览器安全。 +// +// 注意事项: +// - XFrameOptions 防止点击劫持攻击 +// - XContentTypeOptions 防止 MIME 类型嗅探 +// - ContentSecurityPolicy 控制资源加载策略 +// - ReferrerPolicy 控制 Referer 头发送策略 +// - PermissionsPolicy 控制浏览器功能权限 +// +// 使用示例: +// +// headers: +// x_frame_options: "DENY" +// x_content_type_options: "nosniff" +// content_security_policy: "default-src 'self'" +// referrer_policy: "strict-origin-when-cross-origin" +type SecurityHeaders struct { + // XFrameOptions X-Frame-Options 头部 + // 可选值:DENY、SAMEORIGIN,防止页面被嵌入 iframe + XFrameOptions string `yaml:"x_frame_options"` + + // XContentTypeOptions X-Content-Type-Options 头部 + // 建议值:nosniff,防止浏览器 MIME 类型嗅探 + XContentTypeOptions string `yaml:"x_content_type_options"` + + // ContentSecurityPolicy Content-Security-Policy 头部 + // 控制页面可以加载的资源来源 + ContentSecurityPolicy string `yaml:"content_security_policy"` + + // ReferrerPolicy Referrer-Policy 头部 + // 控制 Referer 头的发送策略 + ReferrerPolicy string `yaml:"referrer_policy"` + + // PermissionsPolicy Permissions-Policy 头部 + // 控制浏览器功能权限(原 Feature-Policy) + PermissionsPolicy string `yaml:"permissions_policy"` +} + +// ErrorPageConfig 自定义错误页面配置。 +// +// 允许为特定 HTTP 状态码配置自定义错误页面。 +// 错误页面文件在启动时预加载到内存中,运行时不进行文件 I/O。 +// +// 注意事项: +// - 错误页面文件路径可以是相对路径或绝对路径 +// - 所有错误页面加载失败时会阻止服务器启动 +// - 部分错误页面加载失败会记录警告但允许启动 +// - 支持可选的响应状态码覆盖 +// +// 使用示例: +// +// error_page: +// pages: +// 404: "/var/www/errors/404.html" +// 500: "/var/www/errors/500.html" +// 503: "/var/www/errors/503.html" +// default: "/var/www/errors/error.html" +// response_code: 200 # 可选:覆盖响应状态码 +type ErrorPageConfig struct { + // Pages 状态码到错误页面文件的映射 + // key 为 HTTP 状态码(如 404, 500),value 为文件路径 + Pages map[int]string `yaml:"pages"` + + // Default 默认错误页面 + // 当特定状态码没有配置时使用 + Default string `yaml:"default"` + + // ResponseCode 响应状态码覆盖 + // 如果不为 0,所有错误页面响应将使用此状态码 + // 例如设置为 200 时,即使发生错误也返回 200 OK + ResponseCode int `yaml:"response_code"` +} + +// AuthRequestConfig 外部认证子请求配置。 +// +// 将认证委托给外部服务,根据子请求的响应状态码决定是否允许原请求继续。 +// 适用于需要复杂认证逻辑或与现有认证系统集成的场景。 +// +// 行为规则: +// - 2xx 响应:认证通过,原请求继续处理 +// - 401/403 响应:认证失败,返回相应状态码 +// - 其他响应或超时:返回 500 内部服务器错误 +// - 认证服务不可用时:返回 500 内部服务器错误 +// +// 注意事项: +// - 认证请求使用独立的连接池,避免影响主服务 +// - 支持变量展开(如 $host, $uri, $request_uri) +// - 建议配置合理的超时时间,避免长时间阻塞 +// - 认证请求会携带原请求的头信息(如 Cookie, Authorization) +// +// 使用示例: +// +// security: +// auth_request: +// uri: /auth +// method: GET +// auth_timeout: 5s +// headers: +// X-Original-Uri: $request_uri +// X-Original-Host: $host +type AuthRequestConfig struct { + Headers map[string]string `yaml:"headers"` + URI string `yaml:"uri"` + Method string `yaml:"method"` + ForwardHeaders []string `yaml:"forward_headers"` + Timeout time.Duration `yaml:"auth_timeout"` + Enabled bool `yaml:"enabled"` +} diff --git a/internal/config/server_config.go b/internal/config/server_config.go new file mode 100644 index 0000000..ba1070b --- /dev/null +++ b/internal/config/server_config.go @@ -0,0 +1,344 @@ +package config + +import "time" + +// ServerConfig 服务器配置,包含监听地址、静态文件、代理、SSL 等设置。 +// +// 用于定义单个服务器的完整行为,包括网络监听、请求处理、 +// 安全防护和性能控制等方面。 +// +// 注意事项: +// - Listen 字段为必填项,格式为 "host:port" 或 ":port" +// - Name 字段用于虚拟主机匹配,多服务器模式下建议配置 +// - SSL 配置为可选,但生产环境强烈建议启用 +// - 超时设置需根据实际业务场景调整 +// +// 使用示例: +// +// server: +// listen: ":8080" +// name: "api.example.com" +// read_timeout: 30s +// write_timeout: 30s +type ServerConfig struct { + // 指针类型字段(按大小排列,减少 padding) + CacheAPI *CacheAPIConfig `yaml:"cache_api"` + Lua *LuaMiddlewareConfig `yaml:"lua"` + // 切片字段 + Static []StaticConfig `yaml:"static"` + Proxy []ProxyConfig `yaml:"proxy"` + Rewrite []RewriteRule `yaml:"rewrite"` + // 字符串字段 + ClientMaxBodySize string `yaml:"client_max_body_size"` + Name string `yaml:"name"` + Listen string `yaml:"listen"` + // 结构体字段(嵌入类型) + Security SecurityConfig `yaml:"security"` + Compression CompressionConfig `yaml:"compression"` + SSL SSLConfig `yaml:"ssl"` + UnixSocket UnixSocketConfig `yaml:"unix_socket"` // Unix socket 配置 + LimitRate LimitRateConfig `yaml:"limit_rate"` // 响应速率限制配置 + Types TypesConfig `yaml:"types"` // MIME 类型配置 + // 切片字段 + ServerNames []string `yaml:"server_names"` // 支持多个 server_name + // time.Duration 字段(int64) + ReadTimeout time.Duration `yaml:"read_timeout"` + IdleTimeout time.Duration `yaml:"idle_timeout"` + WriteTimeout time.Duration `yaml:"write_timeout"` + // 基本类型字段(int 按大小排列) + MaxRequestsPerConn int `yaml:"max_requests_per_conn"` + MaxConnsPerIP int `yaml:"max_conns_per_ip"` + Concurrency int `yaml:"concurrency"` // 最大并发连接数(默认 256 * 1024) + ReadBufferSize int `yaml:"read_buffer_size"` // 读缓冲区大小(字节,默认 16KB) + WriteBufferSize int `yaml:"write_buffer_size"` // 写缓冲区大小(字节,默认 16KB) + // 布尔字段(放在一起减少 padding) + Default bool `yaml:"default,omitempty"` // VHost 默认主机标记 + ReduceMemoryUsage bool `yaml:"reduce_memory_usage"` // 是否优先减少内存使用(默认 false,优先性能) + ServerTokens bool `yaml:"server_tokens"` // false 隐藏版本号,默认 true(零值表示显示版本) +} + +// StaticConfig 静态文件服务配置。 +// +// 用于配置静态文件服务器的行为,包括路径匹配、根目录和索引文件。 +// +// 注意事项: +// - Path 为路径前缀,匹配的请求将被该静态处理器处理 +// - Root 路径可以是相对路径或绝对路径 +// - 索引文件按顺序查找,第一个存在的文件将被使用 +// - 目录路径需要确保有读取权限 +// +// 使用示例: +// +// static: +// - path: "/" +// root: "/var/www/html" +// index: ["index.html", "index.htm"] +// - path: "/assets/" +// root: "/var/www/assets" +type StaticConfig struct { + // Path 匹配路径前缀 + // 以此前缀开头的请求将被该静态处理器处理 + // 默认为 "/",匹配所有路径 + Path string `yaml:"path"` + + // Root 静态文件根目录 + // 所有静态文件请求都将以此目录为基础解析 + // 请求路径追加到 root 后面 + // 示例: root=/var/www, path=/static/ → /static/img.png → /var/www/static/img.png + Root string `yaml:"root"` + + // Alias 替换路径(与 root 互斥) + // 将 location 路径替换为 alias 路径(nginx alias 语义) + // 示例: alias=/var/www/files/, path=/images/ → /images/logo.png → /var/www/files/logo.png + Alias string `yaml:"alias"` + + // Index 索引文件列表 + // 访问目录时依次查找这些文件作为默认页面 + // 默认为 ["index.html", "index.htm"] + Index []string `yaml:"index"` + + // TryFiles 按顺序尝试查找的文件列表 + // 支持以下模式: + // - $uri: 请求路径 + // - $uri/: 请求路径加斜杠(目录) + // - $uri.: 请求路径加扩展名(如 $uri.html, $uri.json) + // - /path: 绝对路径回退(如 /index.html) + // - filename: 相对路径回退(如 fallback.html) + // + // nginx 兼容性: + // - $uri 变量语义与 nginx try_files 指令一致 + // - 配置语法可从 nginx 直接迁移 + // + // 安全限制(附加于 nginx 基础): + // - 扩展名仅允许字母、数字、点、下划线、连字符 + // - 禁止危险后缀(.php, .exe, .bat 等) + // - 禁止 null byte 和路径分隔符 + // + // 根路径边界情况: + // - 当 relPath="/" 且模式为 "$uri." 时,返回空字符串 + // - 此设计避免生成 "/.html" 这样的隐藏文件名 + // - 建议使用绝对路径回退(如 /index.html)处理根路径 + // + // 示例: + // try_files: ["$uri", "$uri.html", "/index.html"] + // try_files: ["$uri", "$uri/", "/app.html"] + TryFiles []string `yaml:"try_files"` + + // TryFilesPass 内部重定向是否触发中间件 + // 默认为 false,内部重定向不触发中间件 + // 设置为 true 时,try_files 回退会重新进入中间件链 + TryFilesPass bool `yaml:"try_files_pass"` + + // SymlinkCheck 是否启用符号链接安全检查 + // 默认为 false,启用后会验证符号链接指向的文件是否在允许的路径范围内 + // 防止通过符号链接访问敏感文件(如 /etc/passwd) + SymlinkCheck bool `yaml:"symlink_check"` + + // LocationType 位置匹配类型 + // 可选值:exact、prefix、regex、regex_caseless、prefix_priority、named + LocationType string `yaml:"location_type"` + + // Internal 仅允许内部访问 + // 设置为 true 时,该位置仅允许内部重定向访问 + Internal bool `yaml:"internal"` + + // Expires 缓存过期时间 + // 支持 nginx 兼容格式:30d, 1h, 1m, max, epoch, off + // 设置 Cache-Control: max-age 和 Expires 响应头 + // 示例:expires: 30d → Cache-Control: max-age=2592000 + Expires string `yaml:"expires"` +} + +// TypesConfig MIME 类型配置 +// +// 用于配置静态文件的 MIME 类型映射。 +// +// 注意事项: +// - DefaultType 为默认 MIME 类型 +// - Map 为扩展名到 MIME 类型的映射 +// +// 使用示例: +// +// types: +// default_type: "application/octet-stream" +// map: +// ".html": "text/html" +// ".css": "text/css" +// ".js": "application/javascript" +type TypesConfig struct { + // DefaultType 默认 MIME 类型 + // 当无法识别文件扩展名时使用 + DefaultType string `yaml:"default_type"` + + // Map 扩展名到 MIME 类型的映射 + // 键为文件扩展名(如 ".html"),值为 MIME 类型 + Map map[string]string `yaml:"map"` +} + +// UnixSocketConfig Unix socket 特定配置。 +// +// 用于配置服务器监听 Unix domain socket 时的文件权限和所有权。 +// +// 注意事项: +// - Mode 为 socket 文件权限,默认 0666 +// - User 为 socket 文件所有者用户名 +// - Group 为 socket 文件所属用户组 +// +// 使用示例: +// +// unix_socket: +// mode: 0660 +// user: "www-data" +// group: "www-data" +type UnixSocketConfig struct { + // Mode 文件权限 + // Unix socket 文件的访问权限,默认 0666 + Mode int `yaml:"mode"` + + // User 文件所有者 + // Unix socket 文件的所有者用户名 + User string `yaml:"user"` + + // Group 文件组 + // Unix socket 文件的所属用户组 + Group string `yaml:"group"` +} + +// StreamConfig TCP/UDP Stream 代理配置。 +// +// 用于四层网络代理,如数据库、Redis 等 TCP/UDP 服务。 +// +// 注意事项: +// - Listen 配置监听地址 +// - Protocol 支持 tcp 或 udp +// - Upstream 配置后端目标列表 +// - Stream 代理工作在传输层,不解析应用层协议 +// +// 使用示例: +// +// stream: +// - listen: ":3306" +// protocol: "tcp" +// upstream: +// targets: +// - addr: "mysql1:3306" +// weight: 3 +// - addr: "mysql2:3306" +// weight: 1 +// load_balance: "round_robin" +type StreamConfig struct { + Listen string `yaml:"listen"` + Protocol string `yaml:"protocol"` + Upstream StreamUpstream `yaml:"upstream"` + ProxySSL StreamProxySSLConfig `yaml:"proxy_ssl"` + SSL StreamSSLConfig `yaml:"ssl"` +} + +// StreamUpstream Stream 上游配置。 +// +// 配置 Stream 代理的后端服务器列表。 +// +// 注意事项: +// - Targets 配置后端服务器地址 +// - LoadBalance 配置负载均衡算法 +// +// 使用示例: +// +// upstream: +// targets: +// - addr: "backend1:3306" +// weight: 3 +// load_balance: "round_robin" +type StreamUpstream struct { + LoadBalance string `yaml:"load_balance"` + Targets []StreamTarget `yaml:"targets"` +} + +// StreamTarget Stream 目标配置。 +// +// 定义单个 Stream 后端服务器。 +// +// 注意事项: +// - Addr 为后端服务器地址 +// - Weight 在加权轮询算法下生效 +// +// 使用示例: +// +// targets: +// - addr: "mysql1:3306" +// weight: 3 +// - addr: "mysql2:3306" +// weight: 1 +type StreamTarget struct { + // Addr 目标地址 + // 后端服务器地址,如 "host:port" + Addr string `yaml:"addr"` + + // Weight 权重 + // 用于加权轮询负载均衡 + Weight int `yaml:"weight"` +} + +// StreamSSLConfig Stream SSL 服务端配置。 +// +// 配置 Stream 模块的 TLS 终端功能,用于加密 TCP 流量。 +// +// 注意事项: +// - 仅对 TCP 协议有效,UDP 不支持 TLS +// - 证书文件需要 PEM 格式 +// - 支持配置客户端证书验证(mTLS) +// +// 使用示例: +// +// stream: +// - listen: ":3306" +// protocol: "tcp" +// ssl: +// enabled: true +// cert: "/etc/ssl/server.crt" +// key: "/etc/ssl/server.key" +// upstream: +// targets: +// - addr: "mysql:3306" +type StreamSSLConfig struct { + Cert string `yaml:"cert"` + Key string `yaml:"key"` + ClientCA string `yaml:"client_ca"` + Protocols []string `yaml:"protocols"` + Ciphers []string `yaml:"ciphers"` + VerifyDepth int `yaml:"verify_depth"` + Enabled bool `yaml:"enabled"` +} + +// StreamProxySSLConfig Stream 上游 SSL 配置。 +// +// 配置到上游服务器的 TLS 连接,用于加密代理到后端的流量。 +// +// 注意事项: +// - 启用后,代理将使用 TLS 连接到上游 +// - 支持客户端证书(mTLS)和服务器证书验证 +// - ServerName 用于 SNI 和证书验证 +// +// 使用示例: +// +// stream: +// - listen: ":3306" +// protocol: "tcp" +// proxy_ssl: +// enabled: true +// verify: true +// trusted_ca: "/etc/ssl/ca.crt" +// server_name: "mysql.internal" +// upstream: +// targets: +// - addr: "mysql:3306" +type StreamProxySSLConfig struct { + TrustedCA string `yaml:"trusted_ca"` + ServerName string `yaml:"server_name"` + Cert string `yaml:"cert"` + Key string `yaml:"key"` + Protocols []string `yaml:"protocols"` + Enabled bool `yaml:"enabled"` + Verify bool `yaml:"verify"` + SessionReuse bool `yaml:"session_reuse"` +} diff --git a/internal/config/ssl_config.go b/internal/config/ssl_config.go new file mode 100644 index 0000000..8f0c962 --- /dev/null +++ b/internal/config/ssl_config.go @@ -0,0 +1,123 @@ +package config + +import "time" + +// SSLConfig SSL/TLS 配置。 +// +// 用于配置 HTTPS 服务所需的证书和加密参数。 +// 支持 TLS 1.2 和 TLS 1.3 协议,可自定义加密套件。 +// +// 注意事项: +// - Cert 和 Key 为必需字段,分别指向证书和私钥文件 +// - CertChain 可选,用于配置完整的证书链 +// - Protocols 建议使用默认值,避免使用不安全的 TLS 1.0/1.1 +// - Ciphers 仅对 TLS 1.2 有效,TLS 1.3 有固定加密套件 +// - 启用 OCSPStapling 可提升握手性能 +// +// 使用示例: +// +// ssl: +// cert: "/etc/ssl/certs/server.crt" +// key: "/etc/ssl/private/server.key" +// cert_chain: "/etc/ssl/certs/chain.crt" +// protocols: ["TLSv1.2", "TLSv1.3"] +// ocsp_stapling: true +// hsts: +// max_age: 31536000 +// include_sub_domains: true +type SSLConfig struct { + ClientVerify ClientVerifyConfig `yaml:"client_verify"` + Cert string `yaml:"cert"` + Key string `yaml:"key"` + CertChain string `yaml:"cert_chain"` + Protocols []string `yaml:"protocols"` + Ciphers []string `yaml:"ciphers"` + SessionTickets SessionTicketsConfig `yaml:"session_tickets"` + HTTP2 HTTP2Config `yaml:"http2"` + HSTS HSTSConfig `yaml:"hsts"` + OCSPStapling bool `yaml:"ocsp_stapling"` +} + +// HSTSConfig HTTP Strict Transport Security 配置。 +// +// 强制浏览器使用 HTTPS 访问,防止中间人攻击和协议降级攻击。 +// +// 注意事项: +// - MaxAge 单位为秒,建议至少设置为 1 年(31536000) +// - IncludeSubDomains 为 true 时策略应用于所有子域名 +// - Preload 为 true 表示申请加入浏览器预加载列表 +// - 启用前确保所有站点资源都支持 HTTPS +// +// 使用示例: +// +// hsts: +// max_age: 31536000 +// include_sub_domains: true +// preload: false +type HSTSConfig struct { + // MaxAge 过期时间(秒) + // 默认 31536000(1年),建议至少 6 个月 + MaxAge int `yaml:"max_age"` + + // IncludeSubDomains 包含子域名 + // 为 true 时策略应用于当前域名及其所有子域名 + IncludeSubDomains bool `yaml:"include_sub_domains"` + + // Preload 加入 HSTS 预加载列表 + // 申请加入浏览器内置的 HSTS 列表 + Preload bool `yaml:"preload"` +} + +// SessionTicketsConfig TLS Session Ticket 配置。 +// +// Session Tickets 允许 TLS 1.3 会话恢复,避免完整握手,显著提升性能。 +// 密钥定期轮换增强安全性,同时保留旧密钥确保已发放的票据仍可解密。 +// +// 注意事项: +// - KeyFile 为密钥存储文件路径,用于持久化密钥 +// - RotateInterval 为密钥轮换间隔,建议 1-24 小时 +// - RetainKeys 为保留的历史密钥数量,至少保留 2 个 +// - 密钥文件权限应为 0600(仅所有者可读写) +// +// 使用示例: +// +// ssl: +// session_tickets: +// enabled: true +// key_file: "/var/lib/lolly/session_tickets.key" +// rotate_interval: 1h +// retain_keys: 3 +type SessionTicketsConfig struct { + KeyFile string `yaml:"key_file"` + RotateInterval time.Duration `yaml:"rotate_interval"` + RetainKeys int `yaml:"retain_keys"` + Enabled bool `yaml:"enabled"` +} + +// ClientVerifyConfig mTLS 客户端证书验证配置。 +// +// 配置双向 TLS 认证,要求客户端提供有效证书才能建立连接。 +// 适用于需要强身份验证的场景,如 API 服务、内部系统通信。 +// +// 注意事项: +// - Mode 可选值:none、request、require、optional_no_ca +// - ClientCA 为客户端 CA 证书文件路径(必需) +// - VerifyDepth 为证书链验证深度,默认 1 +// - CRL 为证书撤销列表文件路径(可选) +// +// 使用示例: +// +// ssl: +// client_verify: +// enabled: true +// mode: "require" +// client_ca: "/etc/ssl/ca/client-ca.crt" +// verify_depth: 2 +// crl: "/etc/ssl/ca/client-ca.crl" +type ClientVerifyConfig struct { + Mode string `yaml:"mode"` + ClientCA string `yaml:"client_ca"` + CRL string `yaml:"crl"` + VerifyDepth int `yaml:"verify_depth"` + Enabled bool `yaml:"enabled"` +} diff --git a/internal/config/variable_config.go b/internal/config/variable_config.go new file mode 100644 index 0000000..1f069b9 --- /dev/null +++ b/internal/config/variable_config.go @@ -0,0 +1,211 @@ +package config + +import "time" + +// IncludeConfig 配置引入配置。 +// +// 用于从其他文件加载配置片段并合并到当前配置。 +// 支持 glob 模式展开多个文件。 +// +// 使用示例: +// +// include: +// - path: "conf.d/*.yaml" +type IncludeConfig struct { + Path string `yaml:"path"` +} + +// VariablesConfig 自定义变量配置。 +// +// 用于定义全局自定义变量,可在日志格式和请求头中引用。 +// 变量作用于所有虚拟主机。 +// +// 注意事项: +// - 变量名只允许字母、数字、下划线 +// - 变量名不能与内置变量冲突 +// - 变量名不能以 arg_、http_、cookie_ 开头(动态变量前缀) +// +// 使用示例: +// +// variables: +// set: +// app_name: "lolly" +// version: "1.0.0" +type VariablesConfig struct { + // Set 自定义变量集合 + // 键值对形式,可在日志格式和请求头模板中使用 $var_name 引用 + Set map[string]string `yaml:"set"` +} + +// RewriteRule URL 重写规则。 +// +// 用于在代理或静态文件服务前修改请求 URL。 +// +// 注意事项: +// - Pattern 为正则表达式,用于匹配原始 URL +// - Replacement 为替换后的目标 URL,支持捕获组 +// - Flag 控制重写行为:last、redirect、permanent、break +// - 规则按顺序执行,匹配后根据 Flag 决定是否继续 +// +// 使用示例: +// +// rewrite: +// - pattern: "^/old/(.*)$" +// replacement: "/new/$1" +// flag: "permanent" +// - pattern: "^/api/(.*)$" +// replacement: "/v1/$1" +// flag: "last" +type RewriteRule struct { + // Pattern 匹配模式 + // 正则表达式,用于匹配请求 URL + Pattern string `yaml:"pattern"` + + // Replacement 替换目标 + // 替换后的 URL 路径,支持 $1、$2 等捕获组引用 + Replacement string `yaml:"replacement"` + + // Flag 标志 + // 可选值: + // - last:停止后续规则匹配 + // - redirect:返回 302 临时重定向 + // - permanent:返回 301 永久重定向 + // - break:停止规则匹配但继续处理 + Flag string `yaml:"flag"` +} + +// CompressionConfig 响应压缩配置。 +// +// 配置响应内容压缩,减少传输数据量。 +// +// 注意事项: +// - Type 支持 gzip、brotli 或 both(同时使用两种) +// - Level 压缩级别 1-9,越高压缩率越好但 CPU 消耗越大 +// - MinSize 低于此大小的响应不压缩 +// - Types 指定哪些 MIME 类型进行压缩 +// - GzipStatic 启用后优先使用预压缩文件 +// +// 使用示例: +// +// compression: +// type: "gzip" +// level: 6 +// min_size: 1024 +// types: ["text/html", "text/css", "application/json"] +// gzip_static: true +// gzip_static_extensions: [".gz"] +type CompressionConfig struct { + Type string `yaml:"type"` + Types []string `yaml:"types"` + GzipStaticExtensions []string `yaml:"gzip_static_extensions"` + Level int `yaml:"level"` + MinSize int `yaml:"min_size"` + GzipStatic bool `yaml:"gzip_static"` +} + +// LuaMiddlewareConfig Lua 中间件配置(配置文件格式) +// +// 用于配置 Lua 中间件的行为,包括脚本路径、执行阶段和全局设置。 +// +// 注意事项: +// - Enabled 为 true 时启用 Lua 中间件 +// - Scripts 配置要执行的脚本列表 +// - GlobalSettings 控制 Lua 引擎的全局行为 +// +// 使用示例: +// +// lua: +// enabled: true +// scripts: +// - path: "/scripts/auth.lua" +// phase: "access" +// timeout: 10s +// global_settings: +// max_concurrent_coroutines: 1000 +// coroutine_timeout: 30s +type LuaMiddlewareConfig struct { + Scripts []LuaScriptConfig `yaml:"scripts"` + GlobalSettings LuaGlobalSettings `yaml:"global_settings"` + Enabled bool `yaml:"enabled"` +} + +// LuaScriptConfig 单个脚本配置 +// +// 定义单个 Lua 脚本的执行参数。 +// +// 注意事项: +// - Path 为脚本文件路径,必需字段 +// - Phase 为执行阶段,必需字段 +// - Timeout 控制脚本执行超时 +// +// 使用示例: +// +// scripts: +// - path: "/scripts/auth.lua" +// phase: "access" +// timeout: 10s +// enabled: true +type LuaScriptConfig struct { + // Path 脚本路径 + Path string `yaml:"path"` + + // Phase 执行阶段 + // 可选值:rewrite、access、content、log、header_filter、body_filter + Phase string `yaml:"phase"` + + // Timeout 执行超时 + Timeout time.Duration `yaml:"timeout"` + + // Enabled 是否启用此脚本(默认 true) + Enabled bool `yaml:"enabled"` +} + +// LuaGlobalSettings 全局 Lua 设置 +// +// 控制 Lua 引擎的全局行为。 +// +// 注意事项: +// - MaxConcurrentCoroutines 控制最大并发协程数 +// - CoroutineTimeout 控制协程执行超时 +// - CodeCacheSize 控制字节码缓存大小 +// - CoroutineStackSize 控制协程栈大小(默认64) +// - MinimizeStackMemory 启用栈内存自动收缩 +// - CoroutinePoolWarmup 协程池预热数量 +// +// 使用示例: +// +// global_settings: +// max_concurrent_coroutines: 1000 +// coroutine_timeout: 30s +// code_cache_size: 1000 +// enable_file_watch: true +// max_execution_time: 30s +// coroutine_stack_size: 64 +// minimize_stack_memory: true +// coroutine_pool_warmup: 4 +type LuaGlobalSettings struct { + // MaxConcurrentCoroutines 最大并发协程数 + MaxConcurrentCoroutines int `yaml:"max_concurrent_coroutines"` + + // CoroutineTimeout 协程执行超时 + CoroutineTimeout time.Duration `yaml:"coroutine_timeout"` + + // CodeCacheSize 字节码缓存条目数 + CodeCacheSize int `yaml:"code_cache_size"` + + // MaxExecutionTime 单脚本最大执行时间 + MaxExecutionTime time.Duration `yaml:"max_execution_time"` + + // CoroutineStackSize 协程栈大小(默认64,最大256) + // 较小的栈减少内存分配,适用于简单脚本 + CoroutineStackSize int `yaml:"coroutine_stack_size"` + + // CoroutinePoolWarmup 协程池预热数量,启动时预创建 + CoroutinePoolWarmup int `yaml:"coroutine_pool_warmup"` + + // EnableFileWatch 启用文件变更检测 + EnableFileWatch bool `yaml:"enable_file_watch"` + + // MinimizeStackMemory 启用栈内存自动收缩以减少内存占用 + MinimizeStackMemory bool `yaml:"minimize_stack_memory"` +} diff --git a/internal/lua/api_socket_tcp.go b/internal/lua/api_socket_tcp.go index d849bfa..482dc8a 100644 --- a/internal/lua/api_socket_tcp.go +++ b/internal/lua/api_socket_tcp.go @@ -124,6 +124,13 @@ func (s *TCPSocket) Connect(host string, port int) error { s.setState(SocketStateError) return fmt.Errorf("resolve address: %w", err) } + + // IP 字面量:立即检查受限地址 + if !s.manager.DisableSSRFGuard && addr.IP != nil && isRestrictedIP(addr.IP) { + s.setState(SocketStateError) + return fmt.Errorf("connection to restricted address denied: %s", addr.IP) + } + s.addr = addr // 开始操作 @@ -140,7 +147,24 @@ func (s *TCPSocket) Connect(host string, port int) error { Timeout: s.connectTimeout, } - conn, err := dialer.DialContext(context.Background(), "tcp", addr.String()) + // 主机名时:解析 DNS 并检查受限 IP(避免 DialContext 二次解析 TOCTOU) + connectAddr := addr.String() + if addr.IP == nil { + tcpAddr, resolveErr := net.ResolveTCPAddr("tcp", addr.String()) + if resolveErr != nil { + s.setState(SocketStateError) + s.manager.CompleteOperation(op.ID, nil, fmt.Errorf("resolve: %w", resolveErr)) + return + } + if !s.manager.DisableSSRFGuard && isRestrictedIP(tcpAddr.IP) { + s.setState(SocketStateError) + s.manager.CompleteOperation(op.ID, nil, fmt.Errorf("connection to restricted address denied: %s", tcpAddr.IP)) + return + } + connectAddr = tcpAddr.String() + } + + conn, err := dialer.DialContext(context.Background(), "tcp", connectAddr) if err != nil { s.setState(SocketStateError) s.manager.CompleteOperation(op.ID, nil, fmt.Errorf("dial: %w", err)) diff --git a/internal/lua/ip_guard.go b/internal/lua/ip_guard.go new file mode 100644 index 0000000..ec2d5a5 --- /dev/null +++ b/internal/lua/ip_guard.go @@ -0,0 +1,11 @@ +package lua + +import "net" + +// isRestrictedIP 检查 IP 地址是否属于受限范围(私有、回环、链路本地等)。 +// +// 用于防止 Lua Cosocket 的 SSRF 攻击。 +func isRestrictedIP(ip net.IP) bool { + return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || + ip.IsLinkLocalMulticast() || ip.IsUnspecified() +} diff --git a/internal/lua/ip_guard_test.go b/internal/lua/ip_guard_test.go new file mode 100644 index 0000000..e5bbf30 --- /dev/null +++ b/internal/lua/ip_guard_test.go @@ -0,0 +1,51 @@ +package lua + +import ( + "net" + "testing" +) + +func TestIsRestrictedIP(t *testing.T) { + tests := []struct { + name string + ip net.IP + blocked bool + }{ + // IPv4 回环 + {"IPv4 loopback", net.ParseIP("127.0.0.1"), true}, + {"IPv4 loopback alt", net.ParseIP("127.0.1.1"), true}, + // IPv4 私有 + {"IPv4 private 10.x", net.ParseIP("10.0.0.1"), true}, + {"IPv4 private 172.16.x", net.ParseIP("172.16.0.1"), true}, + {"IPv4 private 192.168.x", net.ParseIP("192.168.0.1"), true}, + // IPv4 链路本地 + {"IPv4 link-local", net.ParseIP("169.254.1.1"), true}, + // IPv4 未指定 + {"IPv4 unspecified", net.ParseIP("0.0.0.0"), true}, + // IPv4 公网 + {"IPv4 public", net.ParseIP("8.8.8.8"), false}, + {"IPv4 public 2", net.ParseIP("1.1.1.1"), false}, + // IPv6 回环 + {"IPv6 loopback", net.ParseIP("::1"), true}, + // IPv6 链路本地 + {"IPv6 link-local", net.ParseIP("fe80::1"), true}, + // IPv6 链路本地多播(应被拦截) + {"IPv6 link-local multicast", net.ParseIP("ff02::1"), true}, + // IPv6 公网 + {"IPv6 public", net.ParseIP("2001:4860:4860::8888"), false}, + // IPv4-mapped IPv6 + {"IPv4-mapped IPv6 loopback", net.ParseIP("::ffff:127.0.0.1"), true}, + {"IPv4-mapped IPv6 private", net.ParseIP("::ffff:10.0.0.1"), true}, + // nil IP + {"nil IP", net.IP{}, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isRestrictedIP(tt.ip) + if result != tt.blocked { + t.Errorf("isRestrictedIP(%v) = %v, want %v", tt.ip, result, tt.blocked) + } + }) + } +} diff --git a/internal/lua/socket_manager.go b/internal/lua/socket_manager.go index 738047a..115c6d7 100644 --- a/internal/lua/socket_manager.go +++ b/internal/lua/socket_manager.go @@ -234,11 +234,18 @@ type CosocketManager struct { // stats 统计信息 stats CosocketStats + + // DisableSSRFGuard 禁用 SSRF 防护(仅用于测试) + DisableSSRFGuard bool } // DefaultCosocketManager 全局默认 Cosocket 管理器 var DefaultCosocketManager = NewCosocketManager() +// testingSSRFGuardDisabled 测试模式下启用,允许本地回环连接。 +// 由 *_test.go 中的 init() 函数设置。 +var testingSSRFGuardDisabled bool + // NewCosocketManager 创建新的 Cosocket 管理器。 // // 启动后台清理循环,每 30 秒检查一次超时操作。 @@ -248,13 +255,14 @@ var DefaultCosocketManager = NewCosocketManager() func NewCosocketManager() *CosocketManager { ctx, cancel := context.WithCancel(context.Background()) cm := &CosocketManager{ - operations: make(map[uint64]*SocketOperation), - nextID: 0, - timeoutChecker: time.NewTicker(30 * time.Second), - ctx: ctx, - cancel: cancel, - defaultTimeout: 60 * time.Second, - cleanupInterval: 30 * time.Second, + operations: make(map[uint64]*SocketOperation), + nextID: 0, + timeoutChecker: time.NewTicker(30 * time.Second), + ctx: ctx, + cancel: cancel, + defaultTimeout: 60 * time.Second, + cleanupInterval: 30 * time.Second, + DisableSSRFGuard: testingSSRFGuardDisabled, } // 启动清理循环 diff --git a/internal/lua/socket_test.go b/internal/lua/socket_test.go index 6bd6b7f..fbd5aba 100644 --- a/internal/lua/socket_test.go +++ b/internal/lua/socket_test.go @@ -11,6 +11,12 @@ import ( "time" ) +func init() { + // 测试环境允许回环地址连接(SSRF 防护对 localhost mock 服务器放宽) + DefaultCosocketManager.DisableSSRFGuard = true + testingSSRFGuardDisabled = true +} + // mockEchoServer 模拟 echo 服务器 func mockEchoServer(t *testing.T, addr string) (net.Listener, func()) { ln, err := net.Listen("tcp", addr) diff --git a/internal/middleware/security/ratelimit.go b/internal/middleware/security/ratelimit.go index 730106d..8c9ed83 100644 --- a/internal/middleware/security/ratelimit.go +++ b/internal/middleware/security/ratelimit.go @@ -550,12 +550,12 @@ func NewConnLimiter(maxConns int, perKey bool, keyType string) (*ConnLimiter, er // - bool: true 表示成功获取,false 表示已达上限 func (cl *ConnLimiter) Acquire(ctx *fasthttp.RequestCtx) bool { if !cl.perKey { - // 全局限制 - current := loadInt64(&cl.current) - if current >= int64(cl.max) { + // 全局限制(原子递增后检查溢出,避免 TOCTOU 竞态) + current := atomic.AddInt64(&cl.current, 1) + if current > int64(cl.max) { + atomic.AddInt64(&cl.current, -1) return false } - addInt64(&cl.current, 1) return true } @@ -640,11 +640,6 @@ func (m *connLimiterMiddleware) Process(next fasthttp.RequestHandler) fasthttp.R // 连接数原子操作辅助函数 -// loadInt64 原子加载 int64 值。 -func loadInt64(ptr *int64) int64 { - return atomic.LoadInt64(ptr) -} - // addInt64 原子添加 int64 增量。 func addInt64(ptr *int64, delta int64) { diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index 06eefc8..8bc1b6c 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -32,11 +32,13 @@ package proxy import ( + "bytes" "context" "errors" "fmt" "hash/fnv" "net" + urlpath "path" "slices" "strings" "sync" @@ -582,6 +584,14 @@ func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) { path = []byte(target.ProxyURI) } + // 检查路径中的危险字符(防止 Proxy URI 注入) + if bytes.ContainsAny(path, "@\r\n") { + logging.Warn().Msgf("rejected suspicious proxy path containing dangerous chars: %s", path) + upstreamStatus = 502 + utils.SendErrorWithDetail(ctx, utils.ErrBadGateway, "invalid proxy path") + return + } + targetURI := make([]byte, 0, len(target.URL)+len(path)+len(query)+1) targetURI = append(targetURI, target.URL...) targetURI = append(targetURI, path...) @@ -725,8 +735,11 @@ func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) { // 检测 X-Accel-Redirect 头,支持内部重定向 if redirectPath := ctx.Response.Header.Peek("X-Accel-Redirect"); len(redirectPath) > 0 { - utils.SetInternalRedirect(ctx, string(redirectPath)) - ctx.Request.SetRequestURI(string(redirectPath)) + pathStr := urlpath.Clean(string(redirectPath)) + if !strings.HasPrefix(pathStr, "/internal/") && !strings.HasPrefix(pathStr, "/admin/") { + utils.SetInternalRedirect(ctx, pathStr) + ctx.Request.SetRequestURI(pathStr) + } return } @@ -1110,6 +1123,10 @@ func (p *Proxy) modifyRequestHeaders(ctx *fasthttp.RequestCtx, target *loadbalan defer variable.ReleaseContext(vc) for key, value := range p.config.Headers.SetRequest { expanded := vc.Expand(value) + if containsCRLF(expanded) { + logging.Warn().Msgf("rejected CRLF in header value: %s", key) + continue + } headers.Set(key, expanded) } } @@ -1176,6 +1193,10 @@ func (p *Proxy) modifyResponseHeaders(ctx *fasthttp.RequestCtx) { defer variable.ReleaseContext(vc) for key, value := range p.config.Headers.SetResponse { expanded := vc.Expand(value) + if containsCRLF(expanded) { + logging.Warn().Msgf("rejected CRLF in header value: %s", key) + continue + } respHeaders.Set(key, expanded) } } diff --git a/internal/proxy/validate.go b/internal/proxy/validate.go new file mode 100644 index 0000000..edee1b6 --- /dev/null +++ b/internal/proxy/validate.go @@ -0,0 +1,10 @@ +package proxy + +import "strings" + +// containsCRLF 检查字符串是否包含回车或换行字符。 +// +// 用于防止 CRLF 注入攻击。 +func containsCRLF(s string) bool { + return strings.ContainsAny(s, "\r\n") +} diff --git a/internal/proxy/validate_test.go b/internal/proxy/validate_test.go new file mode 100644 index 0000000..72228ec --- /dev/null +++ b/internal/proxy/validate_test.go @@ -0,0 +1,28 @@ +package proxy + +import "testing" + +func TestContainsCRLF(t *testing.T) { + tests := []struct { + name string + input string + expected bool + }{ + {"empty", "", false}, + {"normal", "normal value", false}, + {"CRLF", "with\r\nCRLF", true}, + {"LF only", "with\nLF", true}, + {"CR only", "with\rCR", true}, + {"https url", "https://example.com", false}, + {"tab", "with\ttab", false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := containsCRLF(tt.input) + if result != tt.expected { + t.Errorf("containsCRLF(%q) = %v, want %v", tt.input, result, tt.expected) + } + }) + } +}