Firewall #
Firewall adalah lapisan pertahanan yang memfilter traffic jaringan berdasarkan aturan yang telah ditetapkan — memutuskan mana yang boleh masuk, mana yang boleh keluar, dan mana yang harus diblokir. Dalam konteks aplikasi web modern, “firewall” bukan lagi hanya kotak fisik di rack server — ia mencakup security group di cloud, iptables di Linux, Web Application Firewall (WAF) di depan aplikasi, dan bahkan konfigurasi rate limiting di load balancer.
Memahami firewall dari perspektif developer web berarti memahami bagaimana traffic mengalir ke dan dari aplikasi, di titik mana aturan filtering diterapkan, dan bagaimana berbagai lapisan firewall bekerja bersama untuk memberikan defense in depth. Firewall yang dikonfigurasi dengan benar adalah perbedaan antara sistem yang hanya mengekspos apa yang diperlukan vs sistem yang membiarkan attacker bebas mengeksplorasi.
Model Pertahanan Berlapis #
Tidak ada satu komponen firewall yang bisa melindungi dari semua ancaman. Defense in depth menggunakan beberapa lapisan yang saling melengkapi.
graph TB
Internet --> CDN[CDN / DDoS Protection\nCloudflare, AWS Shield]
CDN --> WAF[Web Application Firewall\nOWASP rules, rate limiting]
WAF --> LB[Load Balancer\nSSL termination, health check]
LB --> SG[Security Group / Network Firewall\nPort whitelist, IP rules]
SG --> App[Application Server\niptables, aplikasi]
App --> DB[Database\nHanya dari App Server]
App --> Cache[Redis/Cache\nHanya dari App Server]
App --> Queue[Message Queue\nHanya dari App Server]
style CDN fill:#e8f4f8
style WAF fill:#e8f4f8
style LB fill:#e8f8e8
style SG fill:#e8f8e8
style App fill:#f8f8e8
style DB fill:#f8e8e8
style Cache fill:#f8e8e8Setiap lapisan memiliki tanggung jawab berbeda:
Lapisan dan tanggung jawabnya:
CDN/DDoS Protection:
→ Menyerap volumetric attack sebelum mencapai infrastruktur
→ Anycast routing mendistribusikan traffic ke edge nodes
→ IP reputation filtering berdasarkan database global
Web Application Firewall (WAF):
→ Filter request berdasarkan pola serangan yang dikenal
→ OWASP Core Rule Set: SQLi, XSS, RCE payload
→ Rate limiting per IP dan per endpoint
Load Balancer:
→ SSL/TLS termination
→ Health check — traffic tidak diteruskan ke server down
→ Connection limiting
Security Group / Network Firewall:
→ Port whitelist — hanya port yang diperlukan yang terbuka
→ Source IP restriction untuk port sensitif (SSH, database)
→ Segmentasi network antar tier
Host-based Firewall (iptables/nftables):
→ Defense in depth di level server
→ Filter bahkan jika security group dikonfigurasi salah
→ Egress filtering untuk membatasi koneksi keluar
Database / Cache:
→ Hanya menerima koneksi dari application tier
→ Tidak pernah terbuka ke internet
Network Firewall: iptables dan Security Group #
iptables di Linux #
iptables adalah host-based firewall yang berjalan di setiap server Linux. Ia memfilter packet berdasarkan chain dan aturan yang didefinisikan.
#!/bin/bash
# firewall-setup.sh — Konfigurasi iptables untuk application server
# Flush semua aturan yang ada
iptables -F
iptables -X
iptables -Z
# Default policy: DROP semua traffic (deny by default)
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT # Izinkan semua outgoing (diperketat nanti)
# =============================================
# LOOPBACK — izinkan traffic internal
# =============================================
iptables -A INPUT -i lo -j ACCEPT
# =============================================
# ESTABLISHED CONNECTIONS — izinkan response
# =============================================
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# =============================================
# ICMP — izinkan ping (tapi limit untuk menghindari flood)
# =============================================
iptables -A INPUT -p icmp --icmp-type echo-request \
-m limit --limit 1/second --limit-burst 10 -j ACCEPT
# =============================================
# SSH — izinkan dari IP admin saja (JANGAN biarkan terbuka ke internet!)
# =============================================
iptables -A INPUT -p tcp --dport 22 \
-s 10.0.0.0/8 -j ACCEPT # dari internal network saja
# iptables -A INPUT -p tcp --dport 22 \
# -s 203.x.x.x/32 -j ACCEPT # atau dari IP admin yang spesifik
# =============================================
# HTTPS dan HTTP — izinkan dari mana saja
# =============================================
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -j ACCEPT # untuk redirect ke HTTPS
# =============================================
# RATE LIMITING — anti brute force dan DDoS
# =============================================
# Limit koneksi baru per IP (anti port scan dan koneksi berlebihan)
iptables -A INPUT -p tcp --dport 443 \
-m conntrack --ctstate NEW \
-m limit --limit 60/minute --limit-burst 20 -j ACCEPT
# =============================================
# DROP sisanya dan log untuk investigasi
# =============================================
iptables -A INPUT -j LOG --log-prefix "IPTABLES_DROP: " \
--log-level 4 -m limit --limit 10/minute # limit logging
iptables -A INPUT -j DROP
# =============================================
# EGRESS FILTERING — batasi koneksi keluar
# =============================================
# Ini penting untuk mendeteksi dan membatasi jika server dikompromikan
iptables -P OUTPUT DROP
# DNS
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
# NTP (time sync)
iptables -A OUTPUT -p udp --dport 123 -j ACCEPT
# HTTP/HTTPS untuk download update dan API external
iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT
# Koneksi ke database internal (contoh: Redis di 10.0.1.5)
iptables -A OUTPUT -p tcp -d 10.0.1.5 --dport 6379 -j ACCEPT
# Koneksi ke PostgreSQL internal (contoh: 10.0.1.10)
iptables -A OUTPUT -p tcp -d 10.0.1.10 --dport 5432 -j ACCEPT
# Established connections (responses)
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Loopback
iptables -A OUTPUT -o lo -j ACCEPT
# Log dan drop egress yang tidak diizinkan
iptables -A OUTPUT -j LOG --log-prefix "EGRESS_DROP: " --log-level 4 \
-m limit --limit 5/minute
iptables -A OUTPUT -j DROP
# Simpan aturan agar persist setelah reboot
iptables-save > /etc/iptables/rules.v4
Security Group di Cloud (AWS sebagai contoh) #
Security group di cloud bekerja di level virtual network — lebih fleksibel dari iptables karena bisa diupdate tanpa masuk ke server.
# Terraform — Security Group untuk application server
resource "aws_security_group" "app_server" {
name = "app-server-sg"
description = "Security group untuk application server"
vpc_id = aws_vpc.main.id
# Ingress: traffic yang masuk ke server
# HTTPS dari load balancer saja (bukan dari internet langsung)
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
security_groups = [aws_security_group.load_balancer.id]
description = "HTTPS dari load balancer"
}
# HTTP dari load balancer (untuk redirect ke HTTPS)
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.load_balancer.id]
description = "HTTP redirect dari load balancer"
}
# SSH hanya dari bastion host atau VPN (JANGAN 0.0.0.0/0)
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [aws_security_group.bastion.id]
description = "SSH dari bastion host saja"
}
# Egress: traffic yang keluar dari server
# Ke database (RDS)
egress {
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.database.id]
description = "PostgreSQL ke RDS"
}
# Ke Redis (ElastiCache)
egress {
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [aws_security_group.redis.id]
description = "Redis ke ElastiCache"
}
# HTTPS ke internet (untuk API external, update)
egress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTPS ke internet"
}
# DNS
egress {
from_port = 53
to_port = 53
protocol = "udp"
cidr_blocks = ["0.0.0.0/0"]
description = "DNS resolution"
}
tags = {
Name = "app-server-sg"
Environment = "production"
}
}
# Security group untuk database — TIDAK dari internet
resource "aws_security_group" "database" {
name = "database-sg"
description = "Database hanya bisa diakses dari app server"
vpc_id = aws_vpc.main.id
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.app_server.id]
description = "PostgreSQL dari app server saja"
}
# Tidak ada egress ke internet dari database
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["10.0.0.0/8"] # hanya ke internal network
description = "Internal network saja"
}
}
Web Application Firewall (WAF) #
WAF beroperasi di layer 7 (application layer) — ia memahami HTTP dan bisa memfilter berdasarkan konten request, bukan hanya IP dan port.
# ModSecurity di Nginx — WAF open source
# Di nginx.conf:
# modsecurity on;
# modsecurity_rules_file /etc/nginx/modsecurity/modsecurity.conf;
# modsecurity.conf — konfigurasi dasar
SecRuleEngine On
SecRequestBodyAccess On
SecResponseBodyAccess Off
SecRequestBodyLimit 13107200 # 12.5MB max request body
# OWASP Core Rule Set — aturan untuk serangan yang umum dikenal
Include /etc/nginx/modsecurity/crs/crs-setup.conf
Include /etc/nginx/modsecurity/crs/rules/*.conf
# Deteksi SQL injection
SecRule ARGS "@detectSQLi" \
"id:1001,phase:2,deny,status:403,log,msg:'SQL Injection Attempt'"
# Deteksi XSS
SecRule ARGS "@detectXSS" \
"id:1002,phase:2,deny,status:403,log,msg:'XSS Attempt'"
# Deteksi path traversal
SecRule REQUEST_URI "@contains ../" \
"id:1003,phase:1,deny,status:403,log,msg:'Path Traversal Attempt'"
# Rate limiting per IP untuk endpoint login
SecRule REQUEST_URI "@beginsWith /login" \
"id:1010,phase:1,pass,nolog,setvar:ip.login_count=+1,\
expirevar:ip.login_count=300"
SecRule IP:LOGIN_COUNT "@gt 20" \
"id:1011,phase:1,deny,status:429,log,msg:'Login rate limit exceeded'"
WAF di Cloud (AWS WAF sebagai contoh) #
# AWS WAF v2 dengan Managed Rule Groups
resource "aws_wafv2_web_acl" "main" {
name = "main-waf"
scope = "REGIONAL" # atau CLOUDFRONT untuk CDN
default_action {
allow {}
}
# AWS Managed Rules — OWASP protection
rule {
name = "AWSManagedRulesCommonRuleSet"
priority = 1
override_action {
none {} # gunakan action dari managed rule
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "CommonRuleSetMetric"
sampled_requests_enabled = true
}
}
# Perlindungan SQL injection
rule {
name = "AWSManagedRulesSQLiRuleSet"
priority = 2
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesSQLiRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "SQLiRuleSetMetric"
sampled_requests_enabled = true
}
}
# Rate limiting per IP
rule {
name = "RateLimitPerIP"
priority = 10
action {
block {}
}
statement {
rate_based_statement {
limit = 2000 # request per 5 menit
aggregate_key_type = "IP"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "RateLimitMetric"
sampled_requests_enabled = true
}
}
# IP whitelist untuk endpoint admin
rule {
name = "AdminIPWhitelist"
priority = 5
action {
block {}
}
statement {
and_statement {
statement {
byte_match_statement {
field_to_match {
uri_path {}
}
positional_constraint = "STARTS_WITH"
search_string = "/admin"
text_transformation {
priority = 0
type = "LOWERCASE"
}
}
}
statement {
not_statement {
statement {
ip_set_reference_statement {
arn = aws_wafv2_ip_set.admin_ips.arn
}
}
}
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AdminAccessMetric"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "MainWAFMetric"
sampled_requests_enabled = true
}
}
# IP set untuk admin whitelist
resource "aws_wafv2_ip_set" "admin_ips" {
name = "admin-ip-whitelist"
scope = "REGIONAL"
ip_address_version = "IPV4"
addresses = [
"203.x.x.x/32", # IP kantor
"10.0.0.0/8", # VPN internal
]
}
IP Management: Allowlist dan Blocklist #
# Manajemen IP allowlist dan blocklist di aplikasi
import redis
import ipaddress
redis_client = redis.Redis(host='redis', port=6379, decode_responses=True)
class IPManager:
BLOCKLIST_KEY = "ip:blocklist"
ALLOWLIST_KEY = "ip:allowlist"
TEMP_BLOCK_PREFIX = "ip:temp_block:"
def is_blocked(self, ip: str) -> bool:
"""Cek apakah IP diblokir."""
# Cek permanent blocklist
if redis_client.sismember(self.BLOCKLIST_KEY, ip):
return True
# Cek temporary block
if redis_client.exists(f"{self.TEMP_BLOCK_PREFIX}{ip}"):
return True
# Cek subnet blocklist (untuk menangani range IP)
try:
ip_obj = ipaddress.ip_address(ip)
blocked_subnets = redis_client.smembers("ip:blocklist:subnets")
for subnet in blocked_subnets:
if ip_obj in ipaddress.ip_network(subnet, strict=False):
return True
except ValueError:
pass
return False
def is_allowed(self, ip: str) -> bool:
"""Cek apakah IP dalam allowlist (prioritas tertinggi)."""
return redis_client.sismember(self.ALLOWLIST_KEY, ip)
def block_temporarily(self, ip: str, seconds: int, reason: str):
"""Blokir IP sementara."""
redis_client.setex(
f"{self.TEMP_BLOCK_PREFIX}{ip}",
seconds,
reason
)
def block_permanently(self, ip: str, reason: str):
"""Blokir IP secara permanen."""
pipe = redis_client.pipeline()
pipe.sadd(self.BLOCKLIST_KEY, ip)
pipe.hset("ip:blocklist:reasons", ip, reason)
pipe.execute()
def unblock(self, ip: str):
"""Hapus IP dari semua blocklist."""
redis_client.srem(self.BLOCKLIST_KEY, ip)
redis_client.delete(f"{self.TEMP_BLOCK_PREFIX}{ip}")
ip_manager = IPManager()
# Middleware yang menerapkan IP management
@app.before_request
def check_ip():
ip = request.remote_addr
# Allowlist selalu diizinkan (monitoring, internal tools)
if ip_manager.is_allowed(ip):
return None
# Blocklist diblokir
if ip_manager.is_blocked(ip):
return jsonify({'error': 'Access denied'}), 403
return None
Egress Filtering: Membatasi Koneksi Keluar #
Egress filtering sering diabaikan — fokus biasanya pada membatasi traffic yang masuk, tapi traffic keluar yang tidak dikontrol bisa menandakan server yang dikompromikan atau kebocoran data.
# Egress filtering dengan iptables — hanya izinkan koneksi keluar yang diperlukan
# Hapus policy default OUTPUT
iptables -P OUTPUT DROP
# Loopback selalu diizinkan
iptables -A OUTPUT -o lo -j ACCEPT
# Established connections (responses ke incoming request)
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# DNS — diperlukan untuk resolusi nama
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
# NTP — sinkronisasi waktu
iptables -A OUTPUT -p udp --dport 123 -j ACCEPT
# HTTPS ke internet — untuk update OS, download dependency, API external
iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT
# Database internal saja (bukan ke internet)
iptables -A OUTPUT -p tcp -d 10.0.1.0/24 --dport 5432 -j ACCEPT
iptables -A OUTPUT -p tcp -d 10.0.1.0/24 --dport 6379 -j ACCEPT
# SMTP untuk kirim email (jika ada outbound email server)
iptables -A OUTPUT -p tcp -d 10.0.2.5 --dport 587 -j ACCEPT
# Log koneksi keluar yang diblokir — mendeteksi server yang dikompromikan
iptables -A OUTPUT -j LOG --log-prefix "EGRESS_BLOCKED: " \
--log-level 4 -m limit --limit 5/minute
iptables -A OUTPUT -j DROP
Mengapa egress filtering penting:
Jika server dikompromikan, attacker akan mencoba:
→ Koneksi balik ke C2 server (command and control)
→ Data exfiltration — kirim data ke server attacker
→ Download tools tambahan untuk post-exploitation
→ Pivot ke sistem internal lain
Dengan egress filtering:
→ Koneksi ke domain attacker diblokir → C2 tidak bisa berjalan
→ Data exfiltration ke internet diblokir
→ Alert dipicu karena ada koneksi yang tidak diizinkan
Log "EGRESS_BLOCKED" yang muncul tiba-tiba adalah tanda peringatan
yang perlu segera diinvestigasi
Monitoring Firewall #
Aturan firewall yang ada tapi tidak dimonitor adalah aturan yang tidak berguna.
# Parsing dan analisis log iptables untuk mendeteksi anomali
import re
from collections import Counter, defaultdict
from datetime import datetime
def parse_iptables_log(log_line: str) -> dict | None:
"""Parse satu baris log iptables."""
# Format: Jun 15 14:23:45 server IPTABLES_DROP: IN=eth0 OUT= SRC=1.2.3.4 DST=10.0.0.1 ...
pattern = r'(?P<timestamp>\w+ \d+ \d+:\d+:\d+) (?P<host>\S+) (?P<prefix>[\w_]+): IN=(?P<in>\S*) OUT=(?P<out>\S*) SRC=(?P<src>\S+) DST=(?P<dst>\S+) (?:.*)?DPT=(?P<dpt>\d+)'
match = re.search(pattern, log_line)
if not match:
return None
return {
'timestamp': match.group('timestamp'),
'prefix': match.group('prefix'),
'src_ip': match.group('src'),
'dst_ip': match.group('dst'),
'dst_port': int(match.group('dpt')),
}
class FirewallAnalyzer:
def __init__(self):
self.blocked_ips = Counter()
self.blocked_ports = Counter()
self.egress_blocked = defaultdict(list)
def analyze_log_file(self, log_path: str):
with open(log_path) as f:
for line in f:
entry = parse_iptables_log(line)
if not entry:
continue
if 'IPTABLES_DROP' in entry['prefix']:
# Traffic masuk yang diblokir
self.blocked_ips[entry['src_ip']] += 1
self.blocked_ports[entry['dst_port']] += 1
elif 'EGRESS_BLOCKED' in entry['prefix']:
# Traffic keluar yang diblokir — perlu investigasi!
self.egress_blocked[entry['dst_ip']].append(entry)
def get_alerts(self) -> list[dict]:
alerts = []
# Alert: IP yang sering diblokir (bisa jadi scanner atau attacker)
for ip, count in self.blocked_ips.most_common(10):
if count > 100:
alerts.append({
'type': 'frequent_blocked_ip',
'ip': ip,
'count': count,
'action': 'consider_permanent_block'
})
# Alert: Egress yang diblokir (bisa jadi server dikompromikan)
if self.egress_blocked:
alerts.append({
'type': 'egress_blocked',
'destinations': dict(self.egress_blocked),
'action': 'INVESTIGATE_IMMEDIATELY' # ini serius
})
# Alert: Port scanning (banyak port berbeda dari satu IP)
# (perlu tracking per IP per port)
return alerts
Konfigurasi Fail2ban: Otomasi IP Blocking #
Fail2ban memantau log aplikasi dan otomatis memblokir IP yang menunjukkan perilaku berbahaya.
# /etc/fail2ban/jail.local
[DEFAULT]
# Ban selama 1 jam default
bantime = 3600
# Cek dalam 10 menit terakhir
findtime = 600
# 5 percobaan sebelum ban
maxretry = 5
# Backend untuk monitoring log
backend = auto
# Email notifikasi (opsional)
# action = %(action_mw)s
destemail = [email protected]
sendername = Fail2Ban
[sshd]
enabled = true
port = ssh
maxretry = 3
bantime = 86400 # 24 jam untuk SSH brute force
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 5
[nginx-limit-req]
enabled = true
port = http,https
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
maxretry = 10
findtime = 60
bantime = 300
# Custom filter untuk aplikasi
[app-login-brute]
enabled = true
port = http,https
filter = app-login-brute
logpath = /var/log/app/security.log
maxretry = 5
findtime = 300
bantime = 1800 # 30 menit
# /etc/fail2ban/filter.d/app-login-brute.conf
[Definition]
# Match log entry untuk login gagal
failregex = ^.*"event":"login_failed".*"ip":"<HOST>".*$
# Diabaikan
ignoreregex =
Anti-Pattern yang Harus Dihindari #
# ✗ Anti-pattern 1: SSH terbuka ke seluruh internet
# Security group / iptables:
iptables -A INPUT -p tcp --dport 22 -j ACCEPT # JANGAN!
# AWS: ingress port 22 dari 0.0.0.0/0
# ✓ Solusi: SSH hanya dari IP spesifik atau VPN
iptables -A INPUT -p tcp --dport 22 -s 10.0.0.0/8 -j ACCEPT
# Atau gunakan bastion host / jump server
# ✗ Anti-pattern 2: database port terbuka ke internet
# PostgreSQL port 5432, MySQL port 3306 dari 0.0.0.0/0
# Attacker bisa langsung coba brute force database
# ✓ Solusi: database hanya menerima dari application server
# Security group database: ingress 5432 dari app-server-sg saja
# ✗ Anti-pattern 3: tidak ada egress filtering
# Server dikompromikan → attacker bebas download tools dan kirim data keluar
# ✓ Solusi: whitelist egress yang diperlukan, blokir sisanya
# ✗ Anti-pattern 4: WAF dalam mode "detection only" tapi tidak pernah di-enforce
# WAF logging tapi tidak blocking — seperti CCTV yang tidak dipantau
# ✓ Solusi: setelah periode monitoring dan tuning, aktifkan blocking mode
# ✗ Anti-pattern 5: firewall rules yang tidak di-review
# Aturan yang ditambahkan untuk kebutuhan sementara dan tidak pernah dihapus
# Firewall creep: semakin banyak exception, semakin lemah perlindungan
# ✓ Solusi: review firewall rules secara berkala (quarterly)
# Hapus rules yang sudah tidak diperlukan
# ✗ Anti-pattern 6: tidak ada alerting untuk traffic yang diblokir
# Serangan terjadi tapi tidak ada yang tahu
# ✓ Solusi: monitor dan alert untuk spike dalam blocked traffic
Checklist Firewall #
NETWORK FIREWALL:
□ Default policy DENY — hanya port yang diperlukan yang dibuka
□ SSH tidak terbuka ke internet (0.0.0.0/0)
□ Database port tidak terbuka ke internet
□ Port admin/management hanya dari VPN atau bastion host
□ Egress filtering dikonfigurasi — hanya traffic yang diperlukan yang diizinkan
SECURITY GROUP (CLOUD):
□ Setiap tier punya security group sendiri
□ Database hanya menerima dari app server security group
□ Cache/Redis hanya menerima dari app server security group
□ Load balancer adalah satu-satunya yang expose ke internet
□ Tidak ada security group dengan 0.0.0.0/0 untuk SSH atau database
WEB APPLICATION FIREWALL:
□ WAF aktif di depan aplikasi
□ OWASP Core Rule Set dikonfigurasi
□ WAF dalam mode blocking (bukan hanya detection)
□ Rate limiting dikonfigurasi di WAF
□ False positive dimonitor dan aturan di-tune secara berkala
IP MANAGEMENT:
□ IP blocklist untuk IP yang diketahui berbahaya
□ IP allowlist untuk akses internal dan monitoring
□ Fail2ban atau equivalent untuk otomasi blocking
□ Proses untuk review dan update blocklist
MONITORING:
□ Firewall logs dikumpulkan dan dianalisis
□ Alert untuk spike traffic yang diblokir
□ Alert untuk egress yang diblokir (indikasi kompromi)
□ Dashboard untuk traffic pattern
□ Log retention yang cukup untuk investigasi forensik
REVIEW DAN MAINTENANCE:
□ Firewall rules di-review secara berkala (minimal quarterly)
□ Rules yang sudah tidak diperlukan dihapus
□ Perubahan firewall melalui proses review (IaC, PR, approval)
□ Dokumentasi untuk setiap exception dan alasannya
Ringkasan #
- Defense in depth menggunakan beberapa lapisan — CDN/DDoS protection, WAF, load balancer, security group, dan host-based firewall bekerja bersama. Setiap lapisan menangkap apa yang terlewat lapisan sebelumnya.
- Default DENY adalah prinsip fundamental — hanya traffic yang secara eksplisit diizinkan yang boleh lewat. Semua yang lain diblokir secara default.
- SSH tidak boleh terbuka ke internet — gunakan bastion host, VPN, atau IP allowlist yang ketat. Port 22 yang terbuka ke 0.0.0.0/0 adalah undangan untuk brute force.
- Database tidak boleh pernah terbuka ke internet — hanya application server yang boleh berkomunikasi dengan database. Security group yang benar memastikan ini.
- Egress filtering sama pentingnya dengan ingress — traffic keluar yang tidak diizinkan adalah tanda server dikompromikan. Whitelist egress dan alert untuk anomali.
- WAF beroperasi di layer aplikasi — ia memahami HTTP dan bisa mendeteksi payload serangan (SQLi, XSS, path traversal) yang tidak bisa difilter oleh network firewall biasa.
- WAF harus dalam mode blocking — WAF yang hanya detection tidak memberikan perlindungan nyata. Setelah tuning untuk mengurangi false positive, aktifkan blocking.
- Fail2ban mengotomasi respons terhadap serangan — brute force SSH, login yang berulang gagal, dan pola berbahaya lainnya otomatis menghasilkan temporary IP ban.
- Firewall rules perlu di-review secara berkala — rules yang ditambahkan untuk kebutuhan sementara dan tidak pernah dihapus menciptakan attack surface yang tidak disengaja.
- Semua perubahan firewall harus melalui Infrastructure as Code — iptables atau security group yang dikonfigurasi manual tidak terdokumentasi dan tidak bisa di-review. Gunakan Terraform atau CloudFormation.