mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-02 23:06:36 +08:00
fix lint
This commit is contained in:
+51
-126
@@ -1,3 +1,4 @@
|
||||
// Package openresty renders OpenResty configuration from proxy route definitions.
|
||||
package openresty
|
||||
|
||||
import (
|
||||
@@ -16,6 +17,13 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
routeUpstreamTypePages = "pages"
|
||||
indexHTML = "/index.html"
|
||||
)
|
||||
|
||||
// RenderJSON parses the given JSON string as a Document and renders the full
|
||||
// OpenResty configuration bundle, injecting the provided certificate support files.
|
||||
func RenderJSON(sourceJSON string, certificateFiles []SupportFile) (*Result, error) {
|
||||
var doc Document
|
||||
if err := json.Unmarshal([]byte(strings.TrimSpace(sourceJSON)), &doc); err != nil {
|
||||
@@ -24,6 +32,8 @@ func RenderJSON(sourceJSON string, certificateFiles []SupportFile) (*Result, err
|
||||
return Render(doc, certificateFiles)
|
||||
}
|
||||
|
||||
// Render produces a complete OpenResty configuration Result from a Document and
|
||||
// a set of certificate support files.
|
||||
func Render(doc Document, certificateFiles []SupportFile) (*Result, error) {
|
||||
mainConfig := RenderMainConfig(doc.OpenRestyConfig)
|
||||
routeConfig, err := RenderRouteConfig(doc, certificateFiles)
|
||||
@@ -45,6 +55,8 @@ func Render(doc Document, certificateFiles []SupportFile) (*Result, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RenderMainConfig renders the nginx main configuration string from the given
|
||||
// ConfigSnapshot, falling back to the built-in default template when none is set.
|
||||
func RenderMainConfig(cfg ConfigSnapshot) string {
|
||||
templateText := cfg.MainConfigTemplate
|
||||
if strings.TrimSpace(templateText) == "" {
|
||||
@@ -53,6 +65,8 @@ func RenderMainConfig(cfg ConfigSnapshot) string {
|
||||
return renderMainConfigTemplate(templateText, cfg)
|
||||
}
|
||||
|
||||
// ValidateMainConfigTemplate checks that the provided template text is non-empty
|
||||
// and contains all required OpenResty placeholder tokens.
|
||||
func ValidateMainConfigTemplate(templateText string) error {
|
||||
trimmed := strings.TrimSpace(templateText)
|
||||
if trimmed == "" {
|
||||
@@ -66,6 +80,8 @@ func ValidateMainConfigTemplate(templateText string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenderRouteConfig generates the nginx server-block configuration for all
|
||||
// routes in the Document, resolving certificate files as needed.
|
||||
func RenderRouteConfig(doc Document, certificateFiles []SupportFile) (string, error) {
|
||||
var builder strings.Builder
|
||||
builder.WriteString("# This file is generated by OpenFlare. Do not edit manually.\n")
|
||||
@@ -83,120 +99,21 @@ func RenderRouteConfig(doc Document, certificateFiles []SupportFile) (string, er
|
||||
cacheConfig := routeCacheConfig{Enabled: route.CacheEnabled, Policy: route.CachePolicy, Rules: route.CacheRules}
|
||||
limitConfig := routeLimitConfig{LimitConnPerServer: route.LimitConnPerServer, LimitConnPerIP: route.LimitConnPerIP, LimitRate: route.LimitRate}
|
||||
powEnabled, _ := getPoWConfigForRoute(route.ID, doc.WAF)
|
||||
if normalizeRouteUpstreamType(route.UpstreamType) == "pages" {
|
||||
if route.PagesDeployment == nil {
|
||||
return "", fmt.Errorf("route %s pages deployment is missing", route.Domain)
|
||||
}
|
||||
if !route.EnableHTTPS {
|
||||
builder.WriteString(renderHTTPPagesServer(serverNames, displayName, route.PagesDeployment, limitConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword))
|
||||
continue
|
||||
}
|
||||
certIDs := normalizeCertIDs(route.CertID, route.CertIDs)
|
||||
domainCertIDs := normalizeDomainCertIDs(domains, certIDs, route.DomainCertIDs)
|
||||
if len(certIDs) == 0 {
|
||||
return "", fmt.Errorf("路由 %s 未配置证书", route.Domain)
|
||||
}
|
||||
httpOnlyDomains := make([]string, 0, len(domains))
|
||||
domainsByCertID := make(map[uint][]string, len(certIDs))
|
||||
for index, domain := range domains {
|
||||
if index >= len(domainCertIDs) || domainCertIDs[index] == 0 {
|
||||
httpOnlyDomains = append(httpOnlyDomains, domain)
|
||||
continue
|
||||
}
|
||||
domainsByCertID[domainCertIDs[index]] = append(domainsByCertID[domainCertIDs[index]], domain)
|
||||
}
|
||||
for _, certID := range certIDs {
|
||||
assignedDomains := domainsByCertID[certID]
|
||||
if len(assignedDomains) == 0 {
|
||||
continue
|
||||
}
|
||||
certPEM, ok := certificates[certID]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("route %s certificate %d does not exist", route.Domain, certID)
|
||||
}
|
||||
if err := validateCertificateCoverage(certPEM, assignedDomains); err != nil {
|
||||
return "", fmt.Errorf("site %s certificate validation failed: %w", displayName, err)
|
||||
}
|
||||
}
|
||||
if route.RedirectHTTP {
|
||||
if len(httpOnlyDomains) > 0 {
|
||||
builder.WriteString(renderHTTPPagesServer(renderServerNames(httpOnlyDomains), displayName, route.PagesDeployment, limitConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword))
|
||||
}
|
||||
for _, certID := range certIDs {
|
||||
if assignedDomains := domainsByCertID[certID]; len(assignedDomains) > 0 {
|
||||
builder.WriteString(renderHTTPRedirectServer(renderServerNames(assignedDomains)))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
builder.WriteString(renderHTTPPagesServer(serverNames, displayName, route.PagesDeployment, limitConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword))
|
||||
}
|
||||
for _, certID := range certIDs {
|
||||
if assignedDomains := domainsByCertID[certID]; len(assignedDomains) > 0 {
|
||||
builder.WriteString(renderHTTPSPagesServer(renderServerNames(assignedDomains), displayName, certID, route.PagesDeployment, limitConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, doc.OpenRestyConfig))
|
||||
}
|
||||
if normalizeRouteUpstreamType(route.UpstreamType) == routeUpstreamTypePages {
|
||||
if err := renderPagesRoute(&builder, route, displayName, serverNames, certificates, limitConfig, powEnabled, doc.OpenRestyConfig); err != nil {
|
||||
return "", err
|
||||
}
|
||||
continue
|
||||
}
|
||||
upstreams := route.Upstreams
|
||||
if len(upstreams) == 0 && strings.TrimSpace(route.OriginURL) != "" {
|
||||
upstreams = []string{route.OriginURL}
|
||||
}
|
||||
upstreamConfig := buildRouteUpstreamConfig(route, upstreams)
|
||||
if upstreamConfig.UsesNamedUpstream {
|
||||
builder.WriteString(renderNamedUpstreamBlock(upstreamConfig))
|
||||
}
|
||||
if !route.EnableHTTPS {
|
||||
builder.WriteString(renderHTTPProxyServer(serverNames, displayName, route.OriginURL, route.OriginHost, route.CustomHeaders, cacheConfig, limitConfig, upstreamConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, doc.OpenRestyConfig))
|
||||
continue
|
||||
}
|
||||
certIDs := normalizeCertIDs(route.CertID, route.CertIDs)
|
||||
domainCertIDs := normalizeDomainCertIDs(domains, certIDs, route.DomainCertIDs)
|
||||
if len(certIDs) == 0 {
|
||||
return "", fmt.Errorf("路由 %s 未配置证书", route.Domain)
|
||||
}
|
||||
httpOnlyDomains := make([]string, 0, len(domains))
|
||||
domainsByCertID := make(map[uint][]string, len(certIDs))
|
||||
for index, domain := range domains {
|
||||
if index >= len(domainCertIDs) || domainCertIDs[index] == 0 {
|
||||
httpOnlyDomains = append(httpOnlyDomains, domain)
|
||||
continue
|
||||
}
|
||||
domainsByCertID[domainCertIDs[index]] = append(domainsByCertID[domainCertIDs[index]], domain)
|
||||
}
|
||||
for _, certID := range certIDs {
|
||||
assignedDomains := domainsByCertID[certID]
|
||||
if len(assignedDomains) == 0 {
|
||||
continue
|
||||
}
|
||||
certPEM, ok := certificates[certID]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("route %s certificate %d does not exist", route.Domain, certID)
|
||||
}
|
||||
if err := validateCertificateCoverage(certPEM, assignedDomains); err != nil {
|
||||
return "", fmt.Errorf("site %s certificate validation failed: %w", displayName, err)
|
||||
}
|
||||
}
|
||||
if route.RedirectHTTP {
|
||||
if len(httpOnlyDomains) > 0 {
|
||||
builder.WriteString(renderHTTPProxyServer(renderServerNames(httpOnlyDomains), displayName, route.OriginURL, route.OriginHost, route.CustomHeaders, cacheConfig, limitConfig, upstreamConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, doc.OpenRestyConfig))
|
||||
}
|
||||
for _, certID := range certIDs {
|
||||
if assignedDomains := domainsByCertID[certID]; len(assignedDomains) > 0 {
|
||||
builder.WriteString(renderHTTPRedirectServer(renderServerNames(assignedDomains)))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
builder.WriteString(renderHTTPProxyServer(serverNames, displayName, route.OriginURL, route.OriginHost, route.CustomHeaders, cacheConfig, limitConfig, upstreamConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, doc.OpenRestyConfig))
|
||||
}
|
||||
for _, certID := range certIDs {
|
||||
if assignedDomains := domainsByCertID[certID]; len(assignedDomains) > 0 {
|
||||
builder.WriteString(renderHTTPSServer(renderServerNames(assignedDomains), displayName, route.OriginURL, route.OriginHost, certID, route.CustomHeaders, cacheConfig, limitConfig, upstreamConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, doc.OpenRestyConfig))
|
||||
}
|
||||
if err := renderProxyRoute(&builder, route, displayName, serverNames, certificates, cacheConfig, limitConfig, powEnabled, doc.OpenRestyConfig); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
// RenderPoWConfig serialises the Proof-of-Work configuration for all enabled
|
||||
// routes as a JSON string consumed by the OpenResty Lua runtime.
|
||||
func RenderPoWConfig(doc Document) (string, error) {
|
||||
type domainEntry struct {
|
||||
Domains []string `json:"domains"`
|
||||
@@ -218,6 +135,8 @@ func RenderPoWConfig(doc Document) (string, error) {
|
||||
return string(data), err
|
||||
}
|
||||
|
||||
// RenderWAFConfig serialises the WAF runtime configuration (rule groups and
|
||||
// per-site bindings) as a JSON string consumed by the OpenResty Lua runtime.
|
||||
func RenderWAFConfig(snapshot WAFDocument) (string, error) {
|
||||
type wafRuntimeRuleGroup struct {
|
||||
ID uint `json:"id"`
|
||||
@@ -312,6 +231,9 @@ func sortedUniqueUintIDs(values []uint) []uint {
|
||||
return items
|
||||
}
|
||||
|
||||
// ChecksumBundle returns a stable SHA-256 hex digest over the combined content
|
||||
// of the main config, route config, and deduplicated support files, excluding
|
||||
// the source config JSON file itself.
|
||||
func ChecksumBundle(mainConfig string, routeConfig string, supportFiles []SupportFile) string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString(mainConfig)
|
||||
@@ -333,6 +255,8 @@ func ChecksumBundle(mainConfig string, routeConfig string, supportFiles []Suppor
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
// DedupeSupportFiles returns a new slice with duplicate paths removed, keeping
|
||||
// the last occurrence of each path.
|
||||
func DedupeSupportFiles(files []SupportFile) []SupportFile {
|
||||
if len(files) == 0 {
|
||||
return nil
|
||||
@@ -437,21 +361,22 @@ func renderPagesAPIProxyLocationBlock(deployment *PagesDeployment) string {
|
||||
cleanPath := strings.TrimSuffix(path, "/")
|
||||
|
||||
var builder strings.Builder
|
||||
builder.WriteString(fmt.Sprintf("\n location %s {\n", cleanPath))
|
||||
// 使用 fmt.Fprintf 替代 WriteString(fmt.Sprintf(...))(QF1012)
|
||||
fmt.Fprintf(&builder, "\n location %s {\n", cleanPath)
|
||||
if rewrite != "" {
|
||||
if !strings.HasPrefix(rewrite, "/") {
|
||||
rewrite = "/" + rewrite
|
||||
}
|
||||
cleanRewrite := strings.TrimSuffix(rewrite, "/")
|
||||
if cleanRewrite == "" {
|
||||
builder.WriteString(fmt.Sprintf(" rewrite ^%s/(.*)$ /$1 break;\n", regexp.QuoteMeta(cleanPath)))
|
||||
builder.WriteString(fmt.Sprintf(" rewrite ^%s$ / break;\n", regexp.QuoteMeta(cleanPath)))
|
||||
fmt.Fprintf(&builder, " rewrite ^%s/(.*)$ /$1 break;\n", regexp.QuoteMeta(cleanPath))
|
||||
fmt.Fprintf(&builder, " rewrite ^%s$ / break;\n", regexp.QuoteMeta(cleanPath))
|
||||
} else {
|
||||
builder.WriteString(fmt.Sprintf(" rewrite ^%s/(.*)$ %s/$1 break;\n", regexp.QuoteMeta(cleanPath), cleanRewrite))
|
||||
builder.WriteString(fmt.Sprintf(" rewrite ^%s$ %s break;\n", regexp.QuoteMeta(cleanPath), cleanRewrite))
|
||||
fmt.Fprintf(&builder, " rewrite ^%s/(.*)$ %s/$1 break;\n", regexp.QuoteMeta(cleanPath), cleanRewrite)
|
||||
fmt.Fprintf(&builder, " rewrite ^%s$ %s break;\n", regexp.QuoteMeta(cleanPath), cleanRewrite)
|
||||
}
|
||||
}
|
||||
builder.WriteString(fmt.Sprintf(" proxy_pass %s;\n", pass))
|
||||
fmt.Fprintf(&builder, " proxy_pass %s;\n", pass)
|
||||
builder.WriteString(" proxy_http_version 1.1;\n")
|
||||
builder.WriteString(" proxy_set_header Host $http_host;\n")
|
||||
builder.WriteString(" proxy_set_header X-Real-IP $remote_addr;\n")
|
||||
@@ -499,7 +424,7 @@ func renderPagesLocationBlock(deployment *PagesDeployment, limitConfig routeLimi
|
||||
var builder strings.Builder
|
||||
builder.WriteString(renderRouteLimitBlock(limitConfig))
|
||||
if deployment != nil && deployment.SPAFallbackEnabled {
|
||||
builder.WriteString(fmt.Sprintf(" try_files $uri $uri/ %s;\n", pagesFallbackPath(deployment)))
|
||||
fmt.Fprintf(&builder, " try_files $uri $uri/ %s;\n", pagesFallbackPath(deployment))
|
||||
} else {
|
||||
builder.WriteString(" try_files $uri $uri/ =404;\n")
|
||||
}
|
||||
@@ -522,18 +447,18 @@ func pagesEntryFile(deployment *PagesDeployment) string {
|
||||
|
||||
func pagesFallbackPath(deployment *PagesDeployment) string {
|
||||
if deployment == nil || strings.TrimSpace(deployment.SPAFallbackPath) == "" {
|
||||
return "/index.html"
|
||||
return indexHTML
|
||||
}
|
||||
value := filepathToNginxPath(strings.TrimSpace(deployment.SPAFallbackPath))
|
||||
if !strings.HasPrefix(value, "/") {
|
||||
value = "/" + value
|
||||
}
|
||||
if value == "/" || strings.HasSuffix(value, "/") || strings.Contains(value, "\\") || strings.ContainsAny(value, "\"';") || strings.ContainsAny(value, " \t\r\n") {
|
||||
return "/index.html"
|
||||
return indexHTML
|
||||
}
|
||||
for _, segment := range strings.Split(value, "/") {
|
||||
if segment == "." || segment == ".." {
|
||||
return "/index.html"
|
||||
return indexHTML
|
||||
}
|
||||
}
|
||||
cleaned := path.Clean(value)
|
||||
@@ -546,13 +471,13 @@ func pagesFallbackPath(deployment *PagesDeployment) string {
|
||||
func renderProxyHeaderBlock(originURL string, originHost string, customHeaders []CustomHeader, upstreamConfig routeUpstreamConfig, cfg ConfigSnapshot) string {
|
||||
var builder strings.Builder
|
||||
if strings.TrimSpace(originHost) != "" {
|
||||
builder.WriteString(fmt.Sprintf(" proxy_set_header Host %s;\n", quoteNginxStringLiteral(originHost)))
|
||||
fmt.Fprintf(&builder, " proxy_set_header Host %s;\n", quoteNginxStringLiteral(originHost))
|
||||
} else {
|
||||
builder.WriteString(" proxy_set_header Host $host;\n")
|
||||
}
|
||||
if upstreamServerName := resolveUpstreamServerName(originURL, originHost); upstreamServerName != "" {
|
||||
builder.WriteString(" proxy_ssl_server_name on;\n")
|
||||
builder.WriteString(fmt.Sprintf(" proxy_ssl_name %s;\n", quoteNginxStringLiteral(upstreamServerName)))
|
||||
fmt.Fprintf(&builder, " proxy_ssl_name %s;\n", quoteNginxStringLiteral(upstreamServerName))
|
||||
}
|
||||
builder.WriteString(" proxy_set_header X-Real-IP $remote_addr;\n")
|
||||
builder.WriteString(" proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n")
|
||||
@@ -566,7 +491,7 @@ func renderProxyHeaderBlock(originURL string, originHost string, customHeaders [
|
||||
builder.WriteString(" proxy_set_header Connection \"\";\n")
|
||||
}
|
||||
for _, header := range customHeaders {
|
||||
builder.WriteString(fmt.Sprintf(" proxy_set_header %s %s;\n", header.Key, quoteNginxStringLiteral(header.Value)))
|
||||
fmt.Fprintf(&builder, " proxy_set_header %s %s;\n", header.Key, quoteNginxStringLiteral(header.Value))
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
@@ -635,13 +560,13 @@ func renderRouteCacheBlock(cacheConfig routeCacheConfig, cfg ConfigSnapshot) str
|
||||
func renderRouteLimitBlock(limitConfig routeLimitConfig) string {
|
||||
var builder strings.Builder
|
||||
if limitConfig.LimitConnPerServer > 0 {
|
||||
builder.WriteString(fmt.Sprintf(" limit_conn openflare_conn_per_server %d;\n", limitConfig.LimitConnPerServer))
|
||||
fmt.Fprintf(&builder, " limit_conn openflare_conn_per_server %d;\n", limitConfig.LimitConnPerServer)
|
||||
}
|
||||
if limitConfig.LimitConnPerIP > 0 {
|
||||
builder.WriteString(fmt.Sprintf(" limit_conn openflare_conn_per_ip %d;\n", limitConfig.LimitConnPerIP))
|
||||
fmt.Fprintf(&builder, " limit_conn openflare_conn_per_ip %d;\n", limitConfig.LimitConnPerIP)
|
||||
}
|
||||
if strings.TrimSpace(limitConfig.LimitRate) != "" {
|
||||
builder.WriteString(fmt.Sprintf(" limit_rate %s;\n", limitConfig.LimitRate))
|
||||
fmt.Fprintf(&builder, " limit_rate %s;\n", limitConfig.LimitRate)
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
@@ -700,8 +625,8 @@ func buildRouteUpstreamConfig(route Route, upstreams []string) routeUpstreamConf
|
||||
|
||||
func normalizeRouteUpstreamType(raw string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(raw)) {
|
||||
case "pages":
|
||||
return "pages"
|
||||
case routeUpstreamTypePages:
|
||||
return routeUpstreamTypePages
|
||||
default:
|
||||
return "direct"
|
||||
}
|
||||
@@ -709,9 +634,9 @@ func normalizeRouteUpstreamType(raw string) string {
|
||||
|
||||
func renderNamedUpstreamBlock(upstreamConfig routeUpstreamConfig) string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString(fmt.Sprintf("upstream %s {\n", upstreamConfig.Name))
|
||||
fmt.Fprintf(&builder, "upstream %s {\n", upstreamConfig.Name)
|
||||
for _, server := range upstreamConfig.Servers {
|
||||
builder.WriteString(fmt.Sprintf(" server %s max_fails=3 fail_timeout=10s;\n", server))
|
||||
fmt.Fprintf(&builder, " server %s max_fails=3 fail_timeout=10s;\n", server)
|
||||
}
|
||||
builder.WriteString(" keepalive 128;\n}\n\n")
|
||||
return builder.String()
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
package openresty
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type routeCertPartition struct {
|
||||
httpOnlyDomains []string
|
||||
domainsByCertID map[uint][]string
|
||||
}
|
||||
|
||||
func partitionRouteDomainsByCert(domains []string, certIDs, domainCertIDs []uint) routeCertPartition {
|
||||
httpOnlyDomains := make([]string, 0, len(domains))
|
||||
domainsByCertID := make(map[uint][]string, len(certIDs))
|
||||
for index, domain := range domains {
|
||||
if index >= len(domainCertIDs) || domainCertIDs[index] == 0 {
|
||||
httpOnlyDomains = append(httpOnlyDomains, domain)
|
||||
continue
|
||||
}
|
||||
domainsByCertID[domainCertIDs[index]] = append(domainsByCertID[domainCertIDs[index]], domain)
|
||||
}
|
||||
return routeCertPartition{
|
||||
httpOnlyDomains: httpOnlyDomains,
|
||||
domainsByCertID: domainsByCertID,
|
||||
}
|
||||
}
|
||||
|
||||
func validateRouteCertificates(route Route, displayName string, certIDs []uint, partition routeCertPartition, certificates map[uint]string) error {
|
||||
for _, certID := range certIDs {
|
||||
assignedDomains := partition.domainsByCertID[certID]
|
||||
if len(assignedDomains) == 0 {
|
||||
continue
|
||||
}
|
||||
certPEM, ok := certificates[certID]
|
||||
if !ok {
|
||||
return fmt.Errorf("route %s certificate %d does not exist", route.Domain, certID)
|
||||
}
|
||||
if err := validateCertificateCoverage(certPEM, assignedDomains); err != nil {
|
||||
return fmt.Errorf("site %s certificate validation failed: %w", displayName, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func renderPagesRouteHTTPS(
|
||||
builder *strings.Builder,
|
||||
serverNames, displayName string,
|
||||
route Route,
|
||||
partition routeCertPartition,
|
||||
certIDs []uint,
|
||||
limitConfig routeLimitConfig,
|
||||
powEnabled bool,
|
||||
cfg ConfigSnapshot,
|
||||
) {
|
||||
if route.RedirectHTTP {
|
||||
if len(partition.httpOnlyDomains) > 0 {
|
||||
builder.WriteString(renderHTTPPagesServer(renderServerNames(partition.httpOnlyDomains), displayName, route.PagesDeployment, limitConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword))
|
||||
}
|
||||
for _, certID := range certIDs {
|
||||
if assignedDomains := partition.domainsByCertID[certID]; len(assignedDomains) > 0 {
|
||||
builder.WriteString(renderHTTPRedirectServer(renderServerNames(assignedDomains)))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
builder.WriteString(renderHTTPPagesServer(serverNames, displayName, route.PagesDeployment, limitConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword))
|
||||
}
|
||||
for _, certID := range certIDs {
|
||||
if assignedDomains := partition.domainsByCertID[certID]; len(assignedDomains) > 0 {
|
||||
builder.WriteString(renderHTTPSPagesServer(renderServerNames(assignedDomains), displayName, certID, route.PagesDeployment, limitConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, cfg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func renderProxyRouteHTTPS(
|
||||
builder *strings.Builder,
|
||||
serverNames, displayName string,
|
||||
route Route,
|
||||
partition routeCertPartition,
|
||||
certIDs []uint,
|
||||
cacheConfig routeCacheConfig,
|
||||
limitConfig routeLimitConfig,
|
||||
upstreamConfig routeUpstreamConfig,
|
||||
powEnabled bool,
|
||||
cfg ConfigSnapshot,
|
||||
) {
|
||||
if route.RedirectHTTP {
|
||||
if len(partition.httpOnlyDomains) > 0 {
|
||||
builder.WriteString(renderHTTPProxyServer(renderServerNames(partition.httpOnlyDomains), displayName, route.OriginURL, route.OriginHost, route.CustomHeaders, cacheConfig, limitConfig, upstreamConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, cfg))
|
||||
}
|
||||
for _, certID := range certIDs {
|
||||
if assignedDomains := partition.domainsByCertID[certID]; len(assignedDomains) > 0 {
|
||||
builder.WriteString(renderHTTPRedirectServer(renderServerNames(assignedDomains)))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
builder.WriteString(renderHTTPProxyServer(serverNames, displayName, route.OriginURL, route.OriginHost, route.CustomHeaders, cacheConfig, limitConfig, upstreamConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, cfg))
|
||||
}
|
||||
for _, certID := range certIDs {
|
||||
if assignedDomains := partition.domainsByCertID[certID]; len(assignedDomains) > 0 {
|
||||
builder.WriteString(renderHTTPSServer(renderServerNames(assignedDomains), displayName, route.OriginURL, route.OriginHost, certID, route.CustomHeaders, cacheConfig, limitConfig, upstreamConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, cfg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func renderPagesRoute(builder *strings.Builder, route Route, displayName, serverNames string, certificates map[uint]string, limitConfig routeLimitConfig, powEnabled bool, cfg ConfigSnapshot) error {
|
||||
if route.PagesDeployment == nil {
|
||||
return fmt.Errorf("route %s pages deployment is missing", route.Domain)
|
||||
}
|
||||
if !route.EnableHTTPS {
|
||||
builder.WriteString(renderHTTPPagesServer(serverNames, displayName, route.PagesDeployment, limitConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword))
|
||||
return nil
|
||||
}
|
||||
certIDs := normalizeCertIDs(route.CertID, route.CertIDs)
|
||||
domainCertIDs := normalizeDomainCertIDs(normalizedRouteDomains(route), certIDs, route.DomainCertIDs)
|
||||
if len(certIDs) == 0 {
|
||||
return fmt.Errorf("路由 %s 未配置证书", route.Domain)
|
||||
}
|
||||
partition := partitionRouteDomainsByCert(normalizedRouteDomains(route), certIDs, domainCertIDs)
|
||||
if err := validateRouteCertificates(route, displayName, certIDs, partition, certificates); err != nil {
|
||||
return err
|
||||
}
|
||||
renderPagesRouteHTTPS(builder, serverNames, displayName, route, partition, certIDs, limitConfig, powEnabled, cfg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func renderProxyRoute(builder *strings.Builder, route Route, displayName, serverNames string, certificates map[uint]string, cacheConfig routeCacheConfig, limitConfig routeLimitConfig, powEnabled bool, cfg ConfigSnapshot) error {
|
||||
upstreams := route.Upstreams
|
||||
if len(upstreams) == 0 && strings.TrimSpace(route.OriginURL) != "" {
|
||||
upstreams = []string{route.OriginURL}
|
||||
}
|
||||
upstreamConfig := buildRouteUpstreamConfig(route, upstreams)
|
||||
if upstreamConfig.UsesNamedUpstream {
|
||||
builder.WriteString(renderNamedUpstreamBlock(upstreamConfig))
|
||||
}
|
||||
if !route.EnableHTTPS {
|
||||
builder.WriteString(renderHTTPProxyServer(serverNames, displayName, route.OriginURL, route.OriginHost, route.CustomHeaders, cacheConfig, limitConfig, upstreamConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, cfg))
|
||||
return nil
|
||||
}
|
||||
certIDs := normalizeCertIDs(route.CertID, route.CertIDs)
|
||||
domainCertIDs := normalizeDomainCertIDs(normalizedRouteDomains(route), certIDs, route.DomainCertIDs)
|
||||
if len(certIDs) == 0 {
|
||||
return fmt.Errorf("路由 %s 未配置证书", route.Domain)
|
||||
}
|
||||
partition := partitionRouteDomainsByCert(normalizedRouteDomains(route), certIDs, domainCertIDs)
|
||||
if err := validateRouteCertificates(route, displayName, certIDs, partition, certificates); err != nil {
|
||||
return err
|
||||
}
|
||||
renderProxyRouteHTTPS(builder, serverNames, displayName, route, partition, certIDs, cacheConfig, limitConfig, upstreamConfig, powEnabled, cfg)
|
||||
return nil
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package openresty
|
||||
|
||||
// Placeholder constants used as sentinel values in rendered OpenResty config
|
||||
// files; the deploy process replaces them with real paths before reload.
|
||||
const (
|
||||
CertDirPlaceholder = "__OPENFLARE_CERT_DIR__"
|
||||
RouteConfigPlaceholder = "__OPENFLARE_ROUTE_CONFIG__"
|
||||
@@ -63,16 +65,22 @@ http {
|
||||
}
|
||||
`
|
||||
|
||||
// SupportFile represents an auxiliary file (certificate, WAF config, etc.)
|
||||
// that is written alongside the main OpenResty configuration.
|
||||
type SupportFile struct {
|
||||
Path string `json:"path"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
// CustomHeader is a key/value pair injected as an additional proxy_set_header
|
||||
// directive for a specific route.
|
||||
type CustomHeader struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// PoWListConfig holds the IP, CIDR, path, and user-agent lists used by the
|
||||
// Proof-of-Work whitelist or blacklist filter.
|
||||
type PoWListConfig struct {
|
||||
IPs []string `json:"ips"`
|
||||
IPCidrs []string `json:"ip_cidrs"`
|
||||
@@ -81,6 +89,8 @@ type PoWListConfig struct {
|
||||
UserAgents []string `json:"user_agents"`
|
||||
}
|
||||
|
||||
// PoWConfig holds the full Proof-of-Work challenge parameters for a route,
|
||||
// including difficulty, algorithm, TTLs, and allow/block lists.
|
||||
type PoWConfig struct {
|
||||
Difficulty int `json:"difficulty"`
|
||||
Algorithm string `json:"algorithm"`
|
||||
@@ -90,6 +100,8 @@ type PoWConfig struct {
|
||||
Blacklist PoWListConfig `json:"blacklist"`
|
||||
}
|
||||
|
||||
// Route describes a single proxy or pages site entry in the OpenFlare config
|
||||
// document, including upstream, TLS, caching, rate-limiting and WAF settings.
|
||||
type Route struct {
|
||||
ID uint `json:"id,omitempty"`
|
||||
SiteName string `json:"site_name,omitempty"`
|
||||
@@ -121,6 +133,8 @@ type Route struct {
|
||||
PagesDeployment *PagesDeployment `json:"pages_deployment,omitempty"`
|
||||
}
|
||||
|
||||
// PagesDeployment holds the static-site deployment parameters for a Pages-type
|
||||
// route, including local root, entry file, SPA fallback, and API proxy options.
|
||||
type PagesDeployment struct {
|
||||
ProjectID uint `json:"project_id"`
|
||||
ProjectSlug string `json:"project_slug"`
|
||||
@@ -137,6 +151,8 @@ type PagesDeployment struct {
|
||||
LocalRoot string `json:"local_root"`
|
||||
}
|
||||
|
||||
// WAFRuleGroup defines a WAF rule group with IP/country/region lists, PoW
|
||||
// integration, and per-group block status configuration.
|
||||
type WAFRuleGroup struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
@@ -156,6 +172,8 @@ type WAFRuleGroup struct {
|
||||
PoWConfig *PoWConfig `json:"pow_config,omitempty"`
|
||||
}
|
||||
|
||||
// WAFIPGroup is a named, reusable list of IP addresses or CIDRs that can be
|
||||
// referenced by multiple WAF rule groups as a whitelist or blacklist.
|
||||
type WAFIPGroup struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
@@ -164,18 +182,24 @@ type WAFIPGroup struct {
|
||||
IPList []string `json:"ip_list,omitempty"`
|
||||
}
|
||||
|
||||
// WAFBinding associates a route (by site name) with the WAF rule groups that
|
||||
// should be enforced for that site.
|
||||
type WAFBinding struct {
|
||||
RouteID uint `json:"route_id"`
|
||||
SiteName string `json:"site_name"`
|
||||
RuleGroupIDs []uint `json:"rule_group_ids"`
|
||||
}
|
||||
|
||||
// WAFDocument is the top-level WAF configuration snapshot containing rule
|
||||
// groups, IP groups, and per-site bindings.
|
||||
type WAFDocument struct {
|
||||
RuleGroups []WAFRuleGroup `json:"rule_groups"`
|
||||
IPGroups []WAFIPGroup `json:"ip_groups,omitempty"`
|
||||
Bindings []WAFBinding `json:"bindings"`
|
||||
}
|
||||
|
||||
// ConfigSnapshot holds the full set of OpenResty tuning parameters that are
|
||||
// rendered into the nginx main configuration template.
|
||||
type ConfigSnapshot struct {
|
||||
DefaultServerReturnStatus int `json:"default_server_return_status"`
|
||||
WorkerProcesses string `json:"worker_processes"`
|
||||
@@ -216,12 +240,16 @@ type ConfigSnapshot struct {
|
||||
MainConfigTemplate string `json:"main_config_template,omitempty"`
|
||||
}
|
||||
|
||||
// Document is the top-level input structure for the OpenResty renderer,
|
||||
// combining routes, OpenResty tuning, and WAF configuration.
|
||||
type Document struct {
|
||||
Routes []Route `json:"routes"`
|
||||
OpenRestyConfig ConfigSnapshot `json:"openresty_config"`
|
||||
WAF WAFDocument `json:"waf"`
|
||||
}
|
||||
|
||||
// Result is the output produced by Render, containing the rendered main
|
||||
// config, route config, support files, and a content checksum.
|
||||
type Result struct {
|
||||
MainConfig string
|
||||
RouteConfig string
|
||||
|
||||
Reference in New Issue
Block a user