refactor(nginx): 抽取共享 Nginx 模板包并发布 v1.5.1
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/fonu/fonu/internal/config"
|
||||
"github.com/fonu/fonu/internal/nginxtmpl"
|
||||
"github.com/fonu/fonu/internal/proxy"
|
||||
)
|
||||
|
||||
@@ -171,9 +172,13 @@ func appendSSLServerBlock(b *strings.Builder, cfg config.Config, port int, serve
|
||||
writeListenDirectives(&block, port, true, rule.ListenIPv4, rule.ListenIPv6)
|
||||
block.WriteString(fmt.Sprintf(" server_name %s;\n\n", serverNames))
|
||||
writeCommentLine(&block, 4, "SSL 证书")
|
||||
block.WriteString(fmt.Sprintf(" ssl_certificate %s;\n", absNginxPath(cert.CertPath)))
|
||||
block.WriteString(fmt.Sprintf(" ssl_certificate_key %s;\n", absNginxPath(cert.KeyPath)))
|
||||
writeTLSProtocols(&block, rule.Security)
|
||||
writeTLSProtocolComment(&block, rule.Security)
|
||||
block.WriteString(nginxtmpl.SSLDirectives(nginxtmpl.TLSConfig{
|
||||
CertPath: absNginxPath(cert.CertPath),
|
||||
KeyPath: absNginxPath(cert.KeyPath),
|
||||
TLS13Only: rule.Security.TLSMin13Only,
|
||||
}))
|
||||
block.WriteString("\n")
|
||||
writeServerSecurityHeaders(&block, rule.Security, true)
|
||||
writeErrorPages(&block, cfg)
|
||||
writeLocationWithSecurity(&block, cfg, rule, opts)
|
||||
@@ -217,11 +222,10 @@ func writeProxyLocationBody(b *strings.Builder, upstream string, sec proxy.Secur
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
proxy_set_header X-Real-Proto $scheme;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
%s
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 3600s;
|
||||
`, sslBlock, upstream, hostHeader))
|
||||
`, sslBlock, upstream, hostHeader, nginxtmpl.UpgradeHeaders(" ", "$connection_upgrade")))
|
||||
}
|
||||
|
||||
func writeListenDirectives(b *strings.Builder, port int, ssl bool, ipv4 bool, ipv6 bool) {
|
||||
|
||||
+37
-41
@@ -6,18 +6,19 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/fonu/fonu/internal/config"
|
||||
"github.com/fonu/fonu/internal/nginxtmpl"
|
||||
"github.com/fonu/fonu/internal/proxy"
|
||||
)
|
||||
|
||||
type GenerateOptions struct {
|
||||
TrustedProxy TrustedProxyConfig
|
||||
GlobalIPBlacklist []string
|
||||
GlobalIPWhitelist []string
|
||||
ChinaCIDRAvailable bool
|
||||
ChinaCIDRPathOverride string
|
||||
GlobalCustomOverride string
|
||||
GlobalCustomPathOverride string
|
||||
RuleCustomOverrides map[int64]string
|
||||
TrustedProxy TrustedProxyConfig
|
||||
GlobalIPBlacklist []string
|
||||
GlobalIPWhitelist []string
|
||||
ChinaCIDRAvailable bool
|
||||
ChinaCIDRPathOverride string
|
||||
GlobalCustomOverride string
|
||||
GlobalCustomPathOverride string
|
||||
RuleCustomOverrides map[int64]string
|
||||
}
|
||||
|
||||
func writeGlobalAllowList(b *strings.Builder, cidrs []string) {
|
||||
@@ -52,11 +53,11 @@ func writeLimitZones(b *strings.Builder, rules []proxy.Rule) {
|
||||
writeLimitZoneComments(b, rule)
|
||||
}
|
||||
if rule.Security.RateLimitEnabled() {
|
||||
rl := rule.Security.RateLimit
|
||||
b.WriteString(fmt.Sprintf(" limit_req_zone $binary_remote_addr zone=fonu_rule_%d_req:10m rate=%dr/s;\n", rule.ID, rl.Rate))
|
||||
zone := fmt.Sprintf("fonu_rule_%d_req", rule.ID)
|
||||
writeIndentedZone(b, nginxtmpl.RateLimitZone(zone, rule.Security.RateLimit.Rate))
|
||||
}
|
||||
if rule.Security.ConnLimitEnabled() {
|
||||
b.WriteString(fmt.Sprintf(" limit_conn_zone $binary_remote_addr zone=fonu_rule_%d_conn:10m;\n", rule.ID))
|
||||
writeIndentedZone(b, nginxtmpl.ConnLimitZone(fmt.Sprintf("fonu_rule_%d_conn", rule.ID)))
|
||||
}
|
||||
}
|
||||
if hasLimitZones(rules) {
|
||||
@@ -64,6 +65,13 @@ func writeLimitZones(b *strings.Builder, rules []proxy.Rule) {
|
||||
}
|
||||
}
|
||||
|
||||
func writeIndentedZone(b *strings.Builder, line string) {
|
||||
if strings.TrimSpace(line) == "" {
|
||||
return
|
||||
}
|
||||
b.WriteString(" " + line)
|
||||
}
|
||||
|
||||
func hasLimitZones(rules []proxy.Rule) bool {
|
||||
for _, rule := range rules {
|
||||
if rule.Enabled && (rule.Security.RateLimitEnabled() || rule.Security.ConnLimitEnabled()) {
|
||||
@@ -135,18 +143,18 @@ func writeChinaOnlyBypassGeo(b *strings.Builder, rules []proxy.Rule, opts Genera
|
||||
}
|
||||
}
|
||||
|
||||
func writeChinaOnlyCheck(b *strings.Builder, rule proxy.Rule, opts GenerateOptions) {
|
||||
func chinaOnlyCheckDirectives(rule proxy.Rule, opts GenerateOptions) string {
|
||||
sec := rule.Security.Normalize()
|
||||
if !sec.ChinaOnly {
|
||||
return
|
||||
return ""
|
||||
}
|
||||
if !opts.ChinaCIDRAvailable {
|
||||
b.WriteString(` if ($fonu_is_private = 0) {
|
||||
return ` if ($fonu_is_private = 0) {
|
||||
return 403;
|
||||
}
|
||||
`)
|
||||
return
|
||||
`
|
||||
}
|
||||
var b strings.Builder
|
||||
b.WriteString(` set $fonu_china_block 0;
|
||||
if ($fonu_is_china = 0) {
|
||||
set $fonu_china_block 1;
|
||||
@@ -163,6 +171,7 @@ func writeChinaOnlyCheck(b *strings.Builder, rule proxy.Rule, opts GenerateOptio
|
||||
return 403;
|
||||
}
|
||||
`)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func writeServerSecurityHeaders(b *strings.Builder, sec proxy.SecurityConfig, https bool) {
|
||||
@@ -170,20 +179,8 @@ func writeServerSecurityHeaders(b *strings.Builder, sec proxy.SecurityConfig, ht
|
||||
return
|
||||
}
|
||||
writeSecurityHeadersComment(b)
|
||||
b.WriteString(` add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
|
||||
`)
|
||||
}
|
||||
|
||||
func writeTLSProtocols(b *strings.Builder, sec proxy.SecurityConfig) {
|
||||
writeTLSProtocolComment(b, sec)
|
||||
if sec.TLSMin13Only {
|
||||
b.WriteString(" ssl_protocols TLSv1.3;\n\n")
|
||||
} else {
|
||||
b.WriteString(" ssl_protocols TLSv1.2 TLSv1.3;\n\n")
|
||||
}
|
||||
b.WriteString(nginxtmpl.ServerSecurityHeaders(nginxtmpl.SecurityHeaders{Enabled: true, HTTPS: true}))
|
||||
b.WriteString("\n")
|
||||
}
|
||||
|
||||
func writeErrorPages(b *strings.Builder, cfg config.Config) {
|
||||
@@ -216,25 +213,24 @@ func writeLocationSecurity(b *strings.Builder, cfg config.Config, rule proxy.Rul
|
||||
sec := rule.Security.Normalize()
|
||||
writeLocationSecurityComments(b, rule, opts)
|
||||
|
||||
for _, cidr := range sec.IPBlacklist {
|
||||
b.WriteString(fmt.Sprintf(" deny %s;\n", cidr))
|
||||
acl := nginxtmpl.AccessControl{
|
||||
Deny: sec.IPBlacklist,
|
||||
ChinaDeny: chinaOnlyCheckDirectives(rule, opts),
|
||||
}
|
||||
|
||||
writeChinaOnlyCheck(b, rule, opts)
|
||||
|
||||
if sec.WhitelistEnabled() {
|
||||
for _, cidr := range sec.IPWhitelist {
|
||||
b.WriteString(fmt.Sprintf(" allow %s;\n", cidr))
|
||||
}
|
||||
b.WriteString(" deny all;\n")
|
||||
acl.Allow = sec.IPWhitelist
|
||||
}
|
||||
b.WriteString(nginxtmpl.AccessDirectives(acl, " "))
|
||||
|
||||
limits := nginxtmpl.LimitConfig{Zone: fmt.Sprintf("fonu_rule_%d", rule.ID)}
|
||||
if sec.ConnLimitEnabled() {
|
||||
b.WriteString(fmt.Sprintf(" limit_conn fonu_rule_%d_conn %d;\n", rule.ID, sec.ConnLimit.Max))
|
||||
limits.Conn = sec.ConnLimit.Max
|
||||
}
|
||||
if sec.RateLimitEnabled() {
|
||||
b.WriteString(fmt.Sprintf(" limit_req zone=fonu_rule_%d_req burst=%d nodelay;\n", rule.ID, sec.RateLimit.Burst))
|
||||
limits.Rate = sec.RateLimit.Rate
|
||||
limits.Burst = sec.RateLimit.Burst
|
||||
}
|
||||
b.WriteString(nginxtmpl.LimitDirectives(limits, " "))
|
||||
|
||||
if sec.BasicAuthEnabled() {
|
||||
path := absNginxPath(HtpasswdPath(cfg, rule.ID))
|
||||
|
||||
Reference in New Issue
Block a user