VPN & BGP & Connectivity

Site-to-site VPN with IPsec, BGP path selection and route manipulation, dedicated connectivity with Direct Connect and Cloud Interconnect, and hybrid network security patterns.

Site-to-Site VPN

IPsec Fundamentals

IPsec Protocol Suite

ESP (Encapsulating Security Payload): Provides encryption, authentication, and integrity. The standard choice — use ESP in tunnel mode for site-to-site VPN.

AH (Authentication Header): Authentication and integrity only, no encryption. Rarely used in modern deployments; incompatible with NAT traversal.

Tunnel mode: Entire original IP packet (header + payload) is encrypted and encapsulated. Used for site-to-site VPN — the VPN gateway encrypts the original packet.

Transport mode: Only payload encrypted, original IP header preserved. Used for host-to-host IPsec (e.g. server-to-server inside a network).

IKEv1 vs IKEv2

FeatureIKEv1IKEv2
RFCRFC 2409 (1998)RFC 7296 (2014)
Handshake messages9 messages (Main Mode) or 6 (Aggressive Mode)4 messages (always)
NAT traversalExtension (RFC 3947)Built-in
EAP authNot supported nativelyNative (MFA integration)
MOBIKENoYes (mobile client IP changes)
Dead Peer DetectionExtensionBuilt-in liveness check
RecommendationLegacy — use only when forcedAlways prefer IKEv2

IPsec Phase 1 & Phase 2

# Phase 1 (IKE SA / ISAKMP) — establishes secure control channel
# Negotiates: encryption algorithm, hash, DH group, lifetime, auth method (PSK or certs)
# Typical config:
#   Encryption: AES-256
#   Hash:       SHA-256 or SHA-384
#   DH Group:   14 (2048-bit) or 19 (256-bit ECDH) — prefer 19+
#   Lifetime:   28800 seconds (8 hours)
#   Auth:       Pre-shared key (PSK) or RSA/ECDSA certificates

# Phase 2 (IPsec SA) — establishes data tunnel using Phase 1 as protection
# Negotiates: ESP encryption/hash, PFS group, lifetime, traffic selectors (interesting traffic)
# Typical config:
#   Protocol:   ESP
#   Encryption: AES-256-GCM (AEAD — no separate hash needed)
#   PFS:        Group 14 or 19 (Perfect Forward Secrecy — new DH exchange per SA)
#   Lifetime:   3600 seconds (1 hour)
#   Traffic:    10.0.0.0/16 <→ 192.168.0.0/16

# Quick troubleshooting
# Show IKE SAs (Phase 1)
show crypto isakmp sa              # Cisco IOS
ip xfrm state                     # Linux (StrongSwan/Libreswan)
strongswan statusall               # StrongSwan

# Show IPsec SAs (Phase 2)
show crypto ipsec sa               # Cisco
ip xfrm policy                     # Linux

AWS Site-to-Site VPN

# AWS Site-to-Site VPN components:
# Virtual Private Gateway (VGW) — AWS-side VPN endpoint, attached to VPC
# Customer Gateway (CGW) — represents your on-prem device
# VPN Connection — 2 tunnels (different public IPs/AZs) per connection for HA

# Terraform — AWS VPN with BGP
resource "aws_customer_gateway" "onprem" {
  bgp_asn    = 65000          # your on-prem ASN
  ip_address = "203.0.113.1"  # on-prem router public IP
  type       = "ipsec.1"
  tags = { Name = "onprem-cgw" }
}

resource "aws_vpn_gateway" "vgw" {
  vpc_id          = aws_vpc.prod.id
  amazon_side_asn = 64512     # AWS private ASN
  tags = { Name = "prod-vgw" }
}

resource "aws_vpn_connection" "main" {
  vpn_gateway_id      = aws_vpn_gateway.vgw.id
  customer_gateway_id = aws_customer_gateway.onprem.id
  type                = "ipsec.1"
  static_routes_only  = false   # false = use BGP

  # Tunnel 1 configuration
  tunnel1_inside_cidr   = "169.254.10.0/30"
  tunnel1_preshared_key = var.vpn_psk_1

  # Phase 1
  tunnel1_ike_versions             = ["ikev2"]
  tunnel1_phase1_encryption_algorithms = ["AES256"]
  tunnel1_phase1_integrity_algorithms  = ["SHA2-256"]
  tunnel1_phase1_dh_group_numbers      = [14, 19]
  tunnel1_phase1_lifetime_seconds      = 28800

  # Phase 2
  tunnel1_phase2_encryption_algorithms = ["AES256-GCM-16"]
  tunnel1_phase2_integrity_algorithms  = ["AEAD-AES-256-GCM-16"]
  tunnel1_phase2_dh_group_numbers      = [14, 19]
  tunnel1_phase2_lifetime_seconds      = 3600

  tags = { Name = "prod-vpn-to-onprem" }
}

