mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-12 02:06:37 +08:00
fix(openflare): trust Cloudflare client IP ranges by default
This commit is contained in:
@@ -10,6 +10,10 @@ sidebar: false
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### 🛠 修复
|
||||||
|
|
||||||
|
- 默认信任 Cloudflare 官方 IPv4/IPv6 网段,并允许管理员追加或覆盖可信代理 CIDR;显式 `[]` 可关闭信任。客户端地址恢复后统一用于访问日志、WAF、`X-Real-IP` 和 `X-Forwarded-For` 追加项,旧版自定义 OpenResty 主模板也会自动补入相关指令。
|
||||||
|
|
||||||
## [v3.5.7] - 2026-10-07
|
## [v3.5.7] - 2026-10-07
|
||||||
|
|
||||||
### ✨ 新功能
|
### ✨ 新功能
|
||||||
|
|||||||
@@ -259,6 +259,9 @@ Server 的所有核心基础配置定义在 `config.yaml` 中,且均支持环
|
|||||||
| `openresty_default_limit_rate` | `string` | 站点未配置时的默认单请求带宽(如 `512k`);空表示默认关闭 | 空 |
|
| `openresty_default_limit_rate` | `string` | 站点未配置时的默认单请求带宽(如 `512k`);空表示默认关闭 | 空 |
|
||||||
| `openresty_default_limit_req_per_ip` | `string` | 站点未配置时的默认单 IP 请求频率限制(如 `10r/s`、`100r/m`);空表示默认关闭 | 空 |
|
| `openresty_default_limit_req_per_ip` | `string` | 站点未配置时的默认单 IP 请求频率限制(如 `10r/s`、`100r/m`);空表示默认关闭 | 空 |
|
||||||
| `openresty_main_config_template` | `string` | 允许用户完全重写整个 OpenResty nginx.conf 的底层结构大骨架模板 | 空 (内置缺省骨架) |
|
| `openresty_main_config_template` | `string` | 允许用户完全重写整个 OpenResty nginx.conf 的底层结构大骨架模板 | 空 (内置缺省骨架) |
|
||||||
|
| `openresty_trusted_proxy_cidrs` | `string` (JSON 数组) | 可读取 `CF-Connecting-IP` 的可信代理 IPv4/IPv6 CIDR 列表;默认包含 Cloudflare 官方 22 个 IPv4/IPv6 网段;显式 `[]` 会关闭自动生成的可信代理配置 | Cloudflare 官方网段 |
|
||||||
|
|
||||||
|
在管理端「系统管理 → 系统配置 → 业务配置」中编辑 `openresty_trusted_proxy_cidrs`。默认值为 Cloudflare 官方完整 22 个 IPv4/IPv6 网段,可从 [Cloudflare IPv4 网段](https://www.cloudflare.com/ips-v4) 和 [IPv6 网段](https://www.cloudflare.com/ips-v6) 核对并按官方更新维护。若要信任额外内网代理,应将其 CIDR 追加到现有数组;例如 `10.2.0.227` 只有在确认它是可信代理、且它发送的 `CF-Connecting-IP` 真实可靠后,才追加 `10.2.0.227/32`。该地址不属于默认信任范围。显式设置为 `[]` 会关闭由此配置生成的转发头信任;已有显式空值不会在升级时自动改为默认。自定义主模板中手工写入的 Real-IP 指令仍按模板生效。源站请求必须带有真实的 `CF-Connecting-IP`。保存后需重新生成并发布节点配置版本。Nginx Real-IP 会按 `CF-Connecting-IP` 更新 `$remote_addr`,访问日志、WAF 和上游 `X-Real-IP` 使用这个单一客户端地址;`X-Forwarded-For` 保留原代理链并追加恢复后的客户端地址。旧版自定义主模板即使没有新占位符,也会在 `http {}` 中自动插入 Real-IP 指令;若模板已有 `real_ip_header`,该模板控制使用的头,设置仅追加可信来源 CIDR。数据库中缺少该配置项时使用 Cloudflare 默认网段;非法 JSON 或读取配置失败时按空列表关闭信任。
|
||||||
|
|
||||||
### 7. 源站错误页 (Origin Error Page)
|
### 7. 源站错误页 (Origin Error Page)
|
||||||
|
|
||||||
|
|||||||
@@ -531,6 +531,7 @@ func diffOpenRestyOptionDetails(left openRestyConfigSnapshot, right openRestyCon
|
|||||||
appendIfChanged("OpenRestyCacheLockEnabled", strconv.FormatBool(left.CacheLockEnabled), strconv.FormatBool(right.CacheLockEnabled))
|
appendIfChanged("OpenRestyCacheLockEnabled", strconv.FormatBool(left.CacheLockEnabled), strconv.FormatBool(right.CacheLockEnabled))
|
||||||
appendIfChanged("OpenRestyCacheLockTimeout", left.CacheLockTimeout, right.CacheLockTimeout)
|
appendIfChanged("OpenRestyCacheLockTimeout", left.CacheLockTimeout, right.CacheLockTimeout)
|
||||||
appendIfChanged("OpenRestyCacheUseStale", left.CacheUseStale, right.CacheUseStale)
|
appendIfChanged("OpenRestyCacheUseStale", left.CacheUseStale, right.CacheUseStale)
|
||||||
|
appendIfChanged("OpenRestyTrustedProxyCIDRs", strings.Join(effectiveTrustedProxyCIDRs(left.TrustedProxyCIDRs), ","), strings.Join(effectiveTrustedProxyCIDRs(right.TrustedProxyCIDRs), ","))
|
||||||
appendIfChanged("OpenRestyDefaultLimitConnPerServer", strconv.Itoa(left.DefaultLimitConnPerServer), strconv.Itoa(right.DefaultLimitConnPerServer))
|
appendIfChanged("OpenRestyDefaultLimitConnPerServer", strconv.Itoa(left.DefaultLimitConnPerServer), strconv.Itoa(right.DefaultLimitConnPerServer))
|
||||||
appendIfChanged("OpenRestyDefaultLimitConnPerIP", strconv.Itoa(left.DefaultLimitConnPerIP), strconv.Itoa(right.DefaultLimitConnPerIP))
|
appendIfChanged("OpenRestyDefaultLimitConnPerIP", strconv.Itoa(left.DefaultLimitConnPerIP), strconv.Itoa(right.DefaultLimitConnPerIP))
|
||||||
appendIfChanged("OpenRestyDefaultLimitRate", left.DefaultLimitRate, right.DefaultLimitRate)
|
appendIfChanged("OpenRestyDefaultLimitRate", left.DefaultLimitRate, right.DefaultLimitRate)
|
||||||
@@ -612,6 +613,7 @@ func openRestyOptionKeys() []string {
|
|||||||
"OpenRestyCacheLockEnabled",
|
"OpenRestyCacheLockEnabled",
|
||||||
"OpenRestyCacheLockTimeout",
|
"OpenRestyCacheLockTimeout",
|
||||||
"OpenRestyCacheUseStale",
|
"OpenRestyCacheUseStale",
|
||||||
|
"OpenRestyTrustedProxyCIDRs",
|
||||||
"OpenRestyDefaultLimitConnPerServer",
|
"OpenRestyDefaultLimitConnPerServer",
|
||||||
"OpenRestyDefaultLimitConnPerIP",
|
"OpenRestyDefaultLimitConnPerIP",
|
||||||
"OpenRestyDefaultLimitRate",
|
"OpenRestyDefaultLimitRate",
|
||||||
@@ -625,3 +627,10 @@ func openRestyOptionKeys() []string {
|
|||||||
"SWOfflineDomains",
|
"SWOfflineDomains",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func effectiveTrustedProxyCIDRs(cidrs []string) []string {
|
||||||
|
if cidrs == nil {
|
||||||
|
return openrestyrender.DefaultTrustedProxyCIDRs()
|
||||||
|
}
|
||||||
|
return cidrs
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/Rain-kl/Wavelet/internal/apps/openflare/waf"
|
"github.com/Rain-kl/Wavelet/internal/apps/openflare/waf"
|
||||||
"github.com/Rain-kl/Wavelet/internal/model"
|
"github.com/Rain-kl/Wavelet/internal/model"
|
||||||
"github.com/Rain-kl/Wavelet/internal/repository"
|
"github.com/Rain-kl/Wavelet/internal/repository"
|
||||||
|
"github.com/Rain-kl/Wavelet/pkg/logger"
|
||||||
"github.com/Rain-kl/Wavelet/pkg/protocol"
|
"github.com/Rain-kl/Wavelet/pkg/protocol"
|
||||||
openrestyrender "github.com/Rain-kl/Wavelet/pkg/render/openresty"
|
openrestyrender "github.com/Rain-kl/Wavelet/pkg/render/openresty"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -101,6 +102,7 @@ type snapshotWAFDocument struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type openRestyConfigSnapshot struct {
|
type openRestyConfigSnapshot struct {
|
||||||
|
TrustedProxyCIDRs []string `json:"trusted_proxy_cidrs"`
|
||||||
DefaultServerReturnStatus int `json:"default_server_return_status"`
|
DefaultServerReturnStatus int `json:"default_server_return_status"`
|
||||||
WorkerProcesses string `json:"worker_processes"`
|
WorkerProcesses string `json:"worker_processes"`
|
||||||
WorkerConnections int `json:"worker_connections"`
|
WorkerConnections int `json:"worker_connections"`
|
||||||
@@ -568,6 +570,7 @@ func buildOpenRestyConfigSnapshot(ctx context.Context) openRestyConfigSnapshot {
|
|||||||
CacheLockTimeout: getStringConfig(model.ConfigKeyOpenRestyCacheLockTimeout, "5s"),
|
CacheLockTimeout: getStringConfig(model.ConfigKeyOpenRestyCacheLockTimeout, "5s"),
|
||||||
CacheUseStale: getStringConfig(model.ConfigKeyOpenRestyCacheUseStale, "error timeout updating http_500 http_502 http_503 http_504"),
|
CacheUseStale: getStringConfig(model.ConfigKeyOpenRestyCacheUseStale, "error timeout updating http_500 http_502 http_503 http_504"),
|
||||||
MainConfigTemplate: getStringConfig(model.ConfigKeyOpenRestyMainConfigTemplate, model.DefaultOpenRestyMainConfigTemplate),
|
MainConfigTemplate: getStringConfig(model.ConfigKeyOpenRestyMainConfigTemplate, model.DefaultOpenRestyMainConfigTemplate),
|
||||||
|
TrustedProxyCIDRs: getTrustedProxyCIDRsConfig(ctx),
|
||||||
DefaultLimitConnPerServer: getNonNegIntConfig(model.ConfigKeyOpenRestyDefaultLimitConnPerServer, 0),
|
DefaultLimitConnPerServer: getNonNegIntConfig(model.ConfigKeyOpenRestyDefaultLimitConnPerServer, 0),
|
||||||
DefaultLimitConnPerIP: getNonNegIntConfig(model.ConfigKeyOpenRestyDefaultLimitConnPerIP, 0),
|
DefaultLimitConnPerIP: getNonNegIntConfig(model.ConfigKeyOpenRestyDefaultLimitConnPerIP, 0),
|
||||||
DefaultLimitRate: strings.ToLower(strings.TrimSpace(getStringConfig(model.ConfigKeyOpenRestyDefaultLimitRate, ""))),
|
DefaultLimitRate: strings.ToLower(strings.TrimSpace(getStringConfig(model.ConfigKeyOpenRestyDefaultLimitRate, ""))),
|
||||||
@@ -590,6 +593,26 @@ func buildOpenRestyConfigSnapshot(ctx context.Context) openRestyConfigSnapshot {
|
|||||||
return snapshot
|
return snapshot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseTrustedProxyCIDRs(value string) []string {
|
||||||
|
var cidrs []string
|
||||||
|
if err := json.Unmarshal([]byte(value), &cidrs); err != nil || cidrs == nil {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
return cidrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTrustedProxyCIDRsConfig(ctx context.Context) []string {
|
||||||
|
config, err := repository.GetSystemConfigByKey(ctx, model.ConfigKeyOpenRestyTrustedProxyCIDRs)
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return openrestyrender.DefaultTrustedProxyCIDRs()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
logger.ErrorF(ctx, "[OpenFlareConfig] read trusted proxy CIDRs failed: error=%v", err)
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
return parseTrustedProxyCIDRs(config.Value)
|
||||||
|
}
|
||||||
|
|
||||||
func parseOriginErrorPageStatusCodes(raw string) []string {
|
func parseOriginErrorPageStatusCodes(raw string) []string {
|
||||||
const defaultTag = "500-599"
|
const defaultTag = "500-599"
|
||||||
trimmed := strings.TrimSpace(raw)
|
trimmed := strings.TrimSpace(raw)
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
// Copyright 2026 Arctel.net
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package config_version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
db "github.com/Rain-kl/Wavelet/internal/infra/persistence"
|
||||||
|
"github.com/Rain-kl/Wavelet/internal/model"
|
||||||
|
"github.com/Rain-kl/Wavelet/pkg/cache/ram"
|
||||||
|
openrestyrender "github.com/Rain-kl/Wavelet/pkg/render/openresty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTrustedProxyCIDRsSurviveSnapshotRendering(t *testing.T) {
|
||||||
|
snapshot := snapshotDocument{
|
||||||
|
OpenRestyConfig: openRestyConfigSnapshot{
|
||||||
|
MainConfigTemplate: model.DefaultOpenRestyMainConfigTemplate,
|
||||||
|
TrustedProxyCIDRs: parseTrustedProxyCIDRs(`["173.245.48.0/20","2001:db8:1234::/48"]`),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(snapshot)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rendered, err := renderSnapshotConfig(string(data), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, expected := range []string{"real_ip_header CF-Connecting-IP;", "set_real_ip_from 173.245.48.0/20;", "set_real_ip_from 2001:db8:1234::/48;"} {
|
||||||
|
if !strings.Contains(rendered.MainConfig, expected) {
|
||||||
|
t.Errorf("rendered snapshot missing %q", expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrustedProxyCIDRsSnapshotEmptyAndLegacyDefaults(t *testing.T) {
|
||||||
|
for _, testCase := range []struct {
|
||||||
|
name string
|
||||||
|
json string
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{name: "explicit empty list remains disabled", json: `{"openresty_config":{"trusted_proxy_cidrs":[]}}`, want: 0},
|
||||||
|
{name: "legacy snapshot defaults to Cloudflare ranges", json: `{"openresty_config":{}}`, want: 22},
|
||||||
|
} {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
rendered, err := renderSnapshotConfig(testCase.json, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := strings.Count(rendered.MainConfig, "set_real_ip_from "); got != testCase.want {
|
||||||
|
t.Fatalf("rendered trusted proxy range count = %d, want %d", got, testCase.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrustedProxyCIDRConfigMissingAndExplicitEmpty(t *testing.T) {
|
||||||
|
cleanup := setupOriginErrorPageSnapshotDB(t)
|
||||||
|
defer cleanup()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
missing := buildOpenRestyConfigSnapshot(ctx).TrustedProxyCIDRs
|
||||||
|
if len(missing) != 22 || !slices.Equal(missing, openrestyrender.DefaultTrustedProxyCIDRs()) {
|
||||||
|
t.Fatalf("missing database key defaults to %#v, want the 22 Cloudflare ranges", missing)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.DB(ctx).Create(&model.SystemConfig{Key: model.ConfigKeyOpenRestyTrustedProxyCIDRs, Value: `[]`, Type: "business"}).Error; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
explicitEmpty := buildOpenRestyConfigSnapshot(ctx).TrustedProxyCIDRs
|
||||||
|
if explicitEmpty == nil || len(explicitEmpty) != 0 {
|
||||||
|
t.Fatalf("explicit [] must remain a non-nil empty list, got %#v", explicitEmpty)
|
||||||
|
}
|
||||||
|
payload, err := json.Marshal(snapshotDocument{OpenRestyConfig: buildOpenRestyConfigSnapshot(ctx)})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(payload), `"trusted_proxy_cidrs":[]`) {
|
||||||
|
t.Fatalf("explicit empty list was omitted from snapshot: %s", payload)
|
||||||
|
}
|
||||||
|
rendered, err := renderSnapshotConfig(string(payload), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if strings.Contains(rendered.MainConfig, "set_real_ip_from ") {
|
||||||
|
t.Fatal("explicit empty list still trusts proxy ranges")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.DB(ctx).Model(&model.SystemConfig{}).Where("key = ?", model.ConfigKeyOpenRestyTrustedProxyCIDRs).Update("value", "invalid").Error; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ram.ResetForTest()
|
||||||
|
malformed := buildOpenRestyConfigSnapshot(ctx).TrustedProxyCIDRs
|
||||||
|
if malformed == nil || len(malformed) != 0 {
|
||||||
|
t.Fatalf("malformed configuration must fail closed, got %#v", malformed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrustedProxyCIDRDiffTreatsLegacyNilAsCloudflareDefaults(t *testing.T) {
|
||||||
|
diffs := diffOpenRestyOptionDetails(openRestyConfigSnapshot{}, openRestyConfigSnapshot{TrustedProxyCIDRs: []string{}})
|
||||||
|
for _, diff := range diffs {
|
||||||
|
if diff.Key == "OpenRestyTrustedProxyCIDRs" {
|
||||||
|
if diff.PreviousValue == diff.CurrentValue {
|
||||||
|
t.Fatal("legacy nil and explicit empty list should have different effective values")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatal("trusted proxy CIDR change was not included in config diff")
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ package option
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -17,6 +18,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
maxOriginErrorPageHTMLBytes = 256 << 10 // 256 KiB
|
maxOriginErrorPageHTMLBytes = 256 << 10 // 256 KiB
|
||||||
maxSWOfflineDomains = 1000
|
maxSWOfflineDomains = 1000
|
||||||
|
maxTrustedProxyCIDRs = 256
|
||||||
)
|
)
|
||||||
|
|
||||||
var openRestyOptionValidators = map[string]func(key, value string) error{
|
var openRestyOptionValidators = map[string]func(key, value string) error{
|
||||||
@@ -57,6 +59,7 @@ var openRestyOptionValidators = map[string]func(key, value string) error{
|
|||||||
model.ConfigKeyOpenRestyCacheKeyTemplate: validateOpenRestyCacheKeyTemplate,
|
model.ConfigKeyOpenRestyCacheKeyTemplate: validateOpenRestyCacheKeyTemplate,
|
||||||
model.ConfigKeyOpenRestyCacheUseStale: validateOpenRestyCacheUseStale,
|
model.ConfigKeyOpenRestyCacheUseStale: validateOpenRestyCacheUseStale,
|
||||||
model.ConfigKeyOpenRestyMainConfigTemplate: validateOpenRestyMainConfigTemplate,
|
model.ConfigKeyOpenRestyMainConfigTemplate: validateOpenRestyMainConfigTemplate,
|
||||||
|
model.ConfigKeyOpenRestyTrustedProxyCIDRs: validateOpenRestyTrustedProxyCIDRs,
|
||||||
model.ConfigKeyOpenRestyDefaultLimitConnPerServer: validateNonNegativeIntegerOption,
|
model.ConfigKeyOpenRestyDefaultLimitConnPerServer: validateNonNegativeIntegerOption,
|
||||||
model.ConfigKeyOpenRestyDefaultLimitConnPerIP: validateNonNegativeIntegerOption,
|
model.ConfigKeyOpenRestyDefaultLimitConnPerIP: validateNonNegativeIntegerOption,
|
||||||
model.ConfigKeyOpenRestyDefaultLimitRate: validateOpenRestyDefaultLimitRate,
|
model.ConfigKeyOpenRestyDefaultLimitRate: validateOpenRestyDefaultLimitRate,
|
||||||
@@ -204,6 +207,23 @@ func validateOpenRestyMainConfigTemplate(key, value string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateOpenRestyTrustedProxyCIDRs(key, value string) error {
|
||||||
|
var cidrs []string
|
||||||
|
if err := json.Unmarshal([]byte(value), &cidrs); err != nil || cidrs == nil {
|
||||||
|
return fmt.Errorf("%s 必须为 JSON 字符串数组", key)
|
||||||
|
}
|
||||||
|
if len(cidrs) > maxTrustedProxyCIDRs {
|
||||||
|
return fmt.Errorf("%s 最多允许 %d 个网段", key, maxTrustedProxyCIDRs)
|
||||||
|
}
|
||||||
|
for _, cidr := range cidrs {
|
||||||
|
prefix, err := netip.ParsePrefix(strings.TrimSpace(cidr))
|
||||||
|
if err != nil || prefix != prefix.Masked() {
|
||||||
|
return fmt.Errorf("%s 包含无效网段 %q", key, cidr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func validateOpenRestyDefaultLimitRate(key, trimmed string) error {
|
func validateOpenRestyDefaultLimitRate(key, trimmed string) error {
|
||||||
if trimmed == "" || trimmed == "0" {
|
if trimmed == "" || trimmed == "0" {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -70,6 +70,14 @@ func TestValidateOriginErrorPageStatusCodes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateOpenRestyTrustedProxyCIDRs(t *testing.T) {
|
||||||
|
require.NoError(t, validateOpenRestyOption(model.ConfigKeyOpenRestyTrustedProxyCIDRs, `[]`))
|
||||||
|
require.NoError(t, validateOpenRestyOption(model.ConfigKeyOpenRestyTrustedProxyCIDRs, `["173.245.48.0/20","10.2.0.0/24"]`))
|
||||||
|
for _, value := range []string{`null`, `10.2.0.0/24`, `["10.2.0.0/24;return 200"]`, `["]`} {
|
||||||
|
require.Error(t, validateOpenRestyOption(model.ConfigKeyOpenRestyTrustedProxyCIDRs, value), "value %q must be rejected", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestValidateOriginErrorPageHTML(t *testing.T) {
|
func TestValidateOriginErrorPageHTML(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
-- +goose Up
|
||||||
|
INSERT INTO w_system_configs (key, value, type, visibility, description, created_at, updated_at)
|
||||||
|
VALUES ('openresty_trusted_proxy_cidrs', '["173.245.48.0/20","103.21.244.0/22","103.22.200.0/22","103.31.4.0/22","141.101.64.0/18","108.162.192.0/18","190.93.240.0/20","188.114.96.0/20","197.234.240.0/22","198.41.128.0/17","162.158.0.0/15","104.16.0.0/13","104.24.0.0/14","172.64.0.0/13","131.0.72.0/22","2400:cb00::/32","2606:4700::/32","2803:f800::/32","2405:b500::/32","2405:8100::/32","2a06:98c0::/29","2c0f:f248::/32"]', 'business', 0, '可信代理 IPv4/IPv6 CIDR JSON 数组', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||||
|
ON CONFLICT (key) DO NOTHING;
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
DELETE FROM w_system_configs WHERE key = 'openresty_trusted_proxy_cidrs';
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
-- +goose Up
|
||||||
|
INSERT INTO w_system_configs (key, value, type, visibility, description, created_at, updated_at)
|
||||||
|
VALUES ('openresty_trusted_proxy_cidrs', '["173.245.48.0/20","103.21.244.0/22","103.22.200.0/22","103.31.4.0/22","141.101.64.0/18","108.162.192.0/18","190.93.240.0/20","188.114.96.0/20","197.234.240.0/22","198.41.128.0/17","162.158.0.0/15","104.16.0.0/13","104.24.0.0/14","172.64.0.0/13","131.0.72.0/22","2400:cb00::/32","2606:4700::/32","2803:f800::/32","2405:b500::/32","2405:8100::/32","2a06:98c0::/29","2c0f:f248::/32"]', 'business', 0, '可信代理 IPv4/IPv6 CIDR JSON 数组', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||||
|
ON CONFLICT (key) DO NOTHING;
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
DELETE FROM w_system_configs WHERE key = 'openresty_trusted_proxy_cidrs';
|
||||||
@@ -5,7 +5,9 @@ package migrator
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -13,6 +15,7 @@ import (
|
|||||||
db "github.com/Rain-kl/Wavelet/internal/infra/persistence"
|
db "github.com/Rain-kl/Wavelet/internal/infra/persistence"
|
||||||
"github.com/Rain-kl/Wavelet/internal/model"
|
"github.com/Rain-kl/Wavelet/internal/model"
|
||||||
"github.com/Rain-kl/Wavelet/internal/repository"
|
"github.com/Rain-kl/Wavelet/internal/repository"
|
||||||
|
openrestyrender "github.com/Rain-kl/Wavelet/pkg/render/openresty"
|
||||||
"github.com/alicebob/miniredis/v2"
|
"github.com/alicebob/miniredis/v2"
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
@@ -24,7 +27,38 @@ import (
|
|||||||
// (初始系统配置 + 各期配置迁移/新增 seed:of_options 迁移、文件白名单、磁盘缓存、
|
// (初始系统配置 + 各期配置迁移/新增 seed:of_options 迁移、文件白名单、磁盘缓存、
|
||||||
// 登录会话 TTL、升级源、存储、FRPS Web UI、Pages、OpenResty 限流、单 IP 限频、
|
// 登录会话 TTL、升级源、存储、FRPS Web UI、Pages、OpenResty 限流、单 IP 限频、
|
||||||
// 错误页、SW 离线、日志保留期、指标保留期等);新增配置 seed 迁移时需同步更新本常量。
|
// 错误页、SW 离线、日志保留期、指标保留期等);新增配置 seed 迁移时需同步更新本常量。
|
||||||
const expectedMigratedSystemConfigCount = 96
|
const expectedMigratedSystemConfigCount = 97
|
||||||
|
|
||||||
|
func TestTrustedProxyCIDRMigrationDefaultsMatchRenderer(t *testing.T) {
|
||||||
|
want := openrestyrender.DefaultTrustedProxyCIDRs()
|
||||||
|
if len(want) != 22 {
|
||||||
|
t.Fatalf("renderer default contains %d proxy ranges, want 22", len(want))
|
||||||
|
}
|
||||||
|
for _, migrationPath := range []string{
|
||||||
|
"goose/postgres/202610070005_add_openresty_trusted_proxy_cidrs.sql",
|
||||||
|
"goose/sqlite/202610070005_add_openresty_trusted_proxy_cidrs.sql",
|
||||||
|
} {
|
||||||
|
migration, err := fs.ReadFile(migrationFS, migrationPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
seed, ok := strings.CutPrefix(string(migration), "-- +goose Up\nINSERT INTO w_system_configs (key, value, type, visibility, description, created_at, updated_at)\nVALUES ('openresty_trusted_proxy_cidrs', '")
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("%s does not contain the expected CIDR seed", migrationPath)
|
||||||
|
}
|
||||||
|
seed, _, ok = strings.Cut(seed, "', 'business'")
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("%s CIDR seed value is malformed", migrationPath)
|
||||||
|
}
|
||||||
|
var got []string
|
||||||
|
if err := json.Unmarshal([]byte(seed), &got); err != nil {
|
||||||
|
t.Fatalf("decode %s CIDR seed: %v", migrationPath, err)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got, want) {
|
||||||
|
t.Errorf("%s CIDR seed differs from renderer default", migrationPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGooseMigrationVersionsAreUniquePerDialect(t *testing.T) {
|
func TestGooseMigrationVersionsAreUniquePerDialect(t *testing.T) {
|
||||||
for _, dir := range []string{"goose/postgres", "goose/sqlite"} {
|
for _, dir := range []string{"goose/postgres", "goose/sqlite"} {
|
||||||
@@ -88,6 +122,17 @@ func TestMigrateInitializesSQLiteDatabase(t *testing.T) {
|
|||||||
if systemConfigCount != expectedMigratedSystemConfigCount {
|
if systemConfigCount != expectedMigratedSystemConfigCount {
|
||||||
t.Errorf("Migrate() w_system_configs count = %d, want %d", systemConfigCount, expectedMigratedSystemConfigCount)
|
t.Errorf("Migrate() w_system_configs count = %d, want %d", systemConfigCount, expectedMigratedSystemConfigCount)
|
||||||
}
|
}
|
||||||
|
var trustedProxyConfig model.SystemConfig
|
||||||
|
if err := sqliteDB.Where("key = ?", model.ConfigKeyOpenRestyTrustedProxyCIDRs).First(&trustedProxyConfig).Error; err != nil {
|
||||||
|
t.Fatalf("Migrate() trusted proxy CIDR config error = %v", err)
|
||||||
|
}
|
||||||
|
var trustedProxyCIDRs []string
|
||||||
|
if err := json.Unmarshal([]byte(trustedProxyConfig.Value), &trustedProxyCIDRs); err != nil {
|
||||||
|
t.Fatalf("decode trusted proxy CIDR config: %v", err)
|
||||||
|
}
|
||||||
|
if !slices.Equal(trustedProxyCIDRs, openrestyrender.DefaultTrustedProxyCIDRs()) {
|
||||||
|
t.Errorf("Migrate() trusted proxy CIDRs = %#v, want default Cloudflare ranges", trustedProxyCIDRs)
|
||||||
|
}
|
||||||
|
|
||||||
var logMigrationConfig model.SystemConfig
|
var logMigrationConfig model.SystemConfig
|
||||||
if err := sqliteDB.Where("key = ?", model.ConfigKeyLogDBMigration).First(&logMigrationConfig).Error; err != nil {
|
if err := sqliteDB.Where("key = ?", model.ConfigKeyLogDBMigration).First(&logMigrationConfig).Error; err != nil {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ http {
|
|||||||
client_header_timeout {{OpenRestyClientHeaderTimeout}};
|
client_header_timeout {{OpenRestyClientHeaderTimeout}};
|
||||||
client_body_timeout {{OpenRestyClientBodyTimeout}};
|
client_body_timeout {{OpenRestyClientBodyTimeout}};
|
||||||
client_max_body_size {{OpenRestyClientMaxBodySize}};
|
client_max_body_size {{OpenRestyClientMaxBodySize}};
|
||||||
|
{{OpenRestyRealIPDirectives}}
|
||||||
large_client_header_buffers {{OpenRestyLargeClientHeaderBuffers}};
|
large_client_header_buffers {{OpenRestyLargeClientHeaderBuffers}};
|
||||||
send_timeout {{OpenRestySendTimeout}};
|
send_timeout {{OpenRestySendTimeout}};
|
||||||
proxy_connect_timeout {{OpenRestyProxyConnectTimeout}};
|
proxy_connect_timeout {{OpenRestyProxyConnectTimeout}};
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ const (
|
|||||||
ConfigKeyOpenRestyCacheLockTimeout = "openresty_cache_lock_timeout" // 缓存锁超时
|
ConfigKeyOpenRestyCacheLockTimeout = "openresty_cache_lock_timeout" // 缓存锁超时
|
||||||
ConfigKeyOpenRestyCacheUseStale = "openresty_cache_use_stale" // 缓存失效策略
|
ConfigKeyOpenRestyCacheUseStale = "openresty_cache_use_stale" // 缓存失效策略
|
||||||
ConfigKeyOpenRestyMainConfigTemplate = "openresty_main_config_template" // 主配置模板
|
ConfigKeyOpenRestyMainConfigTemplate = "openresty_main_config_template" // 主配置模板
|
||||||
|
ConfigKeyOpenRestyTrustedProxyCIDRs = "openresty_trusted_proxy_cidrs" // 可信代理 CIDR JSON 数组
|
||||||
ConfigKeyOpenRestyDefaultLimitConnPerServer = "openresty_default_limit_conn_per_server" // 默认站点并发连接
|
ConfigKeyOpenRestyDefaultLimitConnPerServer = "openresty_default_limit_conn_per_server" // 默认站点并发连接
|
||||||
ConfigKeyOpenRestyDefaultLimitConnPerIP = "openresty_default_limit_conn_per_ip" // 默认单 IP 并发连接
|
ConfigKeyOpenRestyDefaultLimitConnPerIP = "openresty_default_limit_conn_per_ip" // 默认单 IP 并发连接
|
||||||
ConfigKeyOpenRestyDefaultLimitRate = "openresty_default_limit_rate" // 默认单请求带宽
|
ConfigKeyOpenRestyDefaultLimitRate = "openresty_default_limit_rate" // 默认单请求带宽
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -170,7 +171,18 @@ func DedupeSupportFiles(files []SupportFile) []SupportFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func renderMainConfigTemplate(templateText string, cfg ConfigSnapshot, limitReqRates []string) string {
|
func renderMainConfigTemplate(templateText string, cfg ConfigSnapshot, limitReqRates []string) string {
|
||||||
|
trustedProxyCIDRs := cfg.TrustedProxyCIDRs
|
||||||
|
if trustedProxyCIDRs == nil {
|
||||||
|
trustedProxyCIDRs = DefaultTrustedProxyCIDRs()
|
||||||
|
}
|
||||||
|
trustedProxyDirectives := renderTrustedProxyDirectives(trustedProxyCIDRs, !hasNginxDirective(templateText, "real_ip_header"))
|
||||||
|
if !strings.Contains(templateText, RealIPDirectivesPlaceholder) && trustedProxyDirectives != "" {
|
||||||
|
// This required token expands to a complete map block in the http context.
|
||||||
|
// Injecting before it avoids depending on formatting or splitting directives.
|
||||||
|
templateText = strings.Replace(templateText, "{{OpenRestyConnectionUpgradeMap}}", trustedProxyDirectives+"{{OpenRestyConnectionUpgradeMap}}", 1)
|
||||||
|
}
|
||||||
replacer := strings.NewReplacer(
|
replacer := strings.NewReplacer(
|
||||||
|
RealIPDirectivesPlaceholder, trustedProxyDirectives,
|
||||||
"{{OpenRestyWorkerProcesses}}", cfg.WorkerProcesses,
|
"{{OpenRestyWorkerProcesses}}", cfg.WorkerProcesses,
|
||||||
"{{OpenRestyWorkerConnections}}", strconv.Itoa(cfg.WorkerConnections),
|
"{{OpenRestyWorkerConnections}}", strconv.Itoa(cfg.WorkerConnections),
|
||||||
"{{OpenRestyWorkerRlimitNofile}}", strconv.Itoa(cfg.WorkerRlimitNofile),
|
"{{OpenRestyWorkerRlimitNofile}}", strconv.Itoa(cfg.WorkerRlimitNofile),
|
||||||
@@ -205,6 +217,37 @@ func renderMainConfigTemplate(templateText string, cfg ConfigSnapshot, limitReqR
|
|||||||
return replacer.Replace(templateText)
|
return replacer.Replace(templateText)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasNginxDirective(templateText string, directive string) bool {
|
||||||
|
for _, line := range strings.Split(templateText, "\n") {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fields := strings.Fields(line); len(fields) > 0 && fields[0] == directive {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderTrustedProxyDirectives(cidrs []string, includeHeader bool) string {
|
||||||
|
if len(cidrs) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var builder strings.Builder
|
||||||
|
if includeHeader {
|
||||||
|
builder.WriteString(" real_ip_header CF-Connecting-IP;\n real_ip_recursive on;\n")
|
||||||
|
}
|
||||||
|
for _, cidr := range cidrs {
|
||||||
|
prefix, err := netip.ParsePrefix(strings.TrimSpace(cidr))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&builder, " set_real_ip_from %s;\n", prefix.Masked())
|
||||||
|
}
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
func renderTemplateDirective(enabled bool, statement string) string {
|
func renderTemplateDirective(enabled bool, statement string) string {
|
||||||
if !enabled {
|
if !enabled {
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -9,6 +9,56 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestRenderMainConfigTrustedProxyCIDRs(t *testing.T) {
|
||||||
|
base := ConfigSnapshot{MainConfigTemplate: defaultMainConfigTemplate}
|
||||||
|
withDefaultTrust := RenderMainConfig(Document{OpenRestyConfig: base})
|
||||||
|
if count := strings.Count(withDefaultTrust, "set_real_ip_from "); count != 22 {
|
||||||
|
t.Fatalf("default config trusts %d proxy ranges, want all 22 Cloudflare ranges", count)
|
||||||
|
}
|
||||||
|
if !strings.Contains(withDefaultTrust, "set_real_ip_from 173.245.48.0/20;") || !strings.Contains(withDefaultTrust, "set_real_ip_from 2c0f:f248::/32;") {
|
||||||
|
t.Fatal("default config is missing Cloudflare IPv4 or IPv6 ranges")
|
||||||
|
}
|
||||||
|
|
||||||
|
base.TrustedProxyCIDRs = []string{}
|
||||||
|
withoutTrust := RenderMainConfig(Document{OpenRestyConfig: base})
|
||||||
|
if strings.Contains(withoutTrust, "real_ip_header") || strings.Contains(withoutTrust, "set_real_ip_from") {
|
||||||
|
t.Fatal("explicit empty trusted-proxy list must disable forwarded-header trust")
|
||||||
|
}
|
||||||
|
|
||||||
|
base.TrustedProxyCIDRs = []string{"173.245.48.0/20", "10.2.0.0/24", "2001:db8:1234::/48", "192.0.2.7/32"}
|
||||||
|
got := RenderMainConfig(Document{OpenRestyConfig: base})
|
||||||
|
for _, expected := range []string{"real_ip_header CF-Connecting-IP;", "set_real_ip_from 173.245.48.0/20;", "set_real_ip_from 10.2.0.0/24;", "set_real_ip_from 2001:db8:1234::/48;", "set_real_ip_from 192.0.2.7/32;"} {
|
||||||
|
if !strings.Contains(got, expected) {
|
||||||
|
t.Errorf("rendered config missing %q", expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderMainConfigInjectsTrustedProxyForLegacyTemplate(t *testing.T) {
|
||||||
|
legacy := strings.ReplaceAll(defaultMainConfigTemplate, RealIPDirectivesPlaceholder+"\n", "")
|
||||||
|
got := RenderMainConfig(Document{OpenRestyConfig: ConfigSnapshot{MainConfigTemplate: legacy, TrustedProxyCIDRs: []string{"10.2.0.0/24"}}})
|
||||||
|
if !strings.Contains(got, "real_ip_header CF-Connecting-IP;\n real_ip_recursive on;\n set_real_ip_from 10.2.0.0/24;") || !strings.Contains(got, "map $http_upgrade $connection_upgrade {") {
|
||||||
|
t.Fatalf("trusted proxy directives were not injected into legacy template:\n%s", got)
|
||||||
|
}
|
||||||
|
if strings.Contains(got, "access_log real_ip_header") {
|
||||||
|
t.Fatalf("trusted proxy directives corrupted access_log directive:\n%s", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderMainConfigPreservesExplicitRealIPHeader(t *testing.T) {
|
||||||
|
templateText := strings.Replace(defaultMainConfigTemplate, "{{OpenRestyConnectionUpgradeMap}}", "# real_ip_header in comment\nreal_ip_header X-Forwarded-For;\n{{OpenRestyConnectionUpgradeMap}}", 1)
|
||||||
|
got := RenderMainConfig(Document{OpenRestyConfig: ConfigSnapshot{
|
||||||
|
MainConfigTemplate: templateText,
|
||||||
|
TrustedProxyCIDRs: []string{"192.0.2.7/32"},
|
||||||
|
}})
|
||||||
|
if !strings.Contains(got, "real_ip_header X-Forwarded-For;") || strings.Contains(got, "real_ip_header CF-Connecting-IP;") {
|
||||||
|
t.Fatalf("explicit template header selection was not preserved:\n%s", got)
|
||||||
|
}
|
||||||
|
if !strings.Contains(got, "set_real_ip_from 192.0.2.7/32;") {
|
||||||
|
t.Fatalf("trusted CIDR was not added to explicit template header:\n%s", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRenderOpenRestyUsesDedicatedWAFIPGroupSharedDict(t *testing.T) {
|
func TestRenderOpenRestyUsesDedicatedWAFIPGroupSharedDict(t *testing.T) {
|
||||||
block := renderOpenRestyObservabilityTemplateBlock()
|
block := renderOpenRestyObservabilityTemplateBlock()
|
||||||
if !strings.Contains(block, "lua_shared_dict openflare_waf_config 1m;") {
|
if !strings.Contains(block, "lua_shared_dict openflare_waf_config 1m;") {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const (
|
|||||||
PowStaticDirPlaceholder = "__OPENFLARE_POW_STATIC_DIR__"
|
PowStaticDirPlaceholder = "__OPENFLARE_POW_STATIC_DIR__"
|
||||||
PagesDirPlaceholder = "__OPENFLARE_PAGES_DIR__"
|
PagesDirPlaceholder = "__OPENFLARE_PAGES_DIR__"
|
||||||
ErrorPageTmplPlaceholder = "__OPENFLARE_ERROR_PAGE_TMPL__"
|
ErrorPageTmplPlaceholder = "__OPENFLARE_ERROR_PAGE_TMPL__"
|
||||||
|
RealIPDirectivesPlaceholder = "{{OpenRestyRealIPDirectives}}"
|
||||||
SWDirPlaceholder = "__OPENFLARE_SW_DIR__"
|
SWDirPlaceholder = "__OPENFLARE_SW_DIR__"
|
||||||
|
|
||||||
SourceConfigFileName = "openresty_config.json"
|
SourceConfigFileName = "openresty_config.json"
|
||||||
@@ -102,6 +103,7 @@ http {
|
|||||||
gzip {{OpenRestyGzip}};
|
gzip {{OpenRestyGzip}};
|
||||||
gzip_min_length {{OpenRestyGzipMinLength}};
|
gzip_min_length {{OpenRestyGzipMinLength}};
|
||||||
gzip_comp_level {{OpenRestyGzipCompLevel}};
|
gzip_comp_level {{OpenRestyGzipCompLevel}};
|
||||||
|
{{OpenRestyRealIPDirectives}}
|
||||||
gzip_vary on;
|
gzip_vary on;
|
||||||
gzip_types text/plain text/css text/xml application/javascript application/json application/xml application/rss+xml application/atom+xml image/svg+xml;
|
gzip_types text/plain text/css text/xml application/javascript application/json application/xml application/rss+xml application/atom+xml image/svg+xml;
|
||||||
{{OpenRestyResolverDirective}}{{OpenRestyCacheBlock}} include {{OpenRestyRouteConfigInclude}};
|
{{OpenRestyResolverDirective}}{{OpenRestyCacheBlock}} include {{OpenRestyRouteConfigInclude}};
|
||||||
@@ -281,6 +283,7 @@ type WAFDocument struct {
|
|||||||
// ConfigSnapshot holds the full set of OpenResty tuning parameters that are
|
// ConfigSnapshot holds the full set of OpenResty tuning parameters that are
|
||||||
// rendered into the nginx main configuration template.
|
// rendered into the nginx main configuration template.
|
||||||
type ConfigSnapshot struct {
|
type ConfigSnapshot struct {
|
||||||
|
TrustedProxyCIDRs []string `json:"trusted_proxy_cidrs"`
|
||||||
DefaultServerReturnStatus int `json:"default_server_return_status"`
|
DefaultServerReturnStatus int `json:"default_server_return_status"`
|
||||||
WorkerProcesses string `json:"worker_processes"`
|
WorkerProcesses string `json:"worker_processes"`
|
||||||
WorkerConnections int `json:"worker_connections"`
|
WorkerConnections int `json:"worker_connections"`
|
||||||
@@ -335,6 +338,20 @@ type ConfigSnapshot struct {
|
|||||||
SWOfflineDomains []string `json:"sw_offline_domains,omitempty"`
|
SWOfflineDomains []string `json:"sw_offline_domains,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultTrustedProxyCIDRs returns Cloudflare's published IPv4 and IPv6
|
||||||
|
// networks. Callers can override the list, including with an explicit empty
|
||||||
|
// slice to disable trusted-proxy processing.
|
||||||
|
func DefaultTrustedProxyCIDRs() []string {
|
||||||
|
return []string{
|
||||||
|
"173.245.48.0/20", "103.21.244.0/22", "103.22.200.0/22", "103.31.4.0/22",
|
||||||
|
"141.101.64.0/18", "108.162.192.0/18", "190.93.240.0/20", "188.114.96.0/20",
|
||||||
|
"197.234.240.0/22", "198.41.128.0/17", "162.158.0.0/15", "104.16.0.0/13",
|
||||||
|
"104.24.0.0/14", "172.64.0.0/13", "131.0.72.0/22",
|
||||||
|
"2400:cb00::/32", "2606:4700::/32", "2803:f800::/32", "2405:b500::/32",
|
||||||
|
"2405:8100::/32", "2a06:98c0::/29", "2c0f:f248::/32",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Document is the top-level input structure for the OpenResty renderer,
|
// Document is the top-level input structure for the OpenResty renderer,
|
||||||
// combining routes, OpenResty tuning, and WAF configuration.
|
// combining routes, OpenResty tuning, and WAF configuration.
|
||||||
type Document struct {
|
type Document struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user