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", 63), &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", 63), &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", 63), &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", 63)
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"
namer "github.com/davidmontoyago/commodity-namer"
"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
namer.Namer
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{
Namer: namer.New(name),
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"
"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", 63)
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", 63), &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", 63), &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.Int(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.Int(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", 63), &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", 63), &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", 63), &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", 63), &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", 63), &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", 63), &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 "))
}