# Enable route propagation from VGW to route tables
resource "aws_vpn_gateway_route_propagation" "private" {
  route_table_id = aws_route_table.private_app[0].id
  vpn_gateway_id = aws_vpn_gateway.vgw.id
}

GCP Cloud VPN

Classic VPN vs HA VPN

Classic VPN: Single interface, single tunnel, 99.9% SLA. Not recommended for production — single point of failure. Does not support dynamic routing with BGP if using policy-based routes.

HA VPN: Two interfaces, two tunnels to two on-prem devices (or two interfaces of same device), 99.99% SLA. Requires BGP. The recommended production option.

# HA VPN with BGP — full redundancy
gcloud compute vpn-gateways create prod-ha-vpn \
  --network=prod-vpc \
  --region=us-central1

# On-prem peer gateway (represents your device — 2 interfaces for redundancy)
gcloud compute external-vpn-gateways create onprem-gw \
  --interfaces 0=203.0.113.1,1=203.0.113.2

# Cloud Router for BGP
gcloud compute routers create prod-vpn-router \
  --network=prod-vpc \
  --region=us-central1 \
  --asn=65001    # GCP side ASN

# VPN Tunnels (2 tunnels for HA — one to each peer interface)
gcloud compute vpn-tunnels create tunnel-1 \
  --region=us-central1 \
  --vpn-gateway=prod-ha-vpn \
  --vpn-gateway-interface=0 \
  --peer-external-gateway=onprem-gw \
  --peer-external-gateway-interface=0 \
  --shared-secret=$PSK_1 \
  --router=prod-vpn-router \
  --ike-version=2

gcloud compute vpn-tunnels create tunnel-2 \
  --region=us-central1 \
  --vpn-gateway=prod-ha-vpn \
  --vpn-gateway-interface=1 \
  --peer-external-gateway=onprem-gw \
  --peer-external-gateway-interface=1 \
  --shared-secret=$PSK_2 \
  --router=prod-vpn-router \
  --ike-version=2

# BGP sessions on Cloud Router (one per tunnel)
gcloud compute routers add-bgp-peer prod-vpn-router \
  --region=us-central1 \
  --peer-name=onprem-peer-1 \
  --interface=if-tunnel-1 \
  --peer-ip-address=169.254.10.1 \
  --peer-asn=65000 \
  --advertised-route-priority=100

gcloud compute routers add-bgp-peer prod-vpn-router \
  --region=us-central1 \
  --peer-name=onprem-peer-2 \
  --interface=if-tunnel-2 \
  --peer-ip-address=169.254.10.5 \
  --peer-asn=65000 \
  --advertised-route-priority=100

# Check BGP status
gcloud compute routers get-status prod-vpn-router --region=us-central1

OpenVPN Configuration

# OpenVPN server config (/etc/openvpn/server.conf)
port 1194
proto udp
dev tun
ca   /etc/openvpn/pki/ca.crt
cert /etc/openvpn/pki/issued/server.crt
key  /etc/openvpn/pki/private/server.key
dh   /etc/openvpn/pki/dh.pem
tls-auth /etc/openvpn/ta.key 0

server 10.8.0.0 255.255.255.0         # VPN client IP pool
push "route 10.0.0.0 255.255.0.0"    # push VPC route to clients
push "dhcp-option DNS 10.0.0.2"      # push VPC DNS
keepalive 10 120
cipher AES-256-GCM
auth SHA256
tls-version-min 1.2
tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384

# MFA with Google Authenticator (PAM plugin)
plugin /usr/lib/openvpn/openvpn-plugin-auth-pam.so openvpn
verify-client-cert none
username-as-common-name
auth-user-pass-verify /etc/openvpn/auth-script.sh via-env

user  nobody
group nogroup
persist-key
persist-tun
status  /var/log/openvpn/status.log
verb 3

