package cloudflare import ( "fmt" "strings" "github.com/pulumi/pulumi-cloudflare/sdk/v6/go/cloudflare" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) // createCacheRules creates modern Ruleset for cached content (replaces legacy cache page rule). // // Uses 3 rules of the 70 under the free tier. func (e *EdgeProtection) createCacheRules(ctx *pulumi.Context, zone *cloudflare.Zone) (*cloudflare.Ruleset, error) { cacheRuleset, err := cloudflare.NewRuleset(ctx, e.newResourceName("cache-ruleset", "optimization", 64), &cloudflare.RulesetArgs{ ZoneId: zone.ID(), Name: pulumi.String("Cache Optimization Rules"), Kind: pulumi.String("zone"), Phase: pulumi.String("http_request_cache_settings"), Description: pulumi.String("Cache rules for static assets and performance optimization"), Rules: cloudflare.RulesetRuleArray{ // Cache everything for static assets (more specific than legacy page rule) &cloudflare.RulesetRuleArgs{ Action: pulumi.String("set_cache_settings"), Expression: pulumi.Sprintf("(%s)", strings.Join([]string{ `(http.request.uri.path contains ".css")`, `(http.request.uri.path contains ".js")`, `(http.request.uri.path contains ".jpg")`, `(http.request.uri.path contains ".jpeg")`, `(http.request.uri.path contains ".png")`, `(http.request.uri.path contains ".gif")`, `(http.request.uri.path contains ".ico")`, `(http.request.uri.path contains ".svg")`, `(http.request.uri.path contains ".woff")`, `(http.request.uri.path contains ".woff2")`, `(http.request.uri.path contains ".ttf")`, `(http.request.uri.path contains ".eot")`, `(http.request.uri.path contains ".pdf")`, `(http.request.uri.path contains ".zip")`, `(http.request.uri.path contains ".mp4")`, `(http.request.uri.path contains ".mp3")`, `(http.request.uri.path contains "/static/")`, `(http.request.uri.path contains "/assets/")`, `(http.request.uri.path contains "/public/")`, `(http.request.uri.path contains "/fonts/")`, `(http.request.headers["content-type"][0] eq "image/jpeg")`, `(http.request.headers["content-type"][0] eq "image/png")`, `(http.request.headers["content-type"][0] eq "image/gif")`, `(http.request.headers["content-type"][0] eq "image/svg+xml")`, }, " or ")), Description: pulumi.String("Cache static assets aggressively"), ActionParameters: &cloudflare.RulesetRuleActionParametersArgs{ Cache: pulumi.Bool(true), BrowserTtl: &cloudflare.RulesetRuleActionParametersBrowserTtlArgs{ Mode: pulumi.String("override_origin"), Default: e.BrowserCacheTTL, }, EdgeTtl: &cloudflare.RulesetRuleActionParametersEdgeTtlArgs{ Mode: pulumi.String("override_origin"), Default: e.EdgeCacheTTLSeconds, }, CacheKey: &cloudflare.RulesetRuleActionParametersCacheKeyArgs{ CacheByDeviceType: pulumi.Bool(false), }, ServeStale: &cloudflare.RulesetRuleActionParametersServeStaleArgs{ DisableStaleWhileUpdating: pulumi.Bool(false), }, }, Enabled: pulumi.Bool(true), }, // Different cache settings for HTML pages &cloudflare.RulesetRuleArgs{ Action: pulumi.String("set_cache_settings"), Expression: pulumi.Sprintf("(%s)", strings.Join([]string{ `(http.request.uri.path contains ".html")`, `(http.request.uri.path contains ".htm")`, `(http.request.uri.path eq "/")`, `(not http.request.uri.path contains ".")`, }, " or ")), Description: pulumi.String("Cache HTML pages with shorter TTL"), ActionParameters: &cloudflare.RulesetRuleActionParametersArgs{ Cache: pulumi.Bool(true), // TODO make me configurable BrowserTtl: &cloudflare.RulesetRuleActionParametersBrowserTtlArgs{ Mode: pulumi.String("override_origin"), Default: pulumi.Int(1800), // 30 minutes for browser }, EdgeTtl: &cloudflare.RulesetRuleActionParametersEdgeTtlArgs{ Mode: pulumi.String("override_origin"), Default: pulumi.Int(7200), // 2 hours for HTML }, CacheKey: &cloudflare.RulesetRuleActionParametersCacheKeyArgs{ CacheByDeviceType: pulumi.Bool(false), }, }, Enabled: pulumi.Bool(true), }, // Bypass cache for dynamic content &cloudflare.RulesetRuleArgs{ Action: pulumi.String("set_cache_settings"), Expression: pulumi.Sprintf("(%s)", strings.Join([]string{ `(http.request.uri.path contains "/api/")`, `(http.request.uri.path contains "/admin/")`, `(http.request.uri.path contains "/login")`, `(http.request.uri.path contains "/checkout")`, `(http.request.uri.path contains "/cart")`, `(http.request.method ne "GET")`, }, " or ")), Description: pulumi.String("Bypass cache for dynamic and sensitive content"), ActionParameters: &cloudflare.RulesetRuleActionParametersArgs{ Cache: pulumi.Bool(true), // TODO make me configurable BrowserTtl: &cloudflare.RulesetRuleActionParametersBrowserTtlArgs{ Mode: pulumi.String("bypass"), }, EdgeTtl: &cloudflare.RulesetRuleActionParametersEdgeTtlArgs{ Mode: pulumi.String("bypass_by_default"), }, }, Enabled: pulumi.Bool(true), }, }, }, pulumi.Parent(e)) if err != nil { return nil, fmt.Errorf("failed to create cache ruleset: %w", err) } return cacheRuleset, nil }
// Package config provides an environment config helper package config import ( "fmt" "log" "github.com/kelseyhightower/envconfig" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" "github.com/davidmontoyago/pulumi-cloudflare-free-edge-protection/pkg/cloudflare" ) // Config is a helper for loading and launching via environment variables // Defaults to a single backend for the upstream. type Config struct { CloudflareAPIToken string `envconfig:"CLOUDFLARE_API_TOKEN" required:"true"` CloudflareAccountID string `envconfig:"CLOUDFLARE_ACCOUNT_ID" required:"true"` BackendURL string `envconfig:"BACKEND_URL" required:"true"` BackendUpstreamURL string `envconfig:"BACKEND_UPSTREAM_URL" required:"true"` SecurityLevel string `envconfig:"SECURITY_LEVEL" default:"medium"` BrowserCacheTTL int `envconfig:"BROWSER_CACHE_TTL" default:"14400"` EdgeCacheTTLSeconds int `envconfig:"EDGE_CACHE_TTL_SECONDS" default:"2419200"` RateLimitThreshold int `envconfig:"RATE_LIMIT_THRESHOLD" default:"60"` RateLimitMode string `envconfig:"RATE_LIMIT_MODE" default:"block"` TLSEncryptionMode string `envconfig:"TLS_ENCRYPTION_MODE" default:"strict"` MinTLSVersion string `envconfig:"MIN_TLS_VERSION" default:"1.2"` AlwaysUseHTTPS bool `envconfig:"ALWAYS_USE_HTTPS" default:"true"` TLS13Enabled bool `envconfig:"TLS_13_ENABLED" default:"true"` BrowserCheckEnabled bool `envconfig:"BROWSER_CHECK_ENABLED" default:"true"` DDoSAttackNotificationsEmail string `envconfig:"DDOS_ATTACK_NOTIFICATIONS_EMAIL" default:""` } // LoadConfig loads configuration from environment variables // All required environment variables must be set or will cause an error func LoadConfig() (*Config, error) { var config Config err := envconfig.Process("", &config) if err != nil { return nil, fmt.Errorf("failed to load configuration from environment variables: %w", err) } log.Printf("Configuration loaded successfully:") log.Printf(" Backend URL: %s", config.BackendURL) log.Printf(" Backend Upstream URL: %s", config.BackendUpstreamURL) log.Printf(" Security Level: %s", config.SecurityLevel) log.Printf(" Rate Limit Mode: %s", config.RateLimitMode) log.Printf(" Browser Cache TTL: %d seconds", config.BrowserCacheTTL) log.Printf(" Edge Cache TTL: %d seconds", config.EdgeCacheTTLSeconds) log.Printf(" Rate Limit Threshold: %d requests", config.RateLimitThreshold) log.Printf(" TLS Encryption Mode: %s", config.TLSEncryptionMode) log.Printf(" Min TLS Version: %s", config.MinTLSVersion) log.Printf(" Always Use HTTPS: %t", config.AlwaysUseHTTPS) log.Printf(" TLS 1.3 Enabled: %t", config.TLS13Enabled) log.Printf(" Browser Check Enabled: %t", config.BrowserCheckEnabled) return &config, nil } // ToEdgeProtectionArgs converts the config to EdgeProtectionArgs for use with the Pulumi component func (c *Config) ToEdgeProtectionArgs() *cloudflare.EdgeProtectionArgs { args := &cloudflare.EdgeProtectionArgs{ Upstreams: []cloudflare.Upstream{ { DomainURL: c.BackendURL, CanonicalNameURL: c.BackendUpstreamURL, }, }, CloudflareZone: cloudflare.Zone{ CloudflareAccountID: c.CloudflareAccountID, Protected: true, }, SecurityLevel: pulumi.String(c.SecurityLevel), BrowserCacheTTL: pulumi.Int(c.BrowserCacheTTL), EdgeCacheTTLSeconds: pulumi.Int(c.EdgeCacheTTLSeconds), RateLimitThreshold: pulumi.Int(c.RateLimitThreshold), RateLimitMode: pulumi.String(c.RateLimitMode), TLSEncryptionMode: pulumi.String(c.TLSEncryptionMode), MinTLSVersion: pulumi.String(c.MinTLSVersion), AlwaysUseHTTPS: pulumi.Bool(c.AlwaysUseHTTPS), TLS13Enabled: pulumi.Bool(c.TLS13Enabled), BrowserCheckEnabled: pulumi.Bool(c.BrowserCheckEnabled), DDoSAttackNotificationsEmail: c.DDoSAttackNotificationsEmail, } return args }
package cloudflare import ( "fmt" "github.com/pulumi/pulumi-cloudflare/sdk/v6/go/cloudflare" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) const ( // Cloudflare L7 DDoS Attack Protection Ruleset ID // See: https://developers.cloudflare.com/terraform/additional-configurations/ddos-managed-rulesets/ ddosL7RulesetID = "4d21379b4f9f4bb088e0729962c8b3cf" ) // createDDoSProtectionRules deploys DDoS managed protection for L7. L4 is provided by default. // // Uses 1 rules of the 70 under the free tier. func (e *EdgeProtection) createDDoSProtectionRules(ctx *pulumi.Context, zone *cloudflare.Zone) (*cloudflare.Ruleset, error) { // DDoS L7 Protection ddosL7Ruleset, err := cloudflare.NewRuleset(ctx, e.newResourceName("l7-ruleset", "ddos-managed", 64), &cloudflare.RulesetArgs{ ZoneId: zone.ID(), Name: pulumi.String("DDoS L7 Protection"), Kind: pulumi.String("zone"), Phase: pulumi.String("ddos_l7"), Description: pulumi.String("Layer 7 DDoS protection"), Rules: cloudflare.RulesetRuleArray{ &cloudflare.RulesetRuleArgs{ Action: pulumi.String("execute"), Expression: pulumi.String("true"), Description: pulumi.String("Execute DDoS L7 Managed Ruleset"), ActionParameters: &cloudflare.RulesetRuleActionParametersArgs{ Id: pulumi.String(ddosL7RulesetID), }, Enabled: pulumi.Bool(true), }, }, }, pulumi.Parent(e)) if err != nil { return nil, fmt.Errorf("failed to create DDoS L7 ruleset: %w", err) } return ddosL7Ruleset, nil }
package cloudflare import ( "fmt" "strings" "github.com/pulumi/pulumi-cloudflare/sdk/v6/go/cloudflare" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) // createZone manages a Cloudflare DNS Zone with free tier plan. func (e *EdgeProtection) createZone(ctx *pulumi.Context) (*cloudflare.Zone, error) { // Extract mydomain.com from my-app.mydomain.com firstUpstream := e.Upstreams[0] zoneDomainURL := firstUpstream.DomainURL[strings.Index(firstUpstream.DomainURL, ".")+1:] zone, err := cloudflare.NewZone(ctx, e.newResourceName("zone", "dns", 64), &cloudflare.ZoneArgs{ Account: cloudflare.ZoneAccountArgs{ Id: pulumi.String(e.CloudflareZone.CloudflareAccountID), }, Name: pulumi.String(zoneDomainURL), // Full zone management. A partial setup with CNAMEs wouldn't be enough. Type: pulumi.String("full"), }, pulumi.Protect(e.CloudflareZone.Protected)) if err != nil { return nil, fmt.Errorf("failed to create Cloudflare DNS zone: %w", err) } return zone, nil } // createDNSRecords creates DNS records pointing to backend and frontend services. func (e *EdgeProtection) createDNSRecords(ctx *pulumi.Context, zone *cloudflare.Zone) ([]*cloudflare.DnsRecord, error) { upstreamRecords := make([]*cloudflare.DnsRecord, 0, len(e.Upstreams)) for _, upstream := range e.Upstreams { recordName := strings.Split(upstream.DomainURL, ".")[0] recordResourceName := e.newResourceName(fmt.Sprintf("upstream-%s", recordName), "dns", 64) record, err := cloudflare.NewDnsRecord(ctx, recordResourceName, &cloudflare.DnsRecordArgs{ ZoneId: zone.ID(), Name: pulumi.String(upstream.DomainURL), Content: pulumi.String(upstream.CanonicalNameURL), Type: pulumi.String("CNAME"), // Enable Cloudflare proxy for CDN + DDoS protection // For first time setup, set to false until the // cross-cloud DNS is resolved (E.g. In GCP Doman Mapping shows success). Proxied: pulumi.Bool(!upstream.DisableProtection), Ttl: pulumi.Float64(1), // Automatic TTL when proxied }, pulumi.Parent(e), pulumi.Protect(e.CloudflareZone.Protected)) if err != nil { return nil, fmt.Errorf("failed to create upstream DNS record %s: %w", upstream.DomainURL, err) } upstreamRecords = append(upstreamRecords, record) } return upstreamRecords, nil }
// Package cloudflare provides Cloudflare edge protection infrastructure components. package cloudflare import ( "fmt" "github.com/pulumi/pulumi-cloudflare/sdk/v6/go/cloudflare" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) // EdgeProtection represents a Cloudflare edge protection stack for internet applications. type EdgeProtection struct { pulumi.ResourceState Upstreams []Upstream CloudflareZone Zone SecurityLevel pulumi.StringOutput BrowserCacheTTL pulumi.IntOutput EdgeCacheTTLSeconds pulumi.IntOutput RateLimitThreshold pulumi.IntOutput RateLimitPeriodSeconds pulumi.IntOutput MitigationTimeoutSeconds pulumi.IntOutput RateLimitMode pulumi.StringOutput TLSEncryptionMode pulumi.StringOutput MinTLSVersion pulumi.StringOutput AlwaysUseHTTPS pulumi.BoolOutput TLS13Enabled pulumi.BoolOutput BrowserCheckEnabled pulumi.BoolOutput DDoSAttackNotificationsEmail string Labels map[string]string name string // Core resources zone *cloudflare.Zone upstreamDNSRecords []*cloudflare.DnsRecord zoneSettings []*cloudflare.ZoneSetting rateLimitRuleset *cloudflare.Ruleset ddosL7Ruleset *cloudflare.Ruleset wafCustomRuleset *cloudflare.Ruleset cacheRuleset *cloudflare.Ruleset redirectRuleset *cloudflare.Ruleset configRuleset *cloudflare.Ruleset freeTierRulesCount pulumi.IntOutput ddosAttackNotifications *cloudflare.NotificationPolicy } // NewEdgeProtection creates a new EdgeProtection instance with the provided configuration. func NewEdgeProtection(ctx *pulumi.Context, name string, args *EdgeProtectionArgs, opts ...pulumi.ResourceOption) (*EdgeProtection, error) { if len(args.Upstreams) == 0 { return nil, fmt.Errorf("upstreams are required") } if args.CloudflareZone.CloudflareAccountID == "" { return nil, fmt.Errorf("cloudflare account ID is required") } edgeProtection := &EdgeProtection{ Upstreams: args.Upstreams, CloudflareZone: args.CloudflareZone, SecurityLevel: setDefaultString(args.SecurityLevel, "medium"), BrowserCacheTTL: setDefaultInt(args.BrowserCacheTTL, 14400), // 4 hours EdgeCacheTTLSeconds: setDefaultInt(args.EdgeCacheTTLSeconds, 2419200), // 28 days RateLimitPeriodSeconds: setDefaultInt(nil, 10), // Free tier requires 10 seconds MitigationTimeoutSeconds: setDefaultInt(nil, 10), // Free tier requires 10 seconds RateLimitThreshold: setDefaultInt(args.RateLimitThreshold, 60), // 60 requests per 10s period RateLimitMode: setDefaultString(args.RateLimitMode, "block"), TLSEncryptionMode: setDefaultString(args.TLSEncryptionMode, "strict"), MinTLSVersion: setDefaultString(args.MinTLSVersion, "1.2"), AlwaysUseHTTPS: setDefaultBool(args.AlwaysUseHTTPS, true), TLS13Enabled: setDefaultBool(args.TLS13Enabled, true), BrowserCheckEnabled: setDefaultBool(args.BrowserCheckEnabled, true), DDoSAttackNotificationsEmail: args.DDoSAttackNotificationsEmail, Labels: args.Labels, name: name, } err := ctx.RegisterComponentResource("pulumi-cloudflare-free-waf:cloudflare:EdgeProtection", name, edgeProtection, opts...) if err != nil { return nil, fmt.Errorf("failed to register component resource: %w", err) } // Deploy the infrastructure err = edgeProtection.deploy(ctx) if err != nil { return nil, fmt.Errorf("failed to deploy edge protection: %w", err) } err = ctx.RegisterResourceOutputs(edgeProtection, pulumi.Map{ "cloudflare_zone_id": edgeProtection.zone.ID(), "cloudflare_zone_name": edgeProtection.zone.Name, "cloudflare_zone_status": edgeProtection.zone.Status, "cloudflare_zone_name_servers": edgeProtection.zone.NameServers, "cloudflare_upstream_dns_record_count": pulumi.Int(len(edgeProtection.upstreamDNSRecords)), "cloudflare_rate_limit_ruleset_id": edgeProtection.rateLimitRuleset.ID(), "cloudflare_ddos_l7_ruleset_id": edgeProtection.ddosL7Ruleset.ID(), "cloudflare_waf_custom_ruleset_id": edgeProtection.wafCustomRuleset.ID(), "cloudflare_cache_ruleset_id": edgeProtection.cacheRuleset.ID(), "cloudflare_redirect_ruleset_id": edgeProtection.redirectRuleset.ID(), "cloudflare_config_ruleset_id": edgeProtection.configRuleset.ID(), "cloudflare_ruleset_rules_count": edgeProtection.freeTierRulesCount, }) if err != nil { return nil, fmt.Errorf("failed to register resource outputs: %w", err) } return edgeProtection, nil } // deploy provisions all the resources for the Cloudflare edge protection. func (e *EdgeProtection) deploy(ctx *pulumi.Context) error { // 1. Create DNS Zone zone, err := e.createZone(ctx) if err != nil { return fmt.Errorf("failed to create zone: %w", err) } e.zone = zone // 2. Create DNS records for each upstream upstreamDNSRecords, err := e.createDNSRecords(ctx, zone) if err != nil { return fmt.Errorf("failed to create upstream DNS records: %w", err) } e.upstreamDNSRecords = upstreamDNSRecords // 3. Configure SSL/TLS settings zoneSettings, err := e.configureTLSSettings(ctx, zone) if err != nil { return fmt.Errorf("failed to configure TLS settings: %w", err) } e.zoneSettings = zoneSettings // 4. Create rate limiting rules rateLimitRuleset, err := e.createRateLimitRuleset(ctx, zone) if err != nil { return fmt.Errorf("failed to create rate limit rule: %w", err) } e.rateLimitRuleset = rateLimitRuleset // 5. Create DDoS protection rules ddosL7Ruleset, err := e.createDDoSProtectionRules(ctx, zone) if err != nil { return fmt.Errorf("failed to create DDoS protection rules: %w", err) } e.ddosL7Ruleset = ddosL7Ruleset // 6. Create custom WAF rules. // Predefined best-practice managed rulesets are provided by default. // See: https://developers.cloudflare.com/waf/managed-rules/ wafCustomRuleset, err := e.createWAFCustomRules(ctx, zone) if err != nil { return fmt.Errorf("failed to create WAF custom rules: %w", err) } e.wafCustomRuleset = wafCustomRuleset // 7. Create traffic optimization rules cacheRuleset, redirectRuleset, configRuleset, err := e.createOptimizationRules(ctx, zone) if err != nil { return fmt.Errorf("failed to create optimization rules: %w", err) } e.cacheRuleset = cacheRuleset e.redirectRuleset = redirectRuleset e.configRuleset = configRuleset // Count total number of free-tier rules pulumi.All( cacheRuleset.Rules, redirectRuleset.Rules, configRuleset.Rules, wafCustomRuleset.Rules, ddosL7Ruleset.Rules, rateLimitRuleset.Rules, ).ApplyT(func(rules []interface{}) error { freeRules := flattenRulesetRules(rules) e.freeTierRulesCount = pulumi.Int(len(freeRules)).ToIntOutput() return nil }) // 8. Create DDoS attack notifications if e.DDoSAttackNotificationsEmail != "" { ddosAttackNotifications, err := e.setupDDoSAttackNotifications(ctx, e.DDoSAttackNotificationsEmail) if err != nil { return fmt.Errorf("failed to create DDoS attack notifications: %w", err) } e.ddosAttackNotifications = ddosAttackNotifications } return nil } // Getter methods for accessing internal resources // GetZone returns the Cloudflare Zone resource. func (e *EdgeProtection) GetZone() *cloudflare.Zone { return e.zone } // GetUpstreamDNSRecords returns the upstream DNS records resource. func (e *EdgeProtection) GetUpstreamDNSRecords() []*cloudflare.DnsRecord { return e.upstreamDNSRecords } // GetZoneSettings returns the zone settings override resource. func (e *EdgeProtection) GetZoneSettings() []*cloudflare.ZoneSetting { return e.zoneSettings } // GetRateLimitRuleset returns the rate limit ruleset resource. func (e *EdgeProtection) GetRateLimitRuleset() *cloudflare.Ruleset { return e.rateLimitRuleset } // GetDDoSL7Ruleset returns the DDoS L7 protection ruleset resource. func (e *EdgeProtection) GetDDoSL7Ruleset() *cloudflare.Ruleset { return e.ddosL7Ruleset } // GetWAFCustomRuleset returns the WAF custom ruleset resource. func (e *EdgeProtection) GetWAFCustomRuleset() *cloudflare.Ruleset { return e.wafCustomRuleset } // GetCacheRuleset returns the cache optimization ruleset resource. func (e *EdgeProtection) GetCacheRuleset() *cloudflare.Ruleset { return e.cacheRuleset } // GetRedirectRuleset returns the redirect ruleset resource. func (e *EdgeProtection) GetRedirectRuleset() *cloudflare.Ruleset { return e.redirectRuleset } // GetConfigurationRuleset returns the configuration ruleset resource. func (e *EdgeProtection) GetConfigurationRuleset() *cloudflare.Ruleset { return e.configRuleset } // GetDDoSAttackNotifications returns the DDoS attack notification policy resource. func (e *EdgeProtection) GetDDoSAttackNotifications() *cloudflare.NotificationPolicy { return e.ddosAttackNotifications }
package cloudflare import ( "fmt" "math" ) // newResourceName generates a consistent resource name with length limits. func (e *EdgeProtection) newResourceName(serviceName, resourceType string, maxLength int) string { var resourceName string if resourceType == "" { resourceName = fmt.Sprintf("%s-%s", e.name, serviceName) } else { resourceName = fmt.Sprintf("%s-%s-%s", e.name, serviceName, resourceType) } if len(resourceName) <= maxLength { return resourceName } surplus := len(resourceName) - maxLength resourceName = e.truncateResourceName(serviceName, resourceType, surplus, maxLength) return resourceName } // truncateResourceName handles the complex logic for truncating resource names. func (e *EdgeProtection) truncateResourceName(serviceName, resourceType string, surplus, maxLength int) string { mainComponentLength := len(e.name) if mainComponentLength > surplus { return e.truncateMainComponent(serviceName, resourceType, surplus) } return e.proportionalTruncate(serviceName, resourceType, maxLength) } // truncateMainComponent truncates the main component name when it's long enough. func (e *EdgeProtection) truncateMainComponent(serviceName, resourceType string, surplus int) string { truncatedMainComponent := e.name[:len(e.name)-surplus] if resourceType == "" { return fmt.Sprintf("%s-%s", truncatedMainComponent, serviceName) } return fmt.Sprintf("%s-%s-%s", truncatedMainComponent, serviceName, resourceType) } // proportionalTruncate applies proportional truncation when main component is too short. func (e *EdgeProtection) proportionalTruncate(serviceName, resourceType string, maxLength int) string { originalLength := len(fmt.Sprintf("%s-%s-%s", e.name, serviceName, resourceType)) if resourceType == "" { originalLength = len(fmt.Sprintf("%s-%s", e.name, serviceName)) } truncateFactorFloat := float64(maxLength) / float64(originalLength) truncateFactor := math.Floor(truncateFactorFloat*100) / 100 mainComponentLength := int(math.Floor(float64(len(e.name)) * truncateFactor)) serviceNameLength := int(math.Floor(float64(len(serviceName)) * truncateFactor)) resourceTypeLength := int(math.Floor(float64(len(resourceType)) * truncateFactor)) if resourceType == "" { return fmt.Sprintf("%s-%s", e.name[:mainComponentLength], serviceName[:serviceNameLength]) } return fmt.Sprintf("%s-%s-%s", e.name[:mainComponentLength], serviceName[:serviceNameLength], resourceType[:resourceTypeLength]) }
package cloudflare import ( "fmt" "github.com/pulumi/pulumi-cloudflare/sdk/v6/go/cloudflare" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) // setupDDoSAttackNotifications sets up email notifications for DDoS attacks // // Free plan only supports email notifications. // // See: https://developers.cloudflare.com/notifications/ func (e *EdgeProtection) setupDDoSAttackNotifications(ctx *pulumi.Context, email string) (*cloudflare.NotificationPolicy, error) { policyName := e.newResourceName("ddos-attack", "notification-policy", 64) ddosNotificationPolicy, err := cloudflare.NewNotificationPolicy(ctx, policyName, &cloudflare.NotificationPolicyArgs{ AccountId: pulumi.String(e.CloudflareZone.CloudflareAccountID), AlertType: pulumi.String("dos_attack_l7"), // dos*attack*l7 ??? Mechanisms: &cloudflare.NotificationPolicyMechanismsArgs{ Emails: cloudflare.NotificationPolicyMechanismsEmailArray{ &cloudflare.NotificationPolicyMechanismsEmailArgs{ Id: pulumi.String(email), }, }, }, Name: pulumi.String(policyName), Description: pulumi.String("DDoS attack notifications."), Filters: &cloudflare.NotificationPolicyFiltersArgs{}, Enabled: pulumi.Bool(true), }, pulumi.Parent(e)) if err != nil { return nil, fmt.Errorf("failed to create DDoS attack notification policy: %w", err) } return ddosNotificationPolicy, nil }
package cloudflare import ( "fmt" "github.com/pulumi/pulumi-cloudflare/sdk/v6/go/cloudflare" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) // createOptimizationRules applies all the traffic optimization rules. func (e *EdgeProtection) createOptimizationRules(ctx *pulumi.Context, zone *cloudflare.Zone) (*cloudflare.Ruleset, *cloudflare.Ruleset, *cloudflare.Ruleset, error) { // 1. Create cache ruleset cacheRuleset, err := e.createCacheRules(ctx, zone) if err != nil { return nil, nil, nil, fmt.Errorf("failed to create cache ruleset: %w", err) } // 2. Create redirect ruleset redirectRuleset, err := e.createRedirectRules(ctx, zone) if err != nil { return nil, nil, nil, fmt.Errorf("failed to create redirect ruleset: %w", err) } // 3. Create configuration ruleset configRuleset, err := e.createConfigurationRules(ctx, zone) if err != nil { return nil, nil, nil, fmt.Errorf("failed to create configuration ruleset: %w", err) } return cacheRuleset, redirectRuleset, configRuleset, nil } // createConfigurationRules creates configuration rules (replaces security settings page rule) to: // - set security level // - enable browser integrity check // // See: https://developers.cloudflare.com/rules/configuration-rules/ func (e *EdgeProtection) createConfigurationRules(ctx *pulumi.Context, zone *cloudflare.Zone) (*cloudflare.Ruleset, error) { // See: https://developers.cloudflare.com/rules/configuration-rules/create-api/#basic-rule-settings configRuleset, err := cloudflare.NewRuleset(ctx, e.newResourceName("config-ruleset", "optimization", 64), &cloudflare.RulesetArgs{ ZoneId: zone.ID(), Name: pulumi.String("Configuration Rules"), Kind: pulumi.String("zone"), Phase: pulumi.String("http_config_settings"), Description: pulumi.String("Zone configuration overrides for security and optimization"), Rules: cloudflare.RulesetRuleArray{ // Security level configuration (replaces SecurityLevel page rule setting) &cloudflare.RulesetRuleArgs{ Action: pulumi.String("set_config"), Expression: pulumi.String("true"), // Apply to all requests Description: pulumi.String("Set security level and browser checks"), ActionParameters: &cloudflare.RulesetRuleActionParametersArgs{ // Security level equivalent SecurityLevel: e.SecurityLevel, // Browser integrity check Bic: e.BrowserCheckEnabled, }, Enabled: pulumi.Bool(true), }, }, }, pulumi.Parent(e)) if err != nil { return nil, fmt.Errorf("failed to create configuration ruleset: %w", err) } return configRuleset, nil }
package cloudflare import ( "github.com/pulumi/pulumi-cloudflare/sdk/v6/go/cloudflare" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) // Helper functions for setting defaults func setDefaultString(input pulumi.StringInput, defaultValue string) pulumi.StringOutput { if input == nil { return pulumi.String(defaultValue).ToStringOutput() } return input.ToStringOutput() } func setDefaultInt(input pulumi.IntInput, defaultValue int) pulumi.IntOutput { if input == nil { return pulumi.Int(defaultValue).ToIntOutput() } return input.ToIntOutput() } func setDefaultBool(input pulumi.BoolInput, defaultValue bool) pulumi.BoolOutput { if input == nil { return pulumi.Bool(defaultValue).ToBoolOutput() } return input.ToBoolOutput() } func flattenRulesetRules(rules []interface{}) []cloudflare.RulesetRule { var allRules []cloudflare.RulesetRule for _, item := range rules { rules := item.([]cloudflare.RulesetRule) allRules = append(allRules, rules...) } return allRules }
package cloudflare import ( "fmt" "github.com/pulumi/pulumi-cloudflare/sdk/v6/go/cloudflare" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) // createRateLimitRules creates modern rate limiting rules (replaces legacy RateLimit). // Only 1 rule in the phase http_ratelimit can be used under the free tier. // See: // https://developers.cloudflare.com/waf/rate-limiting-rules/#availability func (e *EdgeProtection) createRateLimitRuleset(ctx *pulumi.Context, zone *cloudflare.Zone) (*cloudflare.Ruleset, error) { rateLimitRuleset, err := cloudflare.NewRuleset(ctx, e.newResourceName("rate-limit-ruleset", "ddos-custom", 64), &cloudflare.RulesetArgs{ ZoneId: zone.ID(), Name: pulumi.String("Rate Limiting Rules"), Kind: pulumi.String("zone"), Phase: pulumi.String("http_ratelimit"), Description: pulumi.String("Rate limiting for DDoS protection and abuse prevention"), // maximum number of rules in the phase http_ratelimit is 1 Rules: cloudflare.RulesetRuleArray{ // General rate limiting rule &cloudflare.RulesetRuleArgs{ Action: e.RateLimitMode, Expression: pulumi.String("true"), // Apply to all requests Description: pulumi.String("General rate limiting for DDoS protection"), Ratelimit: &cloudflare.RulesetRuleRatelimitArgs{ Characteristics: pulumi.StringArray{ // Rate limit by source IP pulumi.String("ip.src"), // Mandatory characteristic for all rate limiting rules to // ensure counters are not shared across data centers pulumi.String("cf.colo.id"), }, Period: e.RateLimitPeriodSeconds.ApplyT(func(rateLimitPeriod int) int { return rateLimitPeriod }).(pulumi.IntOutput), RequestsPerPeriod: e.RateLimitThreshold.ApplyT(func(rateLimitThreshold int) int { return rateLimitThreshold }).(pulumi.IntOutput), MitigationTimeout: e.MitigationTimeoutSeconds.ApplyT(func(rateLimitTimeout int) int { return rateLimitTimeout }).(pulumi.IntOutput), }, Enabled: pulumi.Bool(true), }, }, }, pulumi.Parent(e)) if err != nil { return nil, fmt.Errorf("failed to create rate limit ruleset: %w", err) } return rateLimitRuleset, nil }
package cloudflare import ( "fmt" "strings" "github.com/pulumi/pulumi-cloudflare/sdk/v6/go/cloudflare" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) // createRedirectRules creates modern redirect rules (replaces legacy HTTPS page rule) func (e *EdgeProtection) createRedirectRules(ctx *pulumi.Context, zone *cloudflare.Zone) (*cloudflare.Ruleset, error) { rules := cloudflare.RulesetRuleArray{} for _, upstream := range e.Upstreams { rules = append(rules, &cloudflare.RulesetRuleArgs{ // Redirect www to root // See: https://developers.cloudflare.com/rules/url-forwarding/examples/redirect-www-to-root/ Action: pulumi.String("redirect"), Expression: pulumi.Sprintf(`http.host eq "www.%s"`, upstream.DomainURL), Description: pulumi.Sprintf("Redirect www.%s to %s domain", upstream.DomainURL, upstream.DomainURL), ActionParameters: &cloudflare.RulesetRuleActionParametersArgs{ FromValue: &cloudflare.RulesetRuleActionParametersFromValueArgs{ PreserveQueryString: pulumi.Bool(true), StatusCode: pulumi.Float64(301), TargetUrl: &cloudflare.RulesetRuleActionParametersFromValueTargetUrlArgs{ Value: pulumi.Sprintf(`https://%s${request.uri}`, upstream.DomainURL), }, }, }, Enabled: pulumi.Bool(true), }, &cloudflare.RulesetRuleArgs{ // Redirect trailing slashes for SEO Action: pulumi.String("redirect"), Expression: pulumi.Sprintf("(%s)", strings.Join([]string{ `ends_with(http.request.uri.path, "/")`, // Ends with / `(http.request.uri.path ne "/")`, // Not root path `(not starts_with(http.request.uri.path, "/api/"))`, // Doesn't start }, " and ")), Description: pulumi.String("Remove trailing slashes for SEO"), ActionParameters: &cloudflare.RulesetRuleActionParametersArgs{ FromValue: &cloudflare.RulesetRuleActionParametersFromValueArgs{ PreserveQueryString: pulumi.Bool(true), StatusCode: pulumi.Float64(301), TargetUrl: &cloudflare.RulesetRuleActionParametersFromValueTargetUrlArgs{ Expression: pulumi.String(`substring(http.request.uri.path,0,-1)`), }, }, }, Enabled: pulumi.Bool(true), }, // TODO add configurable geo location rules // See: https://developers.cloudflare.com/rules/url-forwarding/examples/redirect-all-country/ ) } // See: https://developers.cloudflare.com/rules/url-forwarding/ redirectRuleset, err := cloudflare.NewRuleset(ctx, e.newResourceName("redirect-ruleset", "optimization", 64), &cloudflare.RulesetArgs{ ZoneId: zone.ID(), Name: pulumi.String("HTTPS Redirect Rules"), Kind: pulumi.String("zone"), Phase: pulumi.String("http_request_dynamic_redirect"), Description: pulumi.String("Force HTTPS and handle redirects"), Rules: rules, }, pulumi.Parent(e)) if err != nil { return nil, fmt.Errorf("failed to create redirect ruleset: %w", err) } return redirectRuleset, nil }
package cloudflare import ( "fmt" "github.com/pulumi/pulumi-cloudflare/sdk/v6/go/cloudflare" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) // configureTLSSettings configures SSL/TLS settings for the zone. func (e *EdgeProtection) configureTLSSettings(ctx *pulumi.Context, zone *cloudflare.Zone) ([]*cloudflare.ZoneSetting, error) { // 1. SSL/TLS Encryption Mode // There are edge certificates and origin certificates. // Edge certs are between the browser and Cloudflare proxies. These are automatically provisioned. // Origin certs are between Cloudflare and the app. // See: // - https://developers.cloudflare.com/ssl/origin-configuration/ssl-modes/ // - https://developers.cloudflare.com/ssl/concepts/#ssltls-certificate sslModeSetting, err := cloudflare.NewZoneSetting(ctx, e.newResourceName("ssl-mode", "tls", 64), &cloudflare.ZoneSettingArgs{ ZoneId: zone.ID(), SettingId: pulumi.String("ssl"), // Setting name for encryption mode Value: e.TLSEncryptionMode, // "off", "flexible", "full", "strict" }, pulumi.Parent(e)) if err != nil { return nil, fmt.Errorf("failed to configure SSL mode: %w", err) } // 2. Minimum TLS Version minTLSSetting, err := cloudflare.NewZoneSetting(ctx, e.newResourceName("min-tls", "tls", 64), &cloudflare.ZoneSettingArgs{ ZoneId: zone.ID(), SettingId: pulumi.String("min_tls_version"), // Individual setting Value: e.MinTLSVersion, // "1.0", "1.1", "1.2", "1.3" }, pulumi.Parent(e)) if err != nil { return nil, fmt.Errorf("failed to configure minimum TLS version: %w", err) } // 3. TLS 1.3 Support tls13Setting, err := cloudflare.NewZoneSetting(ctx, e.newResourceName("tls13", "tls", 64), &cloudflare.ZoneSettingArgs{ ZoneId: zone.ID(), SettingId: pulumi.String("tls_1_3"), // Note: tls_1_3, not tls_13 Value: e.TLS13Enabled.ApplyT(func(enabled bool) string { if enabled { return "on" } return "off" }).(pulumi.StringOutput), }, pulumi.Parent(e)) if err != nil { return nil, fmt.Errorf("failed to configure TLS 1.3: %w", err) } // 4. Always Use HTTPS // An alterative approach is to use a Ruleset on the phase "http_request_dynamic_redirect" // to use the Automatic Https Rewrites feature. // See: https://developers.cloudflare.com/ssl/edge-certificates/additional-options/automatic-https-rewrites/ // Favoring always_use_https zone setting to handle redirect before rules are evaluated. alwaysHTTPSSetting, err := cloudflare.NewZoneSetting(ctx, e.newResourceName("always-https", "tls", 64), &cloudflare.ZoneSettingArgs{ ZoneId: zone.ID(), SettingId: pulumi.String("always_use_https"), Value: e.AlwaysUseHTTPS.ApplyT(func(enabled bool) string { if enabled { return "on" } return "off" }).(pulumi.StringOutput), }, pulumi.Parent(e)) if err != nil { return nil, fmt.Errorf("failed to configure Always Use HTTPS: %w", err) } // 6. Automatic universal certificates for all domains. // Automatic, no configuration needed. Ensure domain is added to Cloudflare // and it will automatically get Universal certs. // // Automatically provisioned certs covers: // - Zone apex (e.g., example.com) // - All first-level subdomains (e.g., subdomain.example.com) // // Cloudflare chooses the certificate authority (CA) and it can change anytime. // See: // - https://developers.cloudflare.com/ssl/edge-certificates/universal-ssl/enable-universal-ssl/ return []*cloudflare.ZoneSetting{ sslModeSetting, minTLSSetting, tls13Setting, alwaysHTTPSSetting, }, nil }
package cloudflare import ( "fmt" "strings" "github.com/pulumi/pulumi-cloudflare/sdk/v6/go/cloudflare" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) // createWAFCustomRules creates custom WAF rules for apps to customize to their needs. // // Max 5 WAF rules allowed under free tier. // // Operator "matches" for expressions is reserved for Business plan and WAF Advanced plan. func (e *EdgeProtection) createWAFCustomRules(ctx *pulumi.Context, zone *cloudflare.Zone) (*cloudflare.Ruleset, error) { wafCustomRuleset, err := cloudflare.NewRuleset(ctx, e.newResourceName("waf-custom-ruleset", "security", 64), &cloudflare.RulesetArgs{ ZoneId: zone.ID(), Name: pulumi.String("WAF Custom Security Rules"), Kind: pulumi.String("zone"), Phase: pulumi.String("http_request_firewall_custom"), Description: pulumi.String("Custom WAF rules for blocking common attacks and malicious traffic"), Rules: cloudflare.RulesetRuleArray{ // Rule 1: Block CMS and WordPress specific paths &cloudflare.RulesetRuleArgs{ Action: pulumi.String("block"), Expression: pulumi.String(generateCMSPathBlockingExpression()), Description: pulumi.String("Block CMS, WordPress, and application-specific attack vectors"), Enabled: pulumi.Bool(true), }, // Rule 2: Block system, configuration, version control paths and malicious user agents &cloudflare.RulesetRuleArgs{ Action: pulumi.String("block"), Expression: pulumi.Sprintf("(%s) or (%s)", generateSystemConfigPathBlockingExpression(), generateUserAgentBlockingExpression()), Description: pulumi.String("Block system files, configuration, version control access, and malicious user agents"), Enabled: pulumi.Bool(true), }, // Rule 3: Block admin panels, backup files, and sensitive areas &cloudflare.RulesetRuleArgs{ Action: pulumi.String("block"), Expression: pulumi.String(generateAdminBackupPathBlockingExpression()), Description: pulumi.String("Block admin panels, backup files, and sensitive directories"), Enabled: pulumi.Bool(true), }, // Rule 4: Block development, API, and server information paths &cloudflare.RulesetRuleArgs{ Action: pulumi.String("block"), Expression: pulumi.String(generateDevAPIPathBlockingExpression()), Description: pulumi.String("Block development tools, API endpoints, and server information"), Enabled: pulumi.Bool(true), }, // Rule 5: Block dangerous HTTP methods and challenge suspicious behavior &cloudflare.RulesetRuleArgs{ Action: pulumi.String("managed_challenge"), Expression: pulumi.Sprintf("(%s)", strings.Join([]string{ `(http.request.method eq "TRACE")`, `(http.request.method eq "TRACK")`, `(http.request.method eq "DEBUG")`, `(http.request.method eq "CONNECT")`, `(http.request.uri.query contains "union select")`, `(http.request.uri.query contains "drop table")`, `(http.request.uri.query contains "insert into")`, `(http.request.uri.query contains "<script")`, `(http.request.uri.query contains "javascript:")`, }, " or ")), Description: pulumi.String("Challenge dangerous HTTP methods and requests with SQL injection or XSS patterns"), Enabled: pulumi.Bool(true), }, }, }, pulumi.Parent(e)) if err != nil { return nil, fmt.Errorf("failed to create WAF custom ruleset: %w", err) } return wafCustomRuleset, nil }
package cloudflare import ( "fmt" "strings" ) // WAF Path Blocking Configuration // Generated slices for comprehensive web application firewall protection // WordPressPaths - Paths commonly targeted by attackers in this category var WordPressPaths = []string{ "/wp-admin/", "/wp-login.php", "/wp-config.php", "/xmlrpc.php", "/wp-includes/", "/wp-content/debug.log", "/wp-json/wp/v2/users", "/wp-cron.php", "/wp-config-sample.php", "/wp-load.php", "/wp-settings.php", "/wp-blog-header.php", "/wp-links-opml.php", "/wp-trackback.php", "/wp-comments-post.php", "/readme.html", "/license.txt", } // DatabaseManagementPaths - Paths commonly targeted by attackers in this category var DatabaseManagementPaths = []string{ "/phpmyadmin/", "/pma/", "/phpMyAdmin/", "/mysql/", "/myadmin/", "/db/", "/dbadmin/", "/database/", "/adminer.php", "/adminer/", "/sql/", "/mysql-admin/", "/phpMiniAdmin.php", "/mydb/", } // ConfigurationFilePaths - Paths commonly targeted by attackers in this category var ConfigurationFilePaths = []string{ "/.env", "/.env.local", "/.env.production", "/.env.development", "/.env.staging", "/config.php", "/configuration.php", "/config/", "/settings.php", "/app.config", "/web.config", "/application.properties", "/.htaccess", "/.htpasswd", "/httpd.conf", "/nginx.conf", "/apache.conf", } // VersionControlPaths - Paths commonly targeted by attackers in this category var VersionControlPaths = []string{ "/.git/", "/.git/HEAD", "/.git/config", "/.git/index", "/.git/objects/", "/.git/refs/", "/.git/logs/", "/.svn/", "/.hg/", "/.bzr/", "/CVS/", "/.gitignore", "/.gitconfig", "/git/", "/.git-credentials", } // AdminPanelPaths - Paths commonly targeted by attackers in this category var AdminPanelPaths = []string{ "/admin/", "/administrator/", "/admin.php", "/admin.html", "/adminpanel/", "/control/", "/controlpanel/", "/cpanel/", "/dashboard/", "/manage/", "/manager/", "/panel/", "/webadmin/", "/sysadmin/", "/root/", "/superuser/", } // BackupFilePaths - Paths commonly targeted by attackers in this category var BackupFilePaths = []string{ "/backup/", "/backups/", "/bak/", "/backup.sql", "/backup.zip", "/backup.tar.gz", "/db_backup/", "/site_backup/", "/*.bak", "/*.backup", "/*.old", "/*.orig", "/*.tmp", "/dump/", "/dumps/", "/.backup/", } // DevelopmentTestingPaths - Paths commonly targeted by attackers in this category var DevelopmentTestingPaths = []string{ "/test/", "/tests/", "/testing/", "/dev/", "/development/", "/debug/", "/tmp/", "/temp/", "/cache/", "/log/", "/logs/", "/error_log", "/access_log", "/phpinfo.php", "/info.php", "/test.php", "/debug.php", } // SystemInformationPaths - Paths commonly targeted by attackers in this category var SystemInformationPaths = []string{ "/proc/", "/etc/passwd", "/etc/shadow", "/etc/hosts", "/var/log/", "/usr/", "/bin/", "/boot/", "/dev/", "/home/", "/lib/", "/media/", "/mnt/", "/opt/", "/root/", "/sbin/", "/srv/", "/sys/", "/var/", } // APIEndpointPaths - Paths commonly targeted by attackers in this category var APIEndpointPaths = []string{ "/api/v1/admin", "/api/admin", "/api/config", "/api/users", "/api/user/", "/api/internal/", "/api/private/", "/api/debug/", "/api/test/", "/graphql", "/swagger/", "/swagger-ui/", "/api-docs/", "/openapi.json", "/api/health", } // ApplicationSpecificPaths - Paths commonly targeted by attackers in this category var ApplicationSpecificPaths = []string{ "/app/", "/application/", "/includes/", "/inc/", "/lib/", "/libraries/", "/vendor/", "/composer.json", "/composer.lock", "/package.json", "/package-lock.json", "/node_modules/", "/.dockerignore", "/Dockerfile", "/docker-compose.yml", "/Makefile", } // ServerFilePaths - Paths commonly targeted by attackers in this category var ServerFilePaths = []string{ "/server-status", "/server-info", "/status", "/stats/", "/statistics/", "/metrics/", "/health/", "/ping", "/version", "/info", "/.well-known/", "/crossdomain.xml", "/clientaccesspolicy.xml", "/robots.txt", "/sitemap.xml", } // CMSSpecificPaths - Paths commonly targeted by attackers in this category var CMSSpecificPaths = []string{ "/administrator/", "/components/", "/modules/", "/templates/", "/plugins/", "/libraries/", "/configuration.php", "/sites/default/settings.php", "/sites/default/files/", "/user/", "/admin/config/", "/app/etc/local.xml", "/admin/", "/downloader/", "/concrete/", "/system/", "/fuel/", "/craft/", "/ghost/", "/typo3/", } // PathTraversalPatterns - Paths commonly targeted by attackers in this category var PathTraversalPatterns = []string{ "../", "%2e%2e", "%2e%2e%2f", "%2e%2e%5c", "....//", "%252e%252e%252f", "%c0%ae%c0%ae%c0%af", "%c1%1c%c1%1c%c1%1c", "..%2f", "..%5c", "..%252f", "..%255c", } // MaliciousUserAgents - User agents commonly used by attackers and bots var MaliciousUserAgents = []string{ "sqlmap", "nmap", "nikto", "masscan", "dirbuster", } // generateCMSPathBlockingExpression creates WAF expression for CMS and WordPress specific paths func generateCMSPathBlockingExpression() string { // Group 1: CMS-related paths (WordPress, CMS-specific, Application-specific) group1Paths := make([]string, 0) group1Paths = append(group1Paths, WordPressPaths...) group1Paths = append(group1Paths, CMSSpecificPaths...) group1Paths = append(group1Paths, ApplicationSpecificPaths...) return generatePathExpression(group1Paths) } // generateSystemConfigPathBlockingExpression creates WAF expression for system and configuration paths func generateSystemConfigPathBlockingExpression() string { // Group 2: System, configuration, and version control paths group2Paths := make([]string, 0) group2Paths = append(group2Paths, ConfigurationFilePaths...) group2Paths = append(group2Paths, VersionControlPaths...) group2Paths = append(group2Paths, SystemInformationPaths...) group2Paths = append(group2Paths, PathTraversalPatterns...) return generatePathExpression(group2Paths) } // generateAdminBackupPathBlockingExpression creates WAF expression for admin and backup paths func generateAdminBackupPathBlockingExpression() string { // Group 3: Admin panels, backup files, and database management group3Paths := make([]string, 0) group3Paths = append(group3Paths, AdminPanelPaths...) group3Paths = append(group3Paths, BackupFilePaths...) group3Paths = append(group3Paths, DatabaseManagementPaths...) return generatePathExpression(group3Paths) } // generateDevAPIPathBlockingExpression creates WAF expression for development and API paths func generateDevAPIPathBlockingExpression() string { // Group 4: Development, testing, API endpoints, and server info group4Paths := make([]string, 0) group4Paths = append(group4Paths, DevelopmentTestingPaths...) group4Paths = append(group4Paths, APIEndpointPaths...) group4Paths = append(group4Paths, ServerFilePaths...) return generatePathExpression(group4Paths) } // generatePathExpression helper function to create WAF expressions from path slices func generatePathExpression(paths []string) string { expressions := make([]string, len(paths)) for i, path := range paths { expressions[i] = fmt.Sprintf(`(http.request.uri.path contains "%s")`, path) } // Join all expressions with "or" and wrap in parentheses return fmt.Sprintf("(%s)", strings.Join(expressions, " or ")) } // generateUserAgentBlockingExpression creates WAF expression for blocking malicious user agents func generateUserAgentBlockingExpression() string { // Additional user agent checks additionalChecks := []string{ `(http.user_agent eq "")`, `(len(http.user_agent) < 10)`, } // Create slice with correct total capacity totalExpressions := make([]string, 0, len(MaliciousUserAgents)+len(additionalChecks)) // Add user agent expressions for _, agent := range MaliciousUserAgents { totalExpressions = append(totalExpressions, fmt.Sprintf(`(http.user_agent contains "%s")`, agent)) } // Add additional checks totalExpressions = append(totalExpressions, additionalChecks...) // Join all expressions with "or" and wrap in parentheses return fmt.Sprintf("(%s)", strings.Join(totalExpressions, " or ")) }