GCP Cloud Armor DDoS Protection: Adaptive Protection and Real Attack Mitigation

Implementing Cloud Armor for DDoS protection with adaptive protection, custom WAF rules, and real-world attack mitigation data from production incidents.

#gcp#cloud-armor#security#ddos
Cover image for the article: GCP Cloud Armor DDoS Protection: Adaptive Protection and Real Attack Mitigation

In the past 12 months, our platform absorbed 47 distinct DDoS attacks ranging from simple volumetric floods to sophisticated application-layer attacks. Cloud Armor handled most of them automatically — but the ones it didn't taught us more about defense architecture than any documentation could. Here's what production DDoS defense actually looks like on GCP.

The Problem: Growing Attack Surface

Our SaaS platform serves 200K+ daily active users through a global HTTP(S) load balancer. As we grew, we became a target. The first attack came 6 weeks after reaching $1M ARR — a 15Gbps volumetric flood that took us offline for 23 minutes because we had no protection configured.

After that incident, we implemented a layered Cloud Armor strategy that has maintained 99.99% availability through 47 subsequent attacks.

Cloud Armor Architecture

Cloud Armor operates at the edge of Google's network, filtering traffic before it reaches your backend:

Cloud Armor Defense Layers

Three protection layers:

  1. Network DDoS protection (always on): Google's network absorbs volumetric attacks automatically. No configuration needed.
  2. Cloud Armor security policies: Custom rules for application-layer filtering (WAF).
  3. Adaptive Protection: ML-based anomaly detection that creates rules automatically during attacks.

Adaptive Protection Configuration

Adaptive Protection is Cloud Armor's ML-based system that learns your normal traffic patterns and alerts on anomalies:

# Enable Adaptive Protection on your security policy
gcloud compute security-policies update web-policy \
  --enable-layer7-ddos-defense \
  --layer7-ddos-defense-rule-visibility=STANDARD \
  --global

# Configure alert thresholds
gcloud compute security-policies update web-policy \
  --layer7-ddos-defense-threshold-config='{
    "autoDeployLoadThreshold": 0.8,
    "autoDeployConfidenceThreshold": 0.7,
    "autoDeployImpactedBaselineThreshold": 0.01,
    "autoDeployExpirationSec": 7200
  }' \
  --global

When Adaptive Protection detects an attack, it:

  1. Identifies the attack signature (source IPs, request patterns, headers)
  2. Generates a proposed rule that would block the attack traffic
  3. Optionally auto-deploys the rule (if configured) or alerts for manual review

Security Policy Rules

Our production security policy has evolved through 47 attacks into this configuration:

# Create the security policy
gcloud compute security-policies create web-policy \
  --description="Production web application protection" \
  --global

# Rule 1: Block known bad actors (highest priority)
gcloud compute security-policies rules create 1000 \
  --security-policy=web-policy \
  --src-ip-ranges="198.51.100.0/24,203.0.113.0/24" \
  --action=deny-403 \
  --description="Known malicious IP ranges" \
  --global

# Rule 2: Rate limiting per IP
gcloud compute security-policies rules create 2000 \
  --security-policy=web-policy \
  --expression="true" \
  --action=throttle \
  --rate-limit-threshold-count=100 \
  --rate-limit-threshold-interval-sec=60 \
  --conform-action=allow \
  --exceed-action=deny-429 \
  --enforce-on-key=IP \
  --description="Global rate limit: 100 req/min per IP" \
  --global

# Rule 3: Block requests without valid User-Agent
gcloud compute security-policies rules create 3000 \
  --security-policy=web-policy \
  --expression="!has(request.headers['user-agent']) || request.headers['user-agent'] == ''" \
  --action=deny-403 \
  --description="Block empty User-Agent" \
  --global

# Rule 4: Geographic restrictions (block high-attack-origin countries)
gcloud compute security-policies rules create 4000 \
  --security-policy=web-policy \
  --expression="origin.region_code == 'XX' || origin.region_code == 'YY'" \
  --action=deny-403 \
  --description="Geo-block non-served regions" \
  --global

# Rule 5: WAF rules - SQL injection and XSS
gcloud compute security-policies rules create 5000 \
  --security-policy=web-policy \
  --expression="evaluatePreconfiguredExpr('sqli-v33-stable') || evaluatePreconfiguredExpr('xss-v33-stable')" \
  --action=deny-403 \
  --description="OWASP SQLi and XSS protection" \
  --global

# Default rule: Allow all other traffic
gcloud compute security-policies rules update 2147483647 \
  --security-policy=web-policy \
  --action=allow \
  --global

Real Attack Data

Here's a summary of the 5 most significant attacks we absorbed:

Attack #TypePeak VolumeDurationMitigation TimeImpact
1Volumetric (UDP flood)15 Gbps4 hours23 min (no protection)Full outage
12HTTP flood2.8M RPS2 hours0 sec (auto)Zero
23Slowloris50K connections6 hours45 sec (adaptive)2s latency spike
31API abuse180K RPS12 hours3 min (manual rule)Zero after rule
47L7 + credential stuffing850K RPS8 hours12 sec (adaptive auto-deploy)Zero