# Client config (.ovpn)
client
dev tun
proto udp
remote vpn.example.com 1194
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
cipher AES-256-GCM
auth SHA256
auth-user-pass        # prompt for username + OTP
verb 3
<ca>
-----BEGIN CERTIFICATE-----
... CA certificate ...
-----END CERTIFICATE-----
</ca>

WireGuard

# WireGuard: modern, minimal VPN (Linux kernel 5.6+)
# Uses: Curve25519 (key exchange), ChaCha20-Poly1305 (encryption), BLAKE2s (hash)
# ~4,000 lines of code vs OpenVPN's ~100,000 — dramatically reduced attack surface

# Generate key pair
wg genkey | tee server_private.key | wg pubkey > server_public.key
wg genkey | tee client_private.key | wg pubkey > client_public.key

# Server config (/etc/wireguard/wg0.conf)
[Interface]
Address    = 10.200.0.1/24
ListenPort = 51820
PrivateKey = <server_private_key>
PostUp     = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown   = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

[Peer]
# Remote office
PublicKey  = <client_public_key>
AllowedIPs = 10.200.0.2/32, 192.168.1.0/24   # client IP + remote subnet

# Client config
[Interface]
Address    = 10.200.0.2/24
PrivateKey = <client_private_key>
DNS        = 10.0.0.2

[Peer]
PublicKey  = <server_public_key>
Endpoint   = vpn.example.com:51820
AllowedIPs = 10.0.0.0/16, 10.200.0.1/32   # route VPC + server through tunnel
PersistentKeepalive = 25

# Manage WireGuard
wg-quick up wg0
wg show               # status, peer handshakes, transfer stats
systemctl enable wg-quick@wg0

# Performance comparison (1 Gbps test, AWS c5n.2xlarge):
# WireGuard:  ~950 Mbps  CPU ~15%
# OpenVPN:    ~200 Mbps  CPU ~100%
# IPsec/StrongSwan: ~700 Mbps CPU ~40%

BGP — Border Gateway Protocol

BGP Fundamentals

iBGP vs eBGP

eBGP (external BGP): Between routers in different Autonomous Systems (ASNs). Direct peering (TTL=1 by default). Used between cloud and on-premises, between ISPs.

iBGP (internal BGP): Between routers in the same AS. Full mesh or route reflectors required. Used internally — e.g. between Cloud Routers in GCP, between Direct Connect gateways.

# ASN ranges
# Public ASNs (IANA assigned): 1–64495
# Private ASNs (RFC 6996): 64512–65534 (2-byte), 4200000000–4294967294 (4-byte)
# AWS uses:          64512 by default for VGW/TGW (configurable)
# GCP Cloud Router:  64512–65534 (your choice)
# Your on-prem:      any private ASN (e.g. 65000)

# Key BGP attributes (in path selection order):
# WEIGHT       — Cisco proprietary, local to router, higher preferred
# LOCAL_PREF   — iBGP, higher preferred (inbound traffic control)
# ORIGINATE    — locally originated routes preferred over learned
# AS_PATH      — shorter preferred (hop count); manipulated for traffic engineering
# ORIGIN       — IGP (i) > EGP (e) > Incomplete (?)
# MED          — Multi-Exit Discriminator; lower preferred (outbound traffic control)
# eBGP over iBGP — routes learned via eBGP preferred
# IGP metric   — lower cost to next-hop preferred
# Router ID    — lowest router ID (tiebreaker)

BGP Path Selection Step-by-Step

# BGP best path selection algorithm (Cisco IOS order):
# 1.  Highest WEIGHT (Cisco proprietary — local to router)
# 2.  Highest LOCAL_PREF (set within iBGP domain — controls inbound preference)
# 3.  Locally originated routes (network statement or redistribute)
# 4.  Shortest AS_PATH length (fewer hops = preferred)
# 5.  Lowest ORIGIN code (IGP=0 > EGP=1 > Incomplete=2)
# 6.  Lowest MED (when multiple paths from same neighboring AS)
# 7.  eBGP over iBGP learned routes
# 8.  Lowest IGP metric to BGP next-hop
# 9.  Oldest eBGP route (stability preference)
# 10. Lowest BGP Router ID (ultimate tiebreaker)

