နားလည်ထားရမယ့် အချက်
Firewall တစ်ခုရဲ့ အလုပ်က ရိုးရှင်းသလို ထင်ရတယ် — packet တစ်ခု ဖြတ်သွားခွင့် ရှိမရှိ ဆုံးဖြတ်ဖို့ — ဒါပေမယ့် design တစ်ခုလုံးဟာ firewall က ဘယ်လောက် မှတ်မိထားလဲ ဆိုတဲ့အပေါ်မှာ လှည့်နေတယ်။
Stateless filter တစ်ခုက packet တစ်ခုစီကို သီးခြားစီ စစ်ဆေးပြီး သူ့ရှေ့မှာ ရှိတဲ့ header field တွေကိုသာ သုံးတယ် — source နဲ့ destination address, protocol, port, flag။ အရင်က ဘာဖြစ်ခဲ့လဲ ဆိုတာ လုံးဝ မမှတ်မိဘူး။ ဒါက ဈေးသက်သာပြီး မြန်တယ်၊ ဒါပေမယ့် ရုပ်ဆိုးတဲ့ ပြဿနာတစ်ခု ဖန်တီးတယ်။ သင့် server က အပြင်ဆီ request တစ်ခု လုပ်လိုက်တဲ့အခါ ပြန်ဖြေချက်ဟာ ကျပန်း remote port တစ်ခုကနေ သင့် machine ပေါ်က နံပါတ်မြင့် ephemeral port တစ်ခုဆီ ရောက်လာတယ်။ အဲဒီ ပြန်ဖြေချက်တွေကို ခွင့်ပြုချင်တဲ့ stateless filter တစ်ခုအနေနဲ့ ၎င်းတို့ကို အဲဒီ port အတူတူဆီ မတောင်းဆိုဘဲ ဝင်လာတဲ့ inbound connection တွေနဲ့ ခွဲခြားလို့ မရဘူး — ဒါကြောင့် ephemeral range တစ်ခုလုံးကို ကမ္ဘာကြီးဆီ ဖွင့်ပေးလိုက်ရတယ်။
Stateful firewall တစ်ခုကတော့ connection table တစ်ခု ထားတယ်။ Outbound flow တစ်ခုကို ခွင့်ပြုလိုက်တဲ့အခါ five-tuple ကို မှတ်ထားပြီး၊ ရှိပြီးသား entry တစ်ခုနဲ့ ကိုက်ညီတဲ့ return packet တွေကို လက်ခံတယ် — port တစ်ခု ဖွင့်ထားလို့ မဟုတ်ဘဲ သင် စတင်ခဲ့တဲ့ conversation တစ်ခုထဲက ဖြစ်လို့။ ဒါကြောင့်ပဲ တကယ့် ruleset မှန်သမျှနီးပါးရဲ့ ပထမဆုံး rule က ESTABLISHED traffic ကို လက်ခံတာ ဖြစ်တယ်။ ဒီ ခွဲခြားချက်ဟာ packet ကိုယ်တိုင်ထဲမှာ မမြင်ရဘူး — packet နှစ်ခုဟာ byte တစ်ခုမကျန် တူညီပြီး တစ်ခုက known flow တစ်ခုထဲက ဖြစ်နေရုံနဲ့ ဆန့်ကျင်ဘက် ဆုံးဖြတ်ချက် ရနိုင်တယ်။
ဒါတွေ အလုပ်ဖြစ်စေတဲ့ posture ကတော့ default deny ဖြစ်တယ်။ နောက်ဆုံး rule က အားလုံးကို drop လုပ်ပြီး၊ သူ့အပေါ်က rule တစ်ခုစီက သီးခြား ခြွင်းချက်တစ်ခုစီ ထုတ်ပေးတယ်။ ကျန်တဲ့နည်း — အားလုံး ခွင့်ပြုပြီး ဆိုးတာတွေကို blocklist လုပ်တာ — ဟာ သင် မမျှော်လင့်ထားတဲ့ service တစ်ခုကို တစ်စုံတစ်ယောက်က deploy လုပ်လိုက်တဲ့ အခိုက်မှာ ကျရှုံးတယ်။
Rule တွေကို အပေါ်ကနေ အောက်ဆီ စစ်ပြီး ပထမဆုံး ကိုက်တဲ့ဟာ အနိုင်ရတယ်။ အစီအစဉ်ဟာ program ကိုယ်တိုင် ဖြစ်တယ်။ ကျယ်ပြန့်တဲ့ allow တစ်ခုကို ကျဉ်းမြောင်းတဲ့ deny တစ်ခုအပေါ်မှာ ထားလိုက်ရင် အဲဒီ deny ဟာ ရောက်လို့မရတဲ့ rule ဖြစ်သွားပြီး ဘာ warning မှ မပေးဘူး။
နောက်ဆုံးအနေနဲ့ network firewall က boundary တစ်ခုမှာ filter လုပ်ပြီး၊ host firewall ကတော့ machine ပေါ်မှာတင် run နေလို့ အဲဒီ boundary ကို ဘယ်တုန်းကမှ မဖြတ်ခဲ့တဲ့ traffic အပေါ်မှာပါ သက်ရောက်တယ်။
A PACKET THROUGH AN ORDERED RULE CHAIN
--------------------------------------
packet arrives
|
v
+-----------------------------------+
| 1. state == ESTABLISHED ? |-- match --> ACCEPT
+-----------------------------------+
| no match
v
+-----------------------------------+
| 2. dst 203.0.113.10:443, NEW ? |-- match --> ACCEPT
+-----------------------------------+
| no match
v
+-----------------------------------+
| 3. src 10.0.0.0/24 -> :22, NEW ? |-- match --> ACCEPT
+-----------------------------------+
| no match
v
+-----------------------------------+
| 4. default policy |------------> DROP
+-----------------------------------+
FIRST MATCH WINS
Nothing below a matching rule is ever read. Put a broad ACCEPT
above a narrow DROP and the DROP becomes dead code, silently.
WHY RULE 1 EXISTS
stateless: to let replies in you must open the whole
ephemeral range 32768-60999 to the internet
stateful: the reply matches a flow YOU started, so no
inbound port is opened at allလက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်
Production ဆီ အမြဲ ရောက်သွားတတ်တဲ့ rule ordering bug တစ်ခုကို ကြည့်ရအောင်။ တစ်စုံတစ်ယောက်က admin panel အသစ်တစ်ခု ဖွင့်ဖို့ လိုအပ်လို့ မည်သည့်နေရာကမဆို port 8080 ကို allow rule တစ်ခု ထည့်လိုက်တယ်။ နောက်ပိုင်းမှာ security review တစ်ခုက ရုံးအပြင်ဘက်ကနေ admin panel ကို deny rule တစ်ခု ထည့်တယ်။ သူတို့က ကျန် deny တွေနားက အောက်ဆုံးမှာ ထားလိုက်တယ်။ အဲဒီ rule ဟာ ဘယ်တော့မှ အလုပ်မလုပ်ဘူး — အပေါ်က allow က ကိုက်ညီပြီးသားမို့ first-match-wins အရ စစ်ဆေးမှု အဲဒီမှာ ရပ်သွားတယ်။ Ruleset ဟာ လူတစ်ယောက် ဖတ်ကြည့်ရင် မှန်နေပြီး တကယ်တမ်း မှားနေတဲ့ အလုပ်ကို လုပ်နေတယ်။
ဒါကို ကာကွယ်တဲ့ အလေ့အထ — rule တွေကို အတိကျဆုံးကနေ အကျယ်ပြန့်ဆုံးဆီ စီပါ၊ ပြီးတော့ နောက်ဆုံး default-deny ကိုသာ chain ထဲက တစ်ခုတည်းသော ကျယ်ပြန့်တဲ့ rule အဖြစ် ထားပါ။ 'မည်သည့်နေရာကမဆို' လို့ ဆိုတဲ့ allow တစ်ခုစီဟာ သူ့အပေါ်မှာ ပိုကျဉ်းတဲ့ တစ်ခုခု ထားစရာ လိုမလို ပြန်စစ်ဖို့ သတိပေးသင့်တယ်။
ဒုတိယ အလေ့အထက reject နဲ့ drop ကို တမင်တကာ ခွဲသုံးဖို့ပါ။ Reject က ICMP ဒါမှမဟုတ် TCP RST ပို့တာမို့ client က ချက်ချင်း ကျရှုံးပြီး သင့် user တွေ error ကို မြန်မြန် ရတယ်။ Drop က ဘာမှ မပို့တာမို့ client က timeout အထိ စောင့်နေရတယ်။ Internet ဘက်ကို မျက်နှာမူတဲ့နေရာမှာ drop က ပိုကောင်းတယ် — scanner တစ်ခုကို သတင်းအချက်အလက် ဘာမှ မပေးလို့ပါ။ အတွင်းပိုင်းမှာတော့ reject က ပိုကောင်းတယ် — စက္ကန့် သုံးဆယ် ကြာအောင် ဆိုင်းငံ့နေတာဟာ ချက်ချင်း ငြင်းပယ်ခံရတာထက် debug လုပ်ရ များစွာ ပိုဆိုးလို့ပါ — ပြီးတော့ troubleshooting သင်ခန်းစာမှာ ပြောခဲ့သလို timeout နဲ့ refused ကွာခြားချက်ဟာ firewall တစ်ခု ပါဝင်နေကြောင်း ပြောပြတဲ့ အချက်ပါပဲ။
အောက်က engine က packet အစုတစ်ခုကို ordered chain တစ်ခုနဲ့ စစ်ပြပြီး၊ port 54321 packet နှစ်ခုက statefulness တစ်ခုတည်းက ရလဒ်ကို ဆုံးဖြတ်နေတာ ပြသတယ်။
အတူတူ စမ်းရေးကြည့်မယ်
import ipaddress
# An ordered rule chain. Order is the whole program: the first rule that
# matches decides, and nothing below it is ever consulted.
RULES = [
{"n": 1, "action": "ACCEPT", "state": "ESTABLISHED", "src": "any",
"dst": "any", "port": "any"},
{"n": 2, "action": "ACCEPT", "state": "NEW", "src": "any",
"dst": "203.0.113.10", "port": 443},
{"n": 3, "action": "ACCEPT", "state": "NEW", "src": "10.0.0.0/24",
"dst": "203.0.113.10", "port": 22},
{"n": 4, "action": "DROP", "state": "any", "src": "any",
"dst": "any", "port": "any"}, # default deny, last
]
def matches(rule, pkt):
if rule["state"] != "any" and rule["state"] != pkt["state"]:
return False
if rule["src"] != "any":
if ipaddress.ip_address(pkt["src"]) not in ipaddress.ip_network(rule["src"]):
return False
if rule["dst"] != "any" and rule["dst"] != pkt["dst"]:
return False
if rule["port"] != "any" and rule["port"] != pkt["port"]:
return False
return True
def evaluate(pkt):
for rule in RULES:
if matches(rule, pkt):
return rule["n"], rule["action"]
return None, "DROP" # unreachable here; rule 4 always matches
PACKETS = [
{"src": "198.51.100.7", "dst": "203.0.113.10", "port": 443, "state": "NEW"},
{"src": "198.51.100.7", "dst": "203.0.113.10", "port": 22, "state": "NEW"},
{"src": "10.0.0.5", "dst": "203.0.113.10", "port": 22, "state": "NEW"},
{"src": "93.184.216.34", "dst": "203.0.113.10", "port": 54321,
"state": "ESTABLISHED"},
{"src": "93.184.216.34", "dst": "203.0.113.10", "port": 54321,
"state": "NEW"},
{"src": "198.51.100.7", "dst": "203.0.113.10", "port": 3306, "state": "NEW"},
]
for pkt in PACKETS:
n, action = evaluate(pkt)
where = "rule " + str(n) if n else "policy"
print(action.ljust(7) + pkt["src"].ljust(15) + " -> " +
pkt["dst"] + ":" + str(pkt["port"]).ljust(6) +
pkt["state"].ljust(12) + "(" + where + ")")
print("")
# The two 54321 packets are the point of the whole lesson: identical
# 5-tuples, opposite outcomes, decided purely by connection state.
print("packets 4 and 5 are byte-identical except for state.")
print("stateless filtering cannot tell them apart; stateful can.")ACCEPT 198.51.100.7 -> 203.0.113.10:443 NEW (rule 2)
DROP 198.51.100.7 -> 203.0.113.10:22 NEW (rule 4)
ACCEPT 10.0.0.5 -> 203.0.113.10:22 NEW (rule 3)
ACCEPT 93.184.216.34 -> 203.0.113.10:54321 ESTABLISHED (rule 1)
DROP 93.184.216.34 -> 203.0.113.10:54321 NEW (rule 4)
DROP 198.51.100.7 -> 203.0.113.10:3306 NEW (rule 4)
packets 4 and 5 are byte-identical except for state.
stateless filtering cannot tell them apart; stateful can.၅ မိနစ် စမ်းကြည့်
RULES ထဲမှာ rule 2 ကို rule 3 အပေါ်ဆီ ရွှေ့ပြီး port 22 ကို 'any' လို့ ပြောင်းလိုက်ပါ — ဘယ် packet တွေ အဖြေ ပြောင်းသွားလဲ ကြည့်ပါ။ ပြီးရင် rule 1 (ESTABLISHED) ကို ဖျက်ပြီး ပြန် run ကြည့်ပါ၊ stateless firewall တစ်ခုအနေနဲ့ packet 4 ကို ခွင့်ပြုဖို့ ဘယ်လို rule မျိုး ရေးရမလဲ ဆိုတာ ရေးကြည့်ပါ။
သတိလေးတစ်ချက်
ကျယ်ပြန့်တဲ့ allow rule တစ်ခုကို ပိုကျဉ်းတဲ့ deny တစ်ခုအပေါ်မှာ ထားခြင်း — first-match-wins က အဲဒီ deny ကို ရောက်လို့မရအောင် လုပ်ပစ်ပြီး ဘာ warning မှ မပေးဘူး။
အတွင်းပိုင်း network တွေမှာ DROP သုံးခြင်း — client တွေက ချက်ချင်း ကျရှုံးမယ့်အစား timeout အထိ ဆိုင်းငံ့နေရလို့ ရှင်းလင်းတဲ့ ငြင်းပယ်မှုတစ်ခုကို နှေးကွေး မရေရာတဲ့ outage အဖြစ် ပြောင်းပစ်တယ်။
Cloudflare Learning Center - What is a firewall? — Computer Networking