OCI Web Application Firewall: Production Configuration, Custom Rules, and Terraform Automation

A load balancer in front of your application handles traffic distribution. TLS termination ensures data in transit is encrypted. Network Security Groups control which ports are reachable. None of those protect you from an attacker who sends a valid HTTPS request on port 443 carrying a SQL injection payload in a query parameter. That is a different class of problem, and it requires a different layer of defense.

OCI Web Application Firewall operates at Layer 7. It inspects the content of HTTP and HTTPS requests, matches them against threat signatures and custom rules, and takes action before the request reaches your backend. It handles SQL injection, cross-site scripting, remote file inclusion, HTTP protocol violations, bot traffic, and rate-based attacks. It sits in front of your OCI Load Balancer or directly in front of an origin, integrated into OCI’s networking fabric without requiring you to route traffic through a third-party service.

In this post I will walk through deploying OCI WAF with Terraform, configuring protection rules with proper tuning to avoid false positives, building custom rules for application-specific threats, setting up rate limiting, and validating the configuration with real test traffic.

How OCI WAF Fits Into the Request Path

OCI WAF integrates with OCI Load Balancer as a policy attachment. The WAF policy sits on the load balancer and inspects requests before they reach the backend set. You can also attach a WAF policy to an API Gateway deployment. In both cases the WAF is inline in the request path, not a sidecar or proxy.

Client Request (HTTPS)
|
OCI Load Balancer (WAF Policy attached)
|
WAF Inspection Engine
- Protection Rules (OWASP signatures)
- Custom Rules (application-specific)
- Rate Limiting
- Bot Management
|
Backend Set (compute instances / OKE pods)
|
Application

The WAF does not terminate TLS separately. TLS termination happens at the load balancer listener. The WAF inspects the decrypted request payload before forwarding it to the backend.

Step 1: IAM Policy for WAF

resource "oci_identity_policy" "waf_policy" {
compartment_id = var.compartment_id
name = "waf-management-policy"
description = "Allows WAF service to work with load balancer resources"
statements = [
"Allow service waas to manage waas-family in compartment id ${var.compartment_id}",
"Allow service waas to read load-balancers in compartment id ${var.compartment_id}",
"Allow group ${var.ops_group_name} to manage waas-family in compartment id ${var.compartment_id}"
]
}

Step 2: WAF Policy with Protection Rules

The WAF policy is the core resource. It contains the protection settings, the request access rules, the response rules, and the threat intelligence configuration. Start by creating the policy with protection rules configured in detection mode, not blocking mode. This lets you see what would be blocked before you commit to enforcement.

resource "oci_waas_waas_policy" "production_waf" {
compartment_id = var.compartment_id
display_name = "production-waf-policy"
domain = var.application_domain
origins {
label = "primary-load-balancer"
uri = "https://${oci_load_balancer.app_lb.ip_address_details[0].ip_address}"
https_port = 443
http_port = 80
custom_headers {
name = "X-Forwarded-By"
value = "OCI-WAF"
}
}
policy_config {
certificate_id = var.tls_certificate_ocid
is_https_enabled = true
is_https_forced = true
is_origin_compression_enabled = true
is_response_buffering_enabled = false
cipher_group = "DEFAULT"
tls_protocols = ["TLS_V1_2", "TLS_V1_3"]
load_balancing_method {
method = "ROUND_ROBIN"
}
}
waf_config {
origin = "primary-load-balancer"
access_rules {
name = "block-non-standard-methods"
action = "BLOCK"
criteria {
condition = "HTTP_METHOD_IS_NOT"
value = "GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS"
}
block_action = "SET_RESPONSE_CODE"
block_response_code = 405
block_error_page_message = "Method not allowed"
block_error_page_code = "405"
block_error_page_description = "The HTTP method used is not permitted."
bypass_challenges = []
}
access_rules {
name = "block-suspicious-user-agents"
action = "BLOCK"
criteria {
condition = "HTTP_HEADER_CONTAINS"
value = "sqlmap"
is_case_sensitive = false
}
block_action = "SET_RESPONSE_CODE"
block_response_code = 403
}
access_rules {
name = "allow-health-check-bypass"
action = "ALLOW"
criteria {
condition = "URL_IS"
value = "/health"
}
bypass_challenges = ["JS_CHALLENGE", "CAPTCHA"]
}
protection_settings {
block_action = "SET_RESPONSE_CODE"
block_response_code = 403
block_error_page_message = "Access denied. Your request has been blocked by the web application firewall."
block_error_page_code = "403"
block_error_page_description = "If you believe this is an error, contact support."
is_response_inspected = false
max_argument_count = 255
max_name_length_per_argument = 400
max_response_size_in_ki_b = 1024
max_total_name_length_of_arguments = 64000
media_types = ["application/json", "application/xml", "text/html", "text/plain"]
recommendations_period_in_days = 10
}
}
defined_tags = {
"Operations.Environment" = "production"
"Operations.ManagedBy" = "terraform"
}
}