# Memory aid: "We Love Original Short Memories Even If It Hurts"
# W - Weight
# L - Local Pref
# O - Originate (local)
# S - Shortest AS path
# M - Minimum Origin code
# E - External (eBGP over iBGP)
# I - Interior (IGP metric)
# I - ID (Router ID)

BGP Route Manipulation

# AS_PATH Prepending — make a path look longer (less preferred)
# Use case: route traffic away from a specific link (e.g. backup ISP)

route-map PREPEND_BACKUP permit 10
  set as-path prepend 65000 65000 65000   # prepend your own ASN 3 times
!
neighbor 203.0.113.2 route-map PREPEND_BACKUP out

# LOCAL_PREF — influence inbound traffic (which link traffic enters your AS)
# Set higher LOCAL_PREF for preferred path
route-map PREFER_PRIMARY permit 10
  match ip address prefix-list ALL
  set local-preference 200          # default is 100; higher = preferred
!
neighbor 10.1.1.1 route-map PREFER_PRIMARY in

# MED — influence outbound traffic from a neighboring AS
# Lower MED = preferred (neighboring AS will prefer this path to reach you)
route-map SET_MED permit 10
  set metric 100    # lower MED for primary link
!
neighbor 203.0.113.1 route-map SET_MED out

# BGP Communities — tag routes for policy application downstream
# Well-known communities:
# NO_EXPORT   (65535:65281) — don't advertise outside AS
# NO_ADVERTISE(65535:65282) — don't advertise to any peer
# Custom: 65000:100 = high priority, 65000:200 = backup

route-map TAG_WITH_COMMUNITY permit 10
  set community 65000:100 additive

AWS BGP with Direct Connect & Transit Gateway

# AWS Direct Connect BGP:
# - AWS side ASN: 7224 (fixed for DX, or 64512 on VGW/TGW)
# - Customer ASN: any public or private
# - MD5 auth supported for BGP session security
# - BFD (Bidirectional Forwarding Detection) enabled by default

# Direct Connect BGP route control:
# Use LOCAL_PREF on your router to prefer DX over VPN backup
# Use BGP communities on what you advertise to control AWS routing:
#   7224:9100 — LOCAL_PREF 100 (standard)
#   7224:7100 — LOCAL_PREF 100 on all routers in that region
#   7224:8100 — LOCAL_PREF 100 on all AWS routers

# Transit Gateway route table BGP propagation
resource "aws_ec2_transit_gateway_route_table" "prod" {
  transit_gateway_id = aws_ec2_transit_gateway.main.id
  tags = { Name = "prod-tgw-rt" }
}

resource "aws_ec2_transit_gateway_route_table_propagation" "dx" {
  transit_gateway_attachment_id  = aws_dx_transit_virtual_interface.main.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.prod.id
}

# Static route on TGW (override BGP for specific prefix)
resource "aws_ec2_transit_gateway_route" "onprem_specific" {
  destination_cidr_block         = "192.168.100.0/24"
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.prod.id
  transit_gateway_attachment_id  = aws_dx_transit_virtual_interface.main.id
}

GCP Cloud Router & BGP

# Cloud Router: GCP's managed BGP speaker — used with HA VPN and Cloud Interconnect
# Advertises VPC subnet routes and custom prefixes via BGP

# Custom route advertisement (advertise additional CIDRs beyond subnets)
gcloud compute routers update prod-vpn-router \
  --region=us-central1 \
  --advertisement-mode=custom \
  --set-advertisement-groups=ALL_SUBNETS \
  --set-advertisement-ranges=10.0.0.0/16,10.100.0.0/16

# Update BGP peer with custom MED (to influence return path from on-prem)
gcloud compute routers update-bgp-peer prod-vpn-router \
  --region=us-central1 \
  --peer-name=onprem-peer-1 \
  --advertised-route-priority=100    # MED value sent to peer

# Check BGP session details
gcloud compute routers get-status prod-vpn-router \
  --region=us-central1 \
  --format='json(result.bgpPeerStatus)'

# Learned routes from BGP peer
gcloud compute routers get-status prod-vpn-router \
  --region=us-central1 \
  --format='json(result.bgpPeerStatus[].advertisedRoutes)'

BGP Security