The progression from Attack #1 (full outage) to Attack #47 (zero impact, auto-mitigated) represents our defense maturation.

Custom Rule Language (CEL Expressions)

Cloud Armor uses Common Expression Language (CEL) for rule conditions. Complex patterns we use in production:

# Block requests to sensitive endpoints from non-authenticated sources
# (defense in depth — application auth should catch this too)
sensitive_paths = """
  request.path.matches('/api/admin/.*') && 
  !has(request.headers['x-internal-token'])
"""

# Rate limit by authenticated user (not just IP)
# Prevents credential sharing for DDoS amplification
per_user_rate_limit = """
  has(request.headers['authorization']) &&
  request.headers['authorization'].startsWith('Bearer ')
"""

# Block known bot signatures targeting login endpoints
bot_signature_block = """
  request.path == '/api/auth/login' && (
    request.headers['user-agent'].contains('python-requests') ||
    request.headers['user-agent'].contains('Go-http-client') ||
    request.headers['user-agent'].contains('curl/')
  )
"""

Monitoring and Alerting

# Query Cloud Armor logs for blocked requests
gcloud logging read '
  resource.type="http_load_balancer" AND
  jsonPayload.enforcedSecurityPolicy.outcome="DENY"
' --limit 1000 --format json | \
  jq 'group_by(.jsonPayload.enforcedSecurityPolicy.name) | 
      map({rule: .[0].jsonPayload.enforcedSecurityPolicy.name, count: length})'

We built alerting around three signals:

# Alert: Traffic spike detected (potential attack)
- alert: CloudArmorTrafficSpike
  expr: |
    rate(loadbalancing_googleapis_com:https_request_count[5m]) > 
    3 * avg_over_time(loadbalancing_googleapis_com:https_request_count[24h])
  for: 2m
  labels:
    severity: warning
  annotations:
    summary: "Traffic 3x above 24h average"

# Alert: High deny rate (active attack being blocked)  
- alert: CloudArmorHighDenyRate
  expr: |
    sum(rate(networksecurity_googleapis_com:denied_requests_count[5m])) /
    sum(rate(loadbalancing_googleapis_com:https_request_count[5m])) > 0.3
  for: 5m
  labels:
    severity: critical
  annotations:
    summary: "30%+ of requests being denied - active attack"

# Alert: Adaptive Protection triggered
- alert: CloudArmorAdaptiveTriggered
  expr: |
    increase(networksecurity_googleapis_com:adaptive_protection_alert_count[5m]) > 0
  labels:
    severity: critical
  annotations:
    summary: "Adaptive Protection detected anomaly"

Cost of Protection

ComponentMonthly Cost
Security policy (per policy)$5
Rules (per rule)$1
Requests evaluated (per 1M)$0.75
Adaptive Protection$25/policy
WAF preconfigured rules$5/rule
Total (our configuration)~$380/month

Cloud Armor Cost vs Attack Volume

At $380/month for protection against attacks that previously caused $12,000+ in downtime costs, the ROI is obvious.

Lessons Learned

Lesson 1: Network-layer DDoS protection is free and always on. You don't need Cloud Armor for volumetric floods — Google's network handles those automatically. Cloud Armor's value is application-layer (L7) protection.

Lesson 2: Adaptive Protection needs 2-3 weeks of baseline data. Don't enable it and expect immediate protection. It needs to learn your traffic patterns before it can detect anomalies.

Lesson 3: Rate limiting by IP is necessary but insufficient. Sophisticated attackers use thousands of IPs. Combine IP-based rate limiting with behavioral analysis (request patterns, header fingerprinting).

Lesson 4: Auto-deploy Adaptive Protection rules in production. We initially required manual approval. Attack #23 (Slowloris) caused a 2-second latency spike during the 45 seconds it took us to approve the rule. Auto-deploy eliminated this for Attack #47.

Lesson 5: Test your rules before production. Cloud Armor's preview mode lets you see what traffic a rule would block without actually blocking it.

Key Takeaways

  1. Layer your defense: Network DDoS (free) + WAF rules (explicit) + Adaptive Protection (ML-based) + rate limiting (per-IP and per-user).
  2. Enable Adaptive Protection auto-deploy once you've validated it doesn't produce false positives in your traffic patterns.
  3. $380/month is cheap insurance. One prevented outage pays for years of Cloud Armor.
  4. Log everything. Attack forensics are impossible without complete request logs. Enable verbose logging during active attacks.
  5. Practice incident response. Our first attack caused a 23-minute outage. Our most recent attack caused zero impact. The difference is preparation, not technology.

The attacks will come. The question is whether you've built the defense before or after the first outage.

Comments

    No comments yet. Be the first to share your thoughts.