Step 3: Protection Rules Configuration

OCI WAF ships with hundreds of built-in protection rules based on the OWASP Core Rule Set. Each rule has an action: OFF, DETECT, or BLOCK. The default for new policies is DETECT, which logs violations without blocking. This is intentional.

Configure protection rules by category. Start with the high-confidence, low-false-positive rules in BLOCK mode and leave the noisier signature categories in DETECT mode until you have reviewed the traffic.

resource "oci_waas_protection_rules" "production_rules" {
waas_policy_id = oci_waas_waas_policy.production_waf.id
# SQL Injection - high confidence rules to BLOCK immediately
protection_rules {
key = "941100" # XSS Attack Detected via libinjection
action = "BLOCK"
}
protection_rules {
key = "941110" # XSS Filter - Category 1: Script Tag Vectors
action = "BLOCK"
}
protection_rules {
key = "942100" # SQL Injection Attack Detected via libinjection
action = "BLOCK"
}
protection_rules {
key = "942200" # Detects MySQL comment/space-obfuscated injections
action = "BLOCK"
}
protection_rules {
key = "942270" # Looking for basic SQL injection
action = "BLOCK"
}
protection_rules {
key = "942360" # Detects concatenated basic SQL injection
action = "BLOCK"
}
# Remote File Inclusion - BLOCK
protection_rules {
key = "950120" # Remote File Inclusion (RFI) Attack
action = "BLOCK"
}
# HTTP Protocol Violations - DETECT first, promote to BLOCK after tuning
protection_rules {
key = "920100" # Invalid HTTP Request Line
action = "DETECT"
}
protection_rules {
key = "920230" # Multiple URL Encoding Detected
action = "DETECT"
}
# Scanner Detection
protection_rules {
key = "913100" # Found User-Agent associated with security scanner
action = "DETECT"
}
protection_rules {
key = "913110" # Found User-Agent associated with SQL injection tool
action = "BLOCK"
}
# Local File Inclusion - BLOCK
protection_rules {
key = "930100" # Path Traversal Attack (/../)
action = "BLOCK"
}
protection_rules {
key = "930110" # Path Traversal Attack (/../)
action = "BLOCK"
}
# OS Command Injection - BLOCK
protection_rules {
key = "932100" # Remote Command Execution: Unix Command Injection
action = "BLOCK"
}
protection_rules {
key = "932105" # Remote Command Execution: Unix Command Injection
action = "BLOCK"
}
protection_rules {
key = "932110" # Remote Command Execution: Windows Command Injection
action = "BLOCK"
}
}

Step 4: Custom Protection Rules

Built-in signatures handle known attack patterns. Custom rules handle application-specific threats that signatures cannot know about: your specific API parameter names, your authentication endpoints, your internal IP ranges, and application logic that is unique to your system.

resource "oci_waas_custom_protection_rule" "block_admin_from_internet" {
compartment_id = var.compartment_id
display_name = "block-admin-paths-from-public"
description = "Block access to /admin/* paths from non-corporate IP ranges"
template = <<-EOT
SecRule REQUEST_URI "@beginsWith /admin" \
"id:9001001,\
phase:1,\
deny,\
status:403,\
log,\
msg:'Admin path access blocked from non-corporate IP',\
tag:'application-specific',\
chain"
SecRule REMOTE_ADDR "!@ipMatch ${var.corporate_ip_range}"
EOT
}
resource "oci_waas_custom_protection_rule" "block_api_key_in_url" {
compartment_id = var.compartment_id
display_name = "block-api-key-in-url"
description = "Detect and block requests that include API keys in URL query strings"
template = <<-EOT
SecRule QUERY_STRING "@rx (?i)(api_key|apikey|api-key|access_token|auth_token)=[a-zA-Z0-9_-]{20,}" \
"id:9001002,\
phase:1,\
deny,\
status:400,\
log,\
msg:'API key exposed in URL query string - rejected for security',\
tag:'api-security'"
EOT
}
resource "oci_waas_custom_protection_rule" "block_large_payloads_on_auth" {
compartment_id = var.compartment_id
display_name = "block-oversized-auth-payloads"
description = "Block abnormally large request bodies on authentication endpoints"
template = <<-EOT
SecRule REQUEST_URI "@beginsWith /api/v1/auth" \
"id:9001003,\
phase:1,\
chain,\
log,\
msg:'Oversized payload on auth endpoint'"
SecRule REQUEST_HEADERS:Content-Length "@gt 8192" \
"deny,\
status:413"
EOT
}