BGP route hijacking is a real threat. In 2010, China Telecom hijacked ~15% of internet traffic for 18 minutes via BGP misconfiguration. In 2022, multiple BGP hijack incidents targeted crypto exchanges. RPKI and strict prefix filtering are essential.
# RPKI (Resource Public Key Infrastructure) — cryptographically validates route origins
# ROA (Route Origin Authorization): signed record saying "ASN X is authorized to originate prefix Y/len"

# Check RPKI validity of a prefix
curl -s "https://stat.ripe.net/data/rpki-validation/data.json?resource=AS65000&prefix=192.0.2.0/24"

# BGP prefix filtering — whitelist only expected prefixes from peers
# On Cisco IOS:
ip prefix-list ALLOWED_FROM_ONPREM seq 10 permit 192.168.0.0/16 le 24
ip prefix-list ALLOWED_FROM_ONPREM seq 20 permit 10.200.0.0/16 le 24
!
route-map INBOUND_FILTER permit 10
  match ip address prefix-list ALLOWED_FROM_ONPREM
!
neighbor 203.0.113.1 route-map INBOUND_FILTER in
neighbor 203.0.113.1 maximum-prefix 100 90  # alert at 90%, drop session at 100

# MD5 authentication for BGP session
neighbor 203.0.113.1 password 0 your-md5-secret

# AWS Direct Connect BGP MD5 auth (set in CGW config):
resource "aws_customer_gateway" "main" {
  bgp_asn    = 65000
  ip_address = "203.0.113.1"
  type       = "ipsec.1"
  # BGP auth key set in VPN connection or DX virtual interface
}

Dedicated Connectivity

AWS Direct Connect

Direct Connect Architecture

Dedicated connection: 1G, 10G, or 100G port directly at an AWS DX location. Your equipment or colocation provider connects here.

Hosted connection: Sub-1G to 10G via an AWS Partner. Faster to provision, more flexible bandwidth. No control over physical redundancy.

VIF types: Private VIF (connects to VGW/TGW — reaches private IPs), Public VIF (reaches AWS public services like S3, not VPC), Transit VIF (connects to Direct Connect Gateway → Transit Gateway).

# Direct Connect Gateway: allows one DX connection to reach VPCs in multiple regions
# (unlike VGW which is VPC/region specific)

resource "aws_dx_gateway" "main" {
  name            = "prod-dxgw"
  amazon_side_asn = 64512
}

resource "aws_dx_transit_virtual_interface" "prod" {
  connection_id    = var.dx_connection_id   # physical DX connection ID
  name             = "prod-transit-vif"
  vlan             = 100                    # 802.1Q VLAN tag
  address_family   = "ipv4"
  bgp_asn          = 65000
  amazon_address   = "169.254.100.1/30"    # AWS-side BGP link address
  customer_address = "169.254.100.2/30"    # customer-side BGP link address
  bgp_auth_key     = var.bgp_md5_key
  dx_gateway_id    = aws_dx_gateway.main.id
}

resource "aws_dx_gateway_association" "tgw" {
  dx_gateway_id         = aws_dx_gateway.main.id
  associated_gateway_id = aws_ec2_transit_gateway.main.id
  allowed_prefixes = [
    "10.0.0.0/8",
    "172.16.0.0/12",
  ]
}

# LAG (Link Aggregation Group) — bundle multiple DX ports for bandwidth + redundancy
resource "aws_dx_lag" "prod" {
  name                  = "prod-dx-lag"
  connections_bandwidth = "10Gbps"
  location              = "EqDC2"   # DX location code
  force_destroy         = false
}

GCP Cloud Interconnect

OptionSpeedSLAUse Case
Dedicated Interconnect10G or 100G per circuit99.99% (4 circuits)High bandwidth, colocation at Google facility
Partner Interconnect50 Mbps – 10 Gbps99.99% (via partner)Not colocated, use service provider
# Dedicated Interconnect — 4-circuit redundancy for 99.99% SLA
# 2 metro locations × 2 circuits each

gcloud compute interconnects create prod-interconnect-1 \
  --interconnect-type=DEDICATED \
  --link-type=LINK_TYPE_ETHERNET_10G_LR \
  --requested-link-count=2 \
  --location=equinix-sv1 \
  --admin-enabled

# VLAN attachments connect Interconnect to Cloud Router
gcloud compute interconnects attachments dedicated create vlan-attach-1 \
  --interconnect=prod-interconnect-1 \
  --router=prod-interconnect-router \
  --region=us-central1 \
  --vlan=100 \
  --bandwidth=BPS_10G \
  --candidate-subnets=169.254.0.0/29

gcloud compute interconnects attachments dedicated create vlan-attach-2 \
  --interconnect=prod-interconnect-2 \
  --router=prod-interconnect-router \
  --region=us-central1 \
  --vlan=101 \
  --bandwidth=BPS_10G \
  --candidate-subnets=169.254.0.8/29

# Check Interconnect operational status
gcloud compute interconnects describe prod-interconnect-1 \
  --format='value(operationalStatus,circuitInfos)'

Connectivity Comparison

FactorVPN (IPsec)Direct Connect / Cloud Interconnect
Setup timeMinutes to hoursWeeks to months (physical provisioning)
BandwidthUp to ~1.25 Gbps per tunnelUp to 100 Gbps per circuit
LatencyVariable (public internet path + encryption overhead)Consistent low latency (dedicated path)
ReliabilityDepends on internetSLA-backed 99.9–99.99%
CostLow (hourly + data transfer)High (port + VLAN + data transfer)
EncryptionBuilt-in (IPsec)Not encrypted by default — add MACsec or IPsec over DX

Hybrid Connectivity Patterns

Pattern 1: Primary DX + VPN Backup

Most common pattern. Direct Connect as primary path (high bandwidth, low latency). VPN over internet as automatic failover. BGP preferred over DX via LOCAL_PREF; VPN routes learned with lower LOCAL_PREF. When DX fails, BGP converges to VPN within seconds.

# BGP config for DX primary + VPN backup
# On your on-prem router:

# DX BGP neighbor — high LOCAL_PREF
route-map DX_INBOUND permit 10
  set local-preference 200
!
neighbor 169.254.100.1 route-map DX_INBOUND in

# VPN BGP neighbor — lower LOCAL_PREF (backup)
route-map VPN_INBOUND permit 10
  set local-preference 100    # lower = less preferred
!
neighbor 169.254.200.1 route-map VPN_INBOUND in

Pattern 2: Dual Direct Connect (Maximum Redundancy)

Two separate DX connections at two different DX locations (different physical facilities). AWS recommends for mission-critical workloads. Can also add VPN as tertiary backup. Use AWS Direct Connect SiteLink for inter-region DX routing.

Network Security

AWS Network Firewall

# AWS Network Firewall: stateful L3-L7 inspection, deployed in VPC subnets
# Uses Suricata-compatible rules for IPS/IDS capabilities

resource "aws_networkfirewall_firewall" "prod" {
  name                = "prod-network-firewall"
  firewall_policy_arn = aws_networkfirewall_firewall_policy.prod.arn
  vpc_id              = aws_vpc.prod.id

  subnet_mapping {
    subnet_id = aws_subnet.firewall[0].id
  }
  subnet_mapping {
    subnet_id = aws_subnet.firewall[1].id
  }
}

resource "aws_networkfirewall_rule_group" "stateful_rules" {
  capacity = 100
  name     = "prod-stateful-rules"
  type     = "STATEFUL"

  rule_group {
    rules_source {
      rules_string = <<-EOT
        # Block known malicious domains
        drop dns $HOME_NET any -> any 53 (msg:"Block malware domain"; dns.query; content:"malware-c2.example.com"; nocase; sid:1000001; rev:1;)

        # Allow HTTPS to known good destinations only
        pass tls $HOME_NET any -> $EXTERNAL_NET 443 (msg:"Allow HTTPS"; tls.sni; content:"api.partner.com"; nocase; sid:1000002; rev:1;)

        # Block SSH to internet
        drop tcp $HOME_NET any -> $EXTERNAL_NET 22 (msg:"Block outbound SSH"; flow:to_server,established; sid:1000003; rev:1;)
      EOT
    }
    stateful_rule_options {
      rule_order = "STRICT_ORDER"
    }
  }
}

GCP Cloud Armor

# Cloud Armor: WAF + DDoS protection, integrated with Global HTTPS LB
resource "google_compute_security_policy" "waf" {
  name = "prod-waf-policy"

  # OWASP Top 10 pre-configured rules
  rule {
    action   = "deny(403)"
    priority = 1000
    match {
      expr {
        expression = "evaluatePreconfiguredExpr('xss-v33-stable')"
      }
    }
    description = "Block XSS attacks"
  }

  rule {
    action   = "deny(403)"
    priority = 1001
    match {
      expr {
        expression = "evaluatePreconfiguredExpr('sqli-v33-stable')"
      }
    }
    description = "Block SQL injection"
  }

  # Rate limiting — 1000 req/min per IP
  rule {
    action   = "throttle"
    priority = 2000
    match {
      versioned_expr = "SRC_IPS_V1"
      config { src_ip_ranges = ["*"] }
    }
    rate_limit_options {
      rate_limit_threshold {
        count        = 1000
        interval_sec = 60
      }
      conform_action = "allow"
      exceed_action  = "deny(429)"
      enforce_on_key = "IP"
    }
  }

  # Geo-blocking — block specific countries
  rule {
    action   = "deny(403)"
    priority = 3000
    match {
      expr {
        expression = "origin.region_code == 'KP' || origin.region_code == 'IR'"
      }
    }
    description = "Block specific country codes"
  }

  # Default allow rule (required)
  rule {
    action   = "allow"
    priority = 2147483647
    match {
      versioned_expr = "SRC_IPS_V1"
      config { src_ip_ranges = ["*"] }
    }
    description = "Default allow"
  }

  # Adaptive Protection (ML-based DDoS detection)
  adaptive_protection_config {
    layer_7_ddos_defense_config {
      enable = true
      rule_visibility = "STANDARD"
    }
  }
}

VPC Flow Logs & Traffic Monitoring

# AWS VPC Flow Logs — capture metadata for all traffic (not payload)
# Fields: version, account-id, interface-id, srcaddr, dstaddr, srcport, dstport,
#         protocol, packets, bytes, start, end, action (ACCEPT/REJECT), log-status

resource "aws_flow_log" "vpc" {
  vpc_id          = aws_vpc.prod.id
  traffic_type    = "ALL"
  iam_role_arn    = aws_iam_role.flow_log.arn
  log_destination = aws_cloudwatch_log_group.flow.arn

  # Custom format for better analysis
  log_format = "$${version} $${account-id} $${interface-id} $${srcaddr} $${dstaddr} $${srcport} $${dstport} $${protocol} $${packets} $${bytes} $${start} $${end} $${action} $${flow-direction} $${traffic-path}"
}

# Athena query for rejected traffic analysis
# SELECT srcaddr, dstaddr, dstport, COUNT(*) as count
# FROM vpc_flow_logs
# WHERE action = 'REJECT'
#   AND year = '2026' AND month = '03'
# GROUP BY srcaddr, dstaddr, dstport
# ORDER BY count DESC
# LIMIT 50;

# GCP VPC Flow Logs
resource "google_compute_subnetwork" "app" {
  name          = "app-us-central1"
  ip_cidr_range = "10.0.0.0/20"
  region        = "us-central1"
  network       = google_compute_network.prod.id

  log_config {
    aggregation_interval = "INTERVAL_5_SEC"
    flow_sampling        = 0.5    # sample 50% of flows (1.0 = all)
    metadata             = "INCLUDE_ALL_METADATA"
  }
}

# AWS Traffic Mirroring (full packet capture to inspection appliance)
resource "aws_ec2_traffic_mirror_session" "prod" {
  description              = "Mirror web tier traffic for IDS"
  network_interface_id     = aws_instance.web.primary_network_interface_id
  traffic_mirror_filter_id = aws_ec2_traffic_mirror_filter.web.id
  traffic_mirror_target_id = aws_ec2_traffic_mirror_target.ids.id
  session_number           = 1
  virtual_network_id       = 7777   # VXLAN VNI to identify session
}
Hybrid Connectivity Checklist:
  • Always deploy HA VPN (GCP) or dual-tunnel VPN (AWS) — single tunnel = single point of failure
  • Use BGP over static routing for automatic failover and route propagation
  • Set BFD (Bidirectional Forwarding Detection) for sub-second failure detection on DX/Interconnect
  • Encrypt Direct Connect / Cloud Interconnect traffic with MACsec or IPsec overlay
  • Implement RPKI ROA for your public prefixes to prevent route hijacking
  • Filter BGP prefixes with max-prefix limits to protect against route leaks
  • Enable VPC Flow Logs on all subnets — essential for security investigation and traffic analysis
  • Deploy Network Firewall (AWS) or Cloud Armor (GCP) for east-west and north-south inspection