AWS WebACL API Proposal
Overview
This proposal defines the API for AWS WebACL (Web Application Firewall) functionality in Cloud Manager. The API will be exposed through the cloud-resources.kyma-project.io/v1beta1 API group as a Kubernetes CRD, enabling customers to protect their applications from common web exploits.
Proposed API Structure
apiVersion: cloud-resources.kyma-project.io/v1beta1
kind: AwsWebAcl
metadata:
name: example-webacl
namespace: default
spec:
defaultAction: # Required: allow or block
allow: {}
visibilityConfig: # Required: CloudWatch metrics
cloudWatchMetricsEnabled: true
metricName: example-webacl-metrics
sampledRequestsEnabled: true
description: "..." # Optional
rules: [] # Optional: max 100 rules
Proposed Statement Types
The following statement types will be supported for inline rule definitions:
- geoMatch - Country-based filtering with ForwardedIPConfig support
- rateBased - Rate limiting per IP address
- byteMatch - Pattern matching in request fields
- managedRuleGroup - AWS-managed rule sets (OWASP, Bot Control, etc.)
- labelMatch - Match based on labels from previous rules
- sizeConstraint - Size-based filtering (DDoS protection)
- sqliMatch - SQL injection detection
- xssMatch - Cross-site scripting detection
- regexMatch - Regular expression pattern matching
- asnMatch - Autonomous System Number matching
- ipSetReference - IP address filtering via separate AwsIPSet resource (see IPSet section below)
Design Decisions
MetricName: Decision Required
Context:
- Each AWS WebACL publishes metrics to CloudWatch under the
AWS/WAFV2 namespace
- Multiple WebACLs can share the same MetricName - they are distinguished by the WebACL dimension
- Multiple Kyma instances (Shoots) can share the same AWS account in production environments
- Multiple WebACLs per Shoot are common (one per namespace/application)
- AWS tags do NOT appear as CloudWatch dimensions - only MetricName, WebACL, Region, Rule, etc. are available
Requirement: Need to identify and collect metrics for all WebACLs belonging to a single Shoot when multiple Shoots share the same AWS account.
Question: Should visibilityConfig.metricName be required or optional? If optional, what default value should be used?
Option 1: Customer-Defined Only (Required)
visibilityConfig:
metricName: my-custom-name # REQUIRED - customer must provide
- Pro: Gives customers full control over metric organization
- Pro: No ambiguity about metric names
- Con: Burdens customers with understanding CloudWatch internals
- Con: Complicates bulk metric collection (must query each WebACL individually)
- Con: Cannot identify which WebACLs belong to which Shoot
Option 2: Customer-Defined with Default (Optional)
visibilityConfig:
metricName: my-custom-name # Optional - defaults if omitted
Customer can override, otherwise defaults to one of:
- WebACL resource name - Each WebACL gets unique metric name automatically
- Pro: Simple, unique per resource
- Con: Complicates bulk collection, breaks on renames
- Con: Cannot group by Shoot
- Shoot name - Groups all WebACLs in a Shoot
- Pro: Natural organizational boundary, efficient collection per Shoot
- Pro: Solves multi-Shoot, multi-WebACL per Shoot identification
- Con: Customer override breaks Shoot identification - if customer provides custom name, we lose Shoot association
- Hard-coded default (
"kyma-webacl") - All Kyma WebACLs share one MetricName
- Pro: Works with shared AWS accounts, single query gets all Kyma WebACLs
- Pro: Simple implementation, no need to discover names
- Con: Less granular than per-Shoot (but WebACL dimension still distinguishes individual WebACLs)
- Con: Cannot identify which WebACLs belong to which Shoot
Option 3: Hard-Coded Value Only (No Override) - Recommended for Multi-Shoot Scenarios
# metricName field not exposed - automatically set by reconciler
visibilityConfig:
cloudWatchMetricsEnabled: true
sampledRequestsEnabled: true
Two variants:
3a) Hard-coded to Shoot Name (Recommended for multiple Shoots per AWS account)
- Reconciler automatically sets
metricName = shoot-name
- Pro: Solves Shoot identification - query by MetricName returns all WebACLs for that Shoot
- Pro: Efficient bulk collection per Shoot
- Pro: Works correctly when multiple Shoots share AWS account (each Shoot gets unique MetricName)
- Con: No customer control - but necessary to maintain Shoot-to-metric association
3b) Hard-coded to static value (Only for single Shoot per AWS account)
- Reconciler automatically sets
metricName = "kyma-webacl"
- Pro: Simplest implementation, all WebACLs grouped
- Con: Does NOT solve multi-Shoot identification - all Shoots share same MetricName
Recommendation:
- For multi-Shoot environments (multiple Shoots per AWS account): Use Option 3a - Hard-code to Shoot name with no override
- For single-Shoot environments (one Shoot per AWS account): Either Option 3a or 3b works
Reason: Since AWS tags don't appear in CloudWatch dimensions, MetricName is the ONLY dimension available to group WebACLs by Shoot. Any customer override would break this association, making it impossible to identify which WebACLs belong to which Shoot.
And/Or/Not Statements: Not Implemented
Proposal: Logical operators (And, Or, Not) for combining statements will not be supported in the initial implementation.
Reason:
- Kubernetes API limitation: Circular nested structures cannot be expressed in OpenAPI v3 schema without workarounds
- CRD generation complexity: controller-gen cannot generate proper validation for recursive types
- Limited use cases: Most WAF rules can be expressed using single statements or managed rule groups
Workaround: Customers requiring complex logic can:
- Use managed rule groups with AWS-defined logic
- Create multiple separate rules with label-based matching (rule chains)
- Deploy external AWS WAF rule groups and reference them (future enhancement)
Future consideration: May be implemented using union types or external rule group references if demand justifies the complexity.
IPSet: Separate Resource
Proposal: IP-based filtering will be provided through a separate AwsIPSet resource that can be referenced from WebACL rules using Kubernetes object references.
Reason:
- Resource lifecycle: IPSets are reusable resources referenced by multiple WebACLs
- Management complexity: Inline IP lists would couple IPSet lifecycle to WebACL lifecycle
- AWS best practice: IPSets are managed as independent resources in AWS WAF
- Kubernetes pattern: Cross-resource references use standard object reference format
API Design:
---
# IPSet resource (separate CRD, cluster-scoped)
apiVersion: cloud-resources.kyma-project.io/v1beta1
kind: AwsIPSet
metadata:
name: corporate-ips
spec:
description: Corporate office IP addresses
ipAddressVersion: IPV4 # or IPV6
addresses:
- 203.0.113.0/24
- 198.51.100.10/32
- 192.0.2.44/32
---
# WebACL rule referencing the IPSet
apiVersion: cloud-resources.kyma-project.io/v1beta1
kind: AwsWebAcl
metadata:
name: my-webacl
spec:
defaultAction:
allow: {}
rules:
- name: allow-corporate-ips
priority: 0
action:
allow: {}
statement:
ipSetReference:
# Kubernetes object reference (cluster-scoped)
name: corporate-ips
visibilityConfig:
cloudWatchMetricsEnabled: true
sampledRequestsEnabled: true
Reference Format: Uses standard Kubernetes object reference pattern with only name field (no namespace since resources are cluster-scoped), similar to how klog.ObjectRef structures references.
Implementation Notes:
- Cloud Manager reconciler will:
- Watch for AwsIPSet resources and create/update AWS IPSet resources
- Resolve
ipSetReference to AWS IPSet ARN during WebACL reconciliation
- Validate that referenced IPSet exists before creating/updating WebACL
- Handle IPSet updates (WebACL automatically uses latest IPSet version)
- Prevent IPSet deletion if referenced by any WebACL (use finalizers)
RegexPatternSet: Omitted
Proposal: Regex pattern set references will not be supported. The regexMatch statement will be used for inline patterns instead.
Reason:
- Inline alternative exists:
regexMatch statement covers basic regex use cases (max 512 chars)
- Limited value-add: Pattern sets primarily benefit when sharing patterns across rules, which can be achieved through rule duplication or managed rule groups
- External resource complexity: Would require separate resource management similar to IPSet
Coverage: regexMatch statement supports patterns up to 512 characters, sufficient for common use cases like:
- Path pattern matching:
^/(admin|root|config)/.*$
- Email validation:
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
- API versioning:
^/api/v[0-9]+/.*$
AssociationConfig: Omitted
Proposal: The associationConfig field for custom request body inspection limits will not be supported.
Reason:
- ALB-only usage: Cloud Manager WebACLs are exclusively used with Application Load Balancer (ALB)
- Fixed configuration: ALB has a hardcoded 8KB request body inspection limit that cannot be customized
- No effect: AssociationConfig only applies to CloudFront, API Gateway, Cognito User Pool, App Runner, and Verified Access Instance
- Misleading API: Including the field would suggest functionality that doesn't work with ALB
Future consideration: May be implemented if demand justifies support for CloudFront or API Gateway integration, which would require additional resource association logic beyond current ALB-only scope.
Example Usage
Below is the complete sample demonstrating all available features:
# Complete AwsWebAcl Example with All Features
# ============================================
# This example demonstrates every available field in the AwsWebAcl CRD including:
# - All statement types (GeoMatch, RateBased, ByteMatch, ManagedRuleGroup, etc.)
# - All action types (Allow, Block, Count, Captcha, Challenge)
# - Custom request handling and custom responses
# - Rule labels for advanced filtering
# - Per-rule immunity time overrides
# - Association config for request body inspection limits
# - Token domains for CAPTCHA/Challenge
# - Custom response bodies
#
# NOTE: Logical operators (And/Or/Not) are NOT supported due to Kubernetes API limitations
# NOTE: Use ipSetReference statement to reference separate AwsIPSet resources for IP filtering
apiVersion: cloud-resources.kyma-project.io/v1beta1
kind: AwsWebAcl
metadata:
name: comprehensive-webacl-example
namespace: default
labels:
app: my-application
environment: production
team: security
spec:
# ====================
# DEFAULT ACTION (Required)
# ====================
# What to do when no rules match - exactly one of allow or block must be set
defaultAction:
allow: {}
# Alternatively use block with custom response:
# block:
# customResponse:
# responseCode: 403
# customResponseBodyKey: "block-page"
# responseHeaders:
# - name: X-Custom-Header
# value: blocked-by-waf
# ====================
# DESCRIPTION (Optional)
# ====================
description: "Production WebACL with comprehensive protection: bot control, geo-blocking, IP filtering, rate limiting, and custom pattern matching"
# ====================
# VISIBILITY CONFIG (Required)
# ====================
visibilityConfig:
cloudWatchMetricsEnabled: true
metricName: ComprehensiveWebAclMetrics
sampledRequestsEnabled: true
# ====================
# TOKEN DOMAINS (Optional, max 10)
# ====================
# Domains where CAPTCHA/Challenge tokens are valid (cross-domain support)
tokenDomains:
- example.com
- api.example.com
- www.example.com
# ====================
# CUSTOM RESPONSE BODIES (Optional)
# ====================
# Custom HTML/JSON content for block actions
customResponseBodies:
block-page:
contentType: TEXT_HTML
content: |
<html>
<head><title>Access Denied</title></head>
<body><h1>Access Denied</h1><p>Your request was blocked by our Web Application Firewall.</p></body>
</html>
json-errOrStatement:
contentType: APPLICATION_JSON
content: '{"error": "forbidden", "message": "Request blocked by WAF"}'
# ====================
# GLOBAL CAPTCHA CONFIG (Optional)
# ====================
# Default immunity time after passing CAPTCHA (60-259200 seconds)
captchaConfig:
immunityTime: 3600 # 1 hour
# ====================
# GLOBAL CHALLENGE CONFIG (Optional)
# ====================
# Default immunity time after passing Challenge (60-259200 seconds)
challengeConfig:
immunityTime: 7200 # 2 hours
# ====================
# RULES (Optional, max 100)
# ====================
rules:
# ============================================================
# RULE 1: BYTE MATCH with Rule Labels
# ============================================================
- name: allow-api-path
priority: 0
action:
allow:
customRequestHandling:
insertHeaders:
- name: X-API-Access
value: "true"
statement:
byteMatch:
searchString: "/api"
positionalConstraint: STARTS_WITH
fieldToMatch:
uriPath: true
textTransformations:
- priority: 0
type: LOWERCASE
# Apply labels to matching requests for use in subsequent rules
ruleLabels:
- name: "trusted:corporate"
- name: "source:internal"
visibilityConfig:
cloudWatchMetricsEnabled: true
metricName: allow-corporate-network
sampledRequestsEnabled: true
# ============================================================
# RULE 2: GEO MATCH - Block with custom response
# ============================================================
- name: block-high-risk-countries
priority: 1
action:
block:
customResponse:
responseCode: 403
customResponseBodyKey: "block-page"
responseHeaders:
- name: X-Block-Reason
value: geo-restricted
statement:
geoMatch:
countryCodes:
- "CN"
- "RU"
- "KP"
ruleLabels:
- name: "block:geo-restricted"
- name: "severity:high"
visibilityConfig:
cloudWatchMetricsEnabled: true
metricName: block-high-risk-countries
sampledRequestsEnabled: true
# ============================================================
# RULE 3: RATE BASED - Rate limiting
# ============================================================
- name: rate-limit-per-ip
priority: 2
action:
block:
customResponse:
responseCode: 429
customResponseBodyKey: "json-error"
statement:
rateBased:
limit: 2000
ruleLabels:
- name: "protection:rate-limit"
visibilityConfig:
cloudWatchMetricsEnabled: true
metricName: rate-limit-per-ip
sampledRequestsEnabled: true
# ============================================================
# RULE 4: BYTE MATCH - Pattern in query string
# ============================================================
- name: block-path-traversal
priority: 3
action:
block: {}
statement:
byteMatch:
searchString: "../"
positionalConstraint: CONTAINS
fieldToMatch:
queryString: true
textTransformations:
- priority: 0
type: URL_DECODE
- priority: 1
type: LOWERCASE
visibilityConfig:
cloudWatchMetricsEnabled: true
metricName: block-path-traversal
sampledRequestsEnabled: true
# ============================================================
# RULE 5: BYTE MATCH - Header inspection
# ============================================================
- name: block-malicious-user-agents
priority: 4
action:
block: {}
statement:
byteMatch:
searchString: "sqlmap"
positionalConstraint: CONTAINS
fieldToMatch:
singleHeader: "user-agent"
textTransformations:
- priority: 0
type: LOWERCASE
visibilityConfig:
cloudWatchMetricsEnabled: true
metricName: block-malicious-user-agents
sampledRequestsEnabled: true
# ============================================================
# RULE 6: CAPTCHA with per-rule immunity override
# ============================================================
- name: captcha-challenge-suspicious-ips
priority: 5
action:
captcha:
customRequestHandling:
insertHeaders:
- name: X-Captcha-Passed
value: "true"
statement:
geoMatch:
countryCodes:
- "US"
# Override global CAPTCHA immunity time for this specific rule
captchaConfig:
immunityTime: 300 # 5 minutes (shorter for suspicious IPs)
ruleLabels:
- name: "challenge:captcha"
- name: "risk:medium"
visibilityConfig:
cloudWatchMetricsEnabled: true
metricName: captcha-challenge-suspicious-ips
sampledRequestsEnabled: true
# ============================================================
# RULE 7: CHALLENGE action (silent bot verification)
# ============================================================
- name: challenge-bots
priority: 6
action:
challenge:
customRequestHandling:
insertHeaders:
- name: X-Challenge-Passed
value: "true"
statement:
rateBased:
limit: 500
# Override global Challenge immunity time for this rule
challengeConfig:
immunityTime: 600 # 10 minutes
ruleLabels:
- name: "challenge:silent-verification"
- name: "protection:bot-control"
visibilityConfig:
cloudWatchMetricsEnabled: true
metricName: challenge-bots
sampledRequestsEnabled: true
# ============================================================
# RULE 8: COUNT action (monitoring mode)
# ============================================================
- name: monitor-admin-access
priority: 7
action:
count:
customRequestHandling:
insertHeaders:
- name: X-Admin-Access-Logged
value: "true"
statement:
byteMatch:
searchString: "/admin"
positionalConstraint: STARTS_WITH
fieldToMatch:
uriPath: true
textTransformations:
- priority: 0
type: LOWERCASE
ruleLabels:
- name: "monitOrStatement:admin-path"
- name: "action:count"
visibilityConfig:
cloudWatchMetricsEnabled: true
metricName: monitor-admin-access
sampledRequestsEnabled: true
# ============================================================
# RULE 9: MANAGED RULE GROUP with OverrideAction
# ============================================================
- name: AWS-AWSManagedRulesBotControlRuleSet
priority: 10
# For managed rule groups, use OverrideAction instead of Action
overrideAction:
count: {} # Override all rules in the group to Count (monitoring mode)
# Alternatively use none to keep the group's default actions:
# none: {}
statement:
managedRuleGroup:
vendorName: AWS
name: AWSManagedRulesBotControlRuleSet
version: "" # Empty means latest version
excludedRules: []
visibilityConfig:
cloudWatchMetricsEnabled: true
metricName: AWS-AWSManagedRulesBotControlRuleSet
sampledRequestsEnabled: true
# ============================================================
# RULE 10: MANAGED RULE GROUP - OWASP Top 10
# ============================================================
- name: AWS-AWSManagedRulesCommonRuleSet
priority: 11
overrideAction:
none: {} # Use the rule group's default actions
statement:
managedRuleGroup:
vendorName: AWS
name: AWSManagedRulesCommonRuleSet
version: ""
excludedRules: []
visibilityConfig:
cloudWatchMetricsEnabled: true
metricName: AWS-AWSManagedRulesCommonRuleSet
sampledRequestsEnabled: true
# ============================================================
# RULE 11: MANAGED RULE GROUP - SQL Injection with exclusions
# ============================================================
- name: AWS-AWSManagedRulesSQLiRuleSet
priority: 12
overrideAction:
none: {}
statement:
managedRuleGroup:
vendorName: AWS
name: AWSManagedRulesSQLiRuleSet
version: ""
# Exclude specific rules that cause false positives
excludedRules:
- name: SQLi_QUERYARGUMENTS
visibilityConfig:
cloudWatchMetricsEnabled: true
metricName: AWS-AWSManagedRulesSQLiRuleSet
sampledRequestsEnabled: true
# RULE 16: LABEL MATCH - Block based on labels from previous rules
# ============================================================
# This rule demonstrates label-based filtering, matching requests
# that have been labeled by previous rules. Useful for creating
# rule chains where earlier rules classify traffic and later rules
# take action based on those classifications.
- name: block-labeled-threats
priority: 17
action:
block:
customResponse:
responseCode: 403
customResponseBodyKey: json-error
# Block if request has been labeled as "block:high-risk-combined" by Rule 12
statement:
labelMatch:
key: "block:high-risk-combined"
scope: "LABEL" # LABEL = exact match, NAMESPACE = prefix match
visibilityConfig:
cloudWatchMetricsEnabled: true
metricName: block-labeled-threats
sampledRequestsEnabled: true
# ============================================================
# RULE 17: SIZE CONSTRAINT - Block oversized requests (DDoS protection)
# ============================================================
# This rule blocks requests with query strings larger than 8KB,
# commonly used in DDoS attacks or attempts to exploit parsing vulnerabilities.
- name: block-oversized-query
priority: 18
action:
block:
customResponse:
responseCode: 413 # Request Entity Too Large
customResponseBodyKey: json-error
# Block if query string exceeds 8192 bytes after URL decoding
statement:
sizeConstraint:
comparisonOperator: "GT" # Greater than
size: 8192 # 8 KB
fieldToMatch:
queryString: true
textTransformations:
- priority: 0
type: "URL_DECODE"
ruleLabels:
- name: "protection:size-limit"
- name: "block:oversized-request"
visibilityConfig:
cloudWatchMetricsEnabled: true
metricName: block-oversized-query
sampledRequestsEnabled: true
# ============================================================
# RULE 18: SQL INJECTION DETECTION - Block SQL injection attacks
# ============================================================
# This rule detects SQL injection attempts in query parameters and body.
# HIGH sensitivity level catches more attacks but may have more false positives.
- name: block-sql-injection
priority: 19
action:
block:
customResponse:
responseCode: 403
customResponseBodyKey: json-error
# Detect SQL injection patterns in query strings
statement:
sqliMatch:
fieldToMatch:
queryString: true
textTransformations:
- priority: 0
type: "URL_DECODE"
- priority: 1
type: "HTML_ENTITY_DECODE"
sensitivityLevel: "HIGH" # More detections, may have false positives
ruleLabels:
- name: "security:sqli-protection"
- name: "block:sql-injection"
visibilityConfig:
cloudWatchMetricsEnabled: true
metricName: block-sql-injection
sampledRequestsEnabled: true
# ============================================================
# RULE 19: CROSS-SITE SCRIPTING DETECTION - Block XSS attacks
# ============================================================
# This rule detects cross-site scripting (XSS) attempts in request fields.
# XSS attacks inject malicious scripts that execute in users' browsers.
- name: block-xss-attacks
priority: 20
action:
block:
customResponse:
responseCode: 403
customResponseBodyKey: json-error
# Detect XSS patterns in query strings and body
statement:
xssMatch:
fieldToMatch:
queryString: true
textTransformations:
- priority: 0
type: "URL_DECODE"
- priority: 1
type: "HTML_ENTITY_DECODE"
- priority: 2
type: "JS_DECODE"
ruleLabels:
- name: "security:xss-protection"
- name: "block:xss-attack"
visibilityConfig:
cloudWatchMetricsEnabled: true
metricName: block-xss-attacks
sampledRequestsEnabled: true
# ============================================================
# RULE 20: REGEX PATTERN MATCHING - Match complex patterns
# ============================================================
# This rule uses regular expressions to match sophisticated attack patterns.
# Regex matching is more flexible than ByteMatch for complex validation.
- name: block-suspicious-patterns
priority: 21
action:
block:
customResponse:
responseCode: 403
customResponseBodyKey: json-error
# Block requests with suspicious patterns in URI path (e.g., admin/root/config paths)
statement:
regexMatch:
regexString: "^/(admin|root|config|backup|wp-admin)/.*$"
fieldToMatch:
uriPath: true
textTransformations:
- priority: 0
type: "LOWERCASE"
- priority: 1
type: "URL_DECODE"
ruleLabels:
- name: "security:regex-protection"
- name: "block:suspicious-path"
visibilityConfig:
cloudWatchMetricsEnabled: true
metricName: block-suspicious-patterns
sampledRequestsEnabled: true
# ============================================================
# RULE 21: ASN MATCHING - Match by Autonomous System Number
# ============================================================
# This rule blocks traffic from specific ASNs (network providers).
# Useful for blocking known malicious hosting providers or data centers.
- name: block-suspicious-asns
priority: 22
action:
block:
customResponse:
responseCode: 403
customResponseBodyKey: json-error
# Block requests from specific ASNs (e.g., suspicious hosting providers)
statement:
asnMatch:
autonomousSystemNumbers:
- 64496 # Reserved for documentation/examples
- 64497 # Reserved for documentation/examples
ruleLabels:
- name: "security:asn-block"
- name: "block:suspicious-network"
visibilityConfig:
cloudWatchMetricsEnabled: true
metricName: block-suspicious-asns
sampledRequestsEnabled: true
---
# ====================
# API FIELD REFERENCE
# ====================
#
# REQUIRED FIELDS:
# - spec.defaultAction (exactly one: allow or block)
# - spec.visibilityConfig.cloudWatchMetricsEnabled (bool)
# - spec.visibilityConfig.metricName (string, 1-128 chars)
# - spec.visibilityConfig.sampledRequestsEnabled (bool)
#
# OPTIONAL TOP-LEVEL FIELDS:
# - spec.description (string, max 256 chars)
# - spec.rules[] (array, max 100)
# - spec.tokenDomains[] (array, max 10)
# - spec.customResponseBodies (map[string]AwsWebAclCustomResponseBody)
# - spec.captchaConfig.immunityTime (int64, 60-259200 seconds)
# - spec.challengeConfig.immunityTime (int64, 60-259200 seconds)
#
# RULE REQUIRED FIELDS:
# - name (string, 1-128 chars, pattern: ^[0-9A-Za-z_-]+$)
# - priority (int32, min 0, must be unique)
# - action OR overrideAction (exactly one, mutually exclusive)
# - statement (exactly one statement type)
#
# RULE OPTIONAL FIELDS:
# - ruleLabels[] (array, max 100, name: 1-1024 chars)
# - captchaConfig.immunityTime (overrides global)
# - challengeConfig.immunityTime (overrides global)
# - visibilityConfig (overrides WebACL-level)
#
# ACTION TYPES (for regular rules):
# - allow: { customRequestHandling: { insertHeaders: [...] } }
# - block: { customResponse: { responseCode, customResponseBodyKey, responseHeaders } }
# - count: { customRequestHandling: { insertHeaders: [...] } }
# - captcha: { customRequestHandling: { insertHeaders: [...] } }
# - challenge: { customRequestHandling: { insertHeaders: [...] } }
#
# OVERRIDE ACTION TYPES (for managed rule groups):
# - none: {} # Don't override, use group's actions
# - count: { customRequestHandling: { insertHeaders: [...] } } # Override all to count
#
# STATEMENT TYPES (exactly one per rule):
# - geoMatch: { countryCodes: [...], forwardedIPConfig: {...} }
# - rateBased: { limit: int64, forwardedIPConfig: {...} }
# - byteMatch: { searchString, positionalConstraint, fieldToMatch, textTransformations }
# - managedRuleGroup: { vendorName, name, version, excludedRules, managedRuleGroupConfigs, ruleActionOverrides }
# - labelMatch: { key: "label:name", scope: "LABEL" | "NAMESPACE" } # Match based on labels from previous rules
# - sizeConstraint: { comparisonOperator: "EQ|NE|LE|LT|GE|GT", size, fieldToMatch, textTransformations } # Size-based filtering
# - sqliMatch: { fieldToMatch, textTransformations, sensitivityLevel: "LOW" | "HIGH" } # SQL injection detection
# - xssMatch: { fieldToMatch, textTransformations } # Cross-site scripting detection
# - regexMatch: { regexString, fieldToMatch, textTransformations } # Regular expression pattern matching
# - asnMatch: { autonomousSystemNumbers: [int64...], forwardedIPConfig: {...} } # Match by ASN
#
# NOTE: Logical operators (and/or/not) are NOT supported due to Kubernetes API limitations
# NOTE: Use ipSetReference statement to reference separate AwsIPSet resources for IP filtering
Implementation Plan
This proposal covers the initial implementation of AWS WebACL support in Cloud Manager:
Scope:
- 11 statement types for rule definitions (including ipSetReference)
- Separate AwsIPSet CRD for IP address management
- WebACL-level and rule-level visibility configuration
- Custom response bodies and request handling
- CAPTCHA/Challenge configuration
- Managed rule group integration
- CloudWatch metrics integration (via VisibilityConfig)
Out of Scope (Future Enhancements):
- Logical operators (And/Or/Not) - requires workaround for Kubernetes API circular reference limitations
- RegexPatternSet references - covered by inline regexMatch
- AssociationConfig - not applicable for ALB-only usage
Target Use Case:
- Application Load Balancer (ALB) integration
- Request body inspection fixed at 8KB (ALB limitation)
See example YAML above for comprehensive API usage covering all supported features.
AWS WebACL API Proposal
Overview
This proposal defines the API for AWS WebACL (Web Application Firewall) functionality in Cloud Manager. The API will be exposed through the
cloud-resources.kyma-project.io/v1beta1API group as a Kubernetes CRD, enabling customers to protect their applications from common web exploits.Proposed API Structure
Proposed Statement Types
The following statement types will be supported for inline rule definitions:
Design Decisions
MetricName: Decision Required
Context:
AWS/WAFV2namespaceRequirement: Need to identify and collect metrics for all WebACLs belonging to a single Shoot when multiple Shoots share the same AWS account.
Question: Should
visibilityConfig.metricNamebe required or optional? If optional, what default value should be used?Option 1: Customer-Defined Only (Required)
Option 2: Customer-Defined with Default (Optional)
Customer can override, otherwise defaults to one of:
"kyma-webacl") - All Kyma WebACLs share one MetricNameOption 3: Hard-Coded Value Only (No Override) - Recommended for Multi-Shoot Scenarios
Two variants:
3a) Hard-coded to Shoot Name (Recommended for multiple Shoots per AWS account)
metricName = shoot-name3b) Hard-coded to static value (Only for single Shoot per AWS account)
metricName = "kyma-webacl"Recommendation:
Reason: Since AWS tags don't appear in CloudWatch dimensions, MetricName is the ONLY dimension available to group WebACLs by Shoot. Any customer override would break this association, making it impossible to identify which WebACLs belong to which Shoot.
And/Or/Not Statements: Not Implemented
Proposal: Logical operators (And, Or, Not) for combining statements will not be supported in the initial implementation.
Reason:
Workaround: Customers requiring complex logic can:
Future consideration: May be implemented using union types or external rule group references if demand justifies the complexity.
IPSet: Separate Resource
Proposal: IP-based filtering will be provided through a separate
AwsIPSetresource that can be referenced from WebACL rules using Kubernetes object references.Reason:
API Design:
Reference Format: Uses standard Kubernetes object reference pattern with only
namefield (nonamespacesince resources are cluster-scoped), similar to howklog.ObjectRefstructures references.Implementation Notes:
ipSetReferenceto AWS IPSet ARN during WebACL reconciliationRegexPatternSet: Omitted
Proposal: Regex pattern set references will not be supported. The
regexMatchstatement will be used for inline patterns instead.Reason:
regexMatchstatement covers basic regex use cases (max 512 chars)Coverage:
regexMatchstatement supports patterns up to 512 characters, sufficient for common use cases like:^/(admin|root|config)/.*$^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$^/api/v[0-9]+/.*$AssociationConfig: Omitted
Proposal: The
associationConfigfield for custom request body inspection limits will not be supported.Reason:
Future consideration: May be implemented if demand justifies support for CloudFront or API Gateway integration, which would require additional resource association logic beyond current ALB-only scope.
Example Usage
Below is the complete sample demonstrating all available features:
Implementation Plan
This proposal covers the initial implementation of AWS WebACL support in Cloud Manager:
Scope:
Out of Scope (Future Enhancements):
Target Use Case:
See example YAML above for comprehensive API usage covering all supported features.