Step 5: Rate Limiting

Rate limiting sits at a different layer from protection rules. It counts requests from a given source over a time window and blocks or challenges sources that exceed the threshold. This handles volumetric attacks, credential stuffing, scraping, and API abuse that may not trigger any individual signature rule.

resource "oci_waas_waas_policy" "production_waf" {
waf_config {
js_challenge {
is_enabled = true
action = "DETECT"
failure_threshold = 10
action_expiration_in_seconds = 60
set_http_header {
name = "X-WAF-JS-Challenge"
value = "1"
}
}
captcha {
failure_threshold = 10
action_expiration_in_seconds = 60
session_expiration_in_seconds = 3600
title = "Captcha Challenge"
header_text = "We need to verify you are human."
footer_text = "Complete the challenge to continue."
}
address_rate_limiting {
is_enabled = true
allowed_rate_per_address = 200
max_delayed_count_per_address = 20
block_response_code = 429
}
device_fingerprint_challenge {
is_enabled = true
action = "DETECT"
failure_threshold = 10
action_expiration_in_seconds = 60
failure_threshold_expiration_in_seconds = 60
max_address_count = 20
max_address_count_expiration_in_seconds = 60
}
}
}

The address_rate_limiting block limits each IP to 200 requests per second with a burst allowance of 20 additional delayed requests before the 429 response fires. Tune this based on your application’s normal traffic profile. The device_fingerprint_challenge detects bots that rotate IP addresses by fingerprinting browser characteristics.

Step 6: Threat Intelligence Integration

resource "oci_waas_waas_policy" "production_waf" {
waf_config {
threat_feeds {
key = "OAM_ORACLE_IP_REPUTATION"
action = "BLOCK"
}
threat_feeds {
key = "OAM_TOR_EXIT_NODES"
action = "DETECT"
}
threat_feeds {
key = "OAM_ANONYMOUS_PROXIES"
action = "DETECT"
}
}
}

Set TOR exit nodes and anonymous proxies to DETECT rather than BLOCK initially. Legitimate users may route through VPNs that share IP space with anonymous proxies, and blocking them outright can impact real users. Review the detection logs over one to two weeks and move to BLOCK only if the flagged traffic is consistently malicious.

Step 7: Enable WAF Logging

resource "oci_logging_log_group" "waf_log_group" {
compartment_id = var.compartment_id
display_name = "waf-production-logs"
description = "Log group for OCI WAF access and protection events"
}
resource "oci_logging_log" "waf_access_log" {
display_name = "waf-access-log"
log_group_id = oci_logging_log_group.waf_log_group.id
log_type = "SERVICE"
configuration {
source {
category = "access"
resource = oci_waas_waas_policy.production_waf.id
service = "waas"
source_type = "OCISERVICE"
}
compartment_id = var.compartment_id
}
retention_duration = 90
is_enabled = true
}
resource "oci_logging_log" "waf_protection_log" {
display_name = "waf-protection-log"
log_group_id = oci_logging_log_group.waf_log_group.id
log_type = "SERVICE"
configuration {
source {
category = "protection"
resource = oci_waas_waas_policy.production_waf.id
service = "waas"
source_type = "OCISERVICE"
}
compartment_id = var.compartment_id
}
retention_duration = 90
is_enabled = true
}

Step 8: Attach WAF Policy to the Load Balancer

resource "oci_load_balancer" "app_lb" {
compartment_id = var.compartment_id
display_name = "production-app-lb"
shape = "flexible"
subnet_ids = [var.public_subnet_id]
shape_details {
minimum_bandwidth_in_mbps = 10
maximum_bandwidth_in_mbps = 400
}
}
resource "oci_load_balancer_listener" "https_listener" {
load_balancer_id = oci_load_balancer.app_lb.id
name = "https-443"
default_backend_set_name = oci_load_balancer_backend_set.app_backend.name
port = 443
protocol = "HTTP"
ssl_configuration {
certificate_name = oci_load_balancer_certificate.app_cert.certificate_name
protocols = ["TLSv1.2", "TLSv1.3"]
}
}

After the WAF policy is created and active, update your DNS to point the application domain to the WAF CNAME endpoint rather than directly to the load balancer IP. Traffic flows through the WAF before reaching the load balancer.

# Get the WAF CNAME endpoint
terraform output waf_cname_zone
# Returns something like: xxxxxxxxxxxxxxxx.waas.oci.oraclecloud.net
# Update your DNS CNAME record:
# api.example.com CNAME xxxxxxxxxxxxxxxx.waas.oci.oraclecloud.net

Step 9: Tuning Based on Detection Logs

After running in detection mode for one to two weeks, query the WAF protection logs to find false positives before switching to block mode.

oci logging-search search-logs \
--search-query 'search "ocid1.compartment.oc1..yourcompartmentocid/waas/waf-protection-log" | where data.action = '"'"'DETECT'"'"' | summarize count() by data.protectionRuleKey, data.requestUrl | sort -count()' \
--time-start "$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ)" \
--time-end "$(date -u +%Y-%m-%dT%H:%M:%SZ)"

For false positives caused by specific URL paths, use exclusion rules scoped to that path:

resource "oci_waas_protection_rules" "production_rules" {
protection_rules {
key = "941100"
action = "BLOCK"
exclusions {
target = "REQUEST_COOKIES"
exclusions = ["session-token", "csrf-token"]
}
exclusions {
target = "REQUEST_URL"
exclusions = ["/api/v1/content/editor"]
}
}
}

Step 10: Validating the WAF Configuration

Test SQL injection detection before promoting to block mode:

# This should be detected/blocked
curl -v "https://api.example.com/api/v1/products?id=1' OR '1'='1" \
-H "User-Agent: Mozilla/5.0 (legitimate browser)"

Test path traversal detection:

curl -v "https://api.example.com/api/v1/files?path=../../etc/passwd" \
-H "User-Agent: Mozilla/5.0 (legitimate browser)"

Test that legitimate traffic passes without issue:

curl -v "https://api.example.com/api/v1/products" \
-H "Authorization: Bearer ${VALID_TOKEN}" \
-H "Content-Type: application/json"
# Expected: 200 with normal response

Set a Monitoring alarm to alert on a sudden spike in BLOCK events after promoting rules:

resource "oci_monitoring_alarm" "waf_block_spike" {
compartment_id = var.compartment_id
display_name = "waf-block-rate-spike"
is_enabled = true
metric_compartment_id = var.compartment_id
namespace = "oci_waas"
query = "BlockedRequests[5m]{policyId = '${oci_waas_waas_policy.production_waf.id}'}.sum() > 500"
severity = "WARNING"
pending_duration = "PT5M"
destinations = [var.ops_notification_topic_id]
body = "WAF block rate has exceeded 500 requests in 5 minutes. Verify no legitimate traffic is being blocked after recent rule changes."
}

Where WAF Fits in the OCI Security Stack

OCI WAF protects against application-layer attacks on HTTP and HTTPS traffic. It does not replace OCI Network Firewall, which handles network-layer inspection and IDPS across all protocols. It does not replace Cloud Guard, which monitors configuration posture and detects misconfigurations across your tenancy. And it does not replace OCI Vault, which protects credentials and key material.

A complete security posture uses all of them together. Cloud Guard detects configuration drift and policy violations. OCI Network Firewall handles east-west inspection and egress filtering. OCI WAF handles application-layer threats on internet-facing endpoints. OCI Vault ensures key material never exists in plaintext outside of the HSM. Each layer catches a class of attack that the others cannot, and together they give you defense in depth that any single service on its own cannot provide.

Regards,
Osama

#OCI #OracleCloud #WAF #WebApplicationFirewall #CloudSecurity #OWASP #OracleCloudInfrastructure #Terraform #InfrastructureAsCode #IaC #AppSec #DevSecOps #SQLInjection #XSSProtection #RateLimiting #BotManagement #CloudArchitecture #PlatformEngineering #ThreatIntelligence #TechBlog

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.