Docker UFW کو bypass کیوں کرتا ہے اور حل کیا ہے؟
Docker کے iptables rules UFW کو bypass کرتے ہیں، اس لیے deny کیا گیا port 8080 بھی internet سے جواب دیتا ہے۔ وجہ سمجھیں اور 2 مؤثر fixes دیکھیں۔
Docker، UFW کو کیوں bypass کرتا ہے
Docker، UFW کو bypass کرتا ہے کیونکہ container کے published ports کبھی بھی ان firewall rules سے نہیں گزرتے جنہیں UFW manage کرتا ہے۔ جب آپ docker run -p 8080:80 چلاتے ہیں تو Docker، kernel کی nat table کے PREROUTING chain میں DNAT (destination network address translation) rule لکھ دیتا ہے۔ یہ rule kernel کے packet کی منزل کا فیصلہ کرنے سے پہلے ہر packet کی destination کو container کے private address سے تبدیل کر دیتا ہے۔ اس کے بعد تبدیل شدہ packet FORWARD chain کے ذریعے container میں forward ہو جاتا ہے، جسے Docker control کرتا ہے۔ UFW کے rules INPUT chain میں موجود ہوتے ہیں، اور packet اس chain میں داخل ہی نہیں ہوتا۔ اس لیے ufw status default deny دکھاتا ہے، sudo ufw deny 8080 کامیابی report کرتا ہے، اور port 8080 پھر بھی پورے internet کو جواب دیتا ہے۔
یہ Docker کا bug نہیں ہے، اور UFW بھی خراب نہیں ہے۔ دونوں tools اسی kernel firewall کو configure کرتے ہیں۔ Docker کے rules packet کے راستے میں پہلے ہی apply ہو جاتے ہیں، اس لیے UFW سے کوئی فیصلہ طلب نہیں کیا جاتا۔ یہ guide اس bypass کا عملی مظاہرہ کرتی ہے، mechanism کی وضاحت کرتی ہے، اور پھر کام کرنے والے دو fixes کا احاطہ کرتی ہے: ports کو 127.0.0.1 پر publish کرنا، اور DOCKER-USER chain میں traffic filter کرنا۔ اگر UFW آپ کے لیے نیا ہے تو پہلے UFW firewall کی بنیادی باتوں کی guide کے ذریعے اسے configure کریں، کیونکہ default-deny firewall اب بھی سرور کی باقی تمام سروسز کے لیے درست بنیادی تحفظ ہے۔
اپنے سرور پر بائی پاس دیکھیں
ایسے VPS سے شروع کریں جہاں UFW فعال ہو اور incoming traffic کے لیے default deny policy مقرر ہو۔ شائع شدہ port کے ساتھ ایک web container چلائیں:
sudo ufw status verbose
docker run -d --name web -p 8080:80 nginx:1.29-alpineufw status verbose میں Default: deny (incoming), allow (outgoing) دکھائی دیتا ہے اور port 8080 کے لیے کوئی rule موجود نہیں۔ firewall کی اپنی رپورٹ کے مطابق یہ port بند ہے۔ اب کسی دوسرے machine سے test کریں، خود server سے نہیں:
curl -I http://your-vps-ip:8080/HTTP/1.1 200 OKcontainer جواب دیتا ہے۔ اب ایک واضح deny rule شامل کریں اور دوبارہ test کریں:
sudo ufw deny 8080/tcpport اب بھی جواب دیتا ہے، کیونکہ deny rule ایسی chain میں موجود ہے جہاں packet کبھی پہنچتا ہی نہیں۔ UFW ناکام نہیں ہوا۔ اس سے کبھی مشورہ ہی نہیں لیا گیا۔ یہی وجہ ہے کہ یہ مسئلہ اتنی آسانی سے چھپ جاتا ہے: کہیں بھی کوئی error ظاہر نہیں ہوتا، deploy کامیاب رہتا ہے، اور firewall status output بالکل ایسے locked-down server کی طرح دکھائی دیتا ہے جس کی security درست ہو۔
طریقۂ کار: PREROUTING، INPUT سے پہلے چلتا ہے
Kernel آنے والے packet کو ایک مقررہ ترتیب سے process کرتا ہے، اور پورا مسئلہ اسی ترتیب میں ہے۔
PREROUTINGپہلے چلتا ہے۔ یہاں موجود rules packet کی destination تبدیل کر سکتے ہیں، اور published port کے لیے Docker کا rule بالکل یہی کرتا ہے۔- اس کے بعد routing decision ہوتا ہے۔ Host کو address کیا گیا packet
INPUTchain میں جاتا ہے۔ کسی دوسرے machine کو address کیا گیا packetFORWARDchain میں جاتا ہے۔ - UFW کے rules
INPUTمیں موجود ہیں۔ Docker کے rulesFORWARDمیں موجود ہیں۔
ابھی شروع کیے گئے container کے لیے Docker کا rule دیکھیں:
sudo iptables -t nat -L DOCKER -nChain DOCKER (2 references)
target prot opt source destination
RETURN 0 -- 0.0.0.0/0 0.0.0.0/0
DNAT 6 -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80DNAT والی line پوری بات واضح کرتی ہے۔ Port 8080 کے لیے آنے والے ہر packet کی destination کو 172.17.0.2:80 میں تبدیل کر دیا جاتا ہے، جو Docker کے private bridge network پر container کا address ہے۔ تبدیلی کے بعد packet اب host کو address نہیں کر رہا ہوتا، اس لیے routing decision اسے FORWARD والے راستے پر بھیج دیتا ہے۔ وہاں Docker پہلے ہی اپنی networks میں traffic قبول کرنے والے rules شامل کر چکا ہوتا ہے۔ آپ کا deny 8080/tcp rule INPUT میں ایسے packet کا انتظار کرتا رہتا ہے جو کبھی نہیں آتا۔
Ubuntu 24.04 پر iptables command، nftables کے لیے front end ہے، لیکن chain order اور نتیجہ یکساں رہتے ہیں۔ UFW اور Docker دونوں ایک ہی kernel packet pipeline میں rules شامل کرتے ہیں، اور Docker کا entry point پہلے آتا ہے۔ یہ مسئلہ صرف UFW تک محدود نہیں ہے: Rocky یا AlmaLinux VPS پر firewalld اسی pipeline کے مقام پر filtering کرتا ہے اور اسی DNAT rule سے bypass ہو جاتا ہے، اس لیے ذیل کے fixes وہاں بھی استعمال کیے جاتے ہیں۔
روزمرہ حل: ports کو 127.0.0.1 پر publish کریں
زیادہ تر containers کو ابتدا ہی سے public ہونے کی ضرورت نہیں تھی۔ Database، reverse proxy کے پیچھے موجود app server، admin panel، اور metrics endpoint میں سے کسی کو بھی براہِ راست internet کو جواب نہیں دینا چاہیے۔ انہیں loopback address پر publish کریں:
docker run -d --name web -p 127.0.0.1:8080:80 nginx:1.29-alpineیا Compose file میں:
services:
web:
image: nginx:1.29-alpine
ports:
- "127.0.0.1:8080:80"یہ اس لیے کام کرتا ہے کہ Docker کا DNAT rule اب صرف ان packets پر لاگو ہوتا ہے جن کا destination 127.0.0.1 ہو۔ Internet سے آنے والا packet قانونی طور پر یہ destination کبھی نہیں رکھ سکتا، اس لیے kernel کسی firewall rule کے چلنے سے پہلے ہی اسے drop کر دیتا ہے۔ Port host سے قابلِ رسائی رہتا ہے، اور کسی دوسری جگہ سے نہیں۔ Binding کی تصدیق کریں:
sudo ss -tlnp | grep 8080Output میں 127.0.0.1:8080 ہونا چاہیے، 0.0.0.0:8080 یا [::]:8080 نہیں۔ پھر کسی دوسری machine سے تصدیق کریں کہ curl http://your-vps-ip:8080/ کو connection refused ملتا ہے۔
جن services کو internet کے سامنے ہونا چاہیے، ان کے لیے ایک reverse proxy چلائیں جو ports 80 اور 443 کا مالک ہو اور hostname کے مطابق traffic route کرے۔ اس کے علاوہ کوئی port publish نہ کریں۔ یہی pattern Traefik reverse proxy guide میں استعمال کیا گیا ہے۔ اسی طرح VPS پر Nextcloud جیسی self-hosted app اپنے proxy کے علاوہ کسی ذریعے سے قابلِ رسائی نہیں رہتی۔ ports: entries کیسے declare کی جاتی ہیں، اور Compose workflow کے باقی مراحل، Docker Compose basics guide میں بیان کیے گئے ہیں۔
جب ہر internal container loopback پر ہو تو UFW دوبارہ اپنا معمول کا کام کرتا ہے: host کی جانب سے فراہم کیے جانے والے ports کی حفاظت۔ یہاں rule set بنائیں، پھر commands کو ترتیب سے چلائیں:
حقیقی filtering: DOCKER-USER chain
کبھی کبھی container port کو network پر published رکھنا ضروری ہوتا ہے، لیکن اس تک رسائی محدود ہونی چاہیے۔ مثال کے طور پر database replica port تک صرف ایک office address کو رسائی دی جا سکتی ہے۔ اس مقصد کے لیے Docker، DOCKER-USER chain فراہم کرتا ہے۔ ہر packet جو کسی بھی container کی طرف جا رہا ہو، Docker کے اپنے accept rules سے پہلے DOCKER-USER سے گزرتا ہے، اور Docker اس chain میں کبھی rules نہیں لکھتا۔ یہ chain آپ کے rules کے لیے موجود ہے، اور Docker daemon restart ہونے کے باوجود اس کے contents میں تبدیلی نہیں کرتا۔
Command چلانے سے پہلے ایک اہم نکتہ سمجھیں: packet جب DOCKER-USER تک پہنچتا ہے تو DNAT rewrite پہلے ہی ہو چکی ہوتی ہے۔ Packet کا destination port published port (8080) کے بجائے container port (80، ہماری مثال میں) ہوتا ہے۔ اس لیے --dport 8080 سے match کرنے والا rule کسی packet پر لاگو نہیں ہوگا۔ قابلِ اعتماد طریقہ یہ ہے کہ client نے اصل میں جس port سے رابطہ کیا تھا، اسے match کیا جائے۔ kernel کا connection tracker اس معلومات کو محفوظ رکھتا ہے:
sudo iptables -I DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 8080 --ctdir ORIGINAL ! -s 10.0.0.10 -j DROPاس کا مطلب ہے: وہ packets جو eth0 سے داخل ہوئے ہوں اور ایسی connection کا حصہ ہوں جس کا original destination port 8080 تھا، لیکن جو 10.0.0.10 سے نہ بھیجے گئے ہوں، انہیں drop کر دیں۔ --ctdir ORIGINAL match rule کو صرف client-to-container direction تک محدود کرتا ہے، اس لیے reply packets غلطی سے اس rule کی زد میں نہیں آتے۔ eth0 کو اپنے public interface کے نام سے تبدیل کریں؛ ip route | grep default اس کا نام دکھاتا ہے۔ پہلے کی طرح test کریں: اجازت یافتہ address سے curl کامیاب ہوگا، جبکہ کسی اور جگہ سے connection timeout ہو جائے گی۔ یہ تعطل اس بات کی علامت ہے کہ DROP rule درست کام کر رہا ہے، نہ کہ اس port کے پیچھے کوئی service موجود نہیں۔ refused connection اور timeout ہونے والی connection میں فرق یہ معلوم کرنے کا تیز ترین طریقہ ہے کہ port filtered ہے یا service صرف listening نہیں کر رہی۔
iptables command سے شامل کیے گئے rules reboot کے بعد ختم ہو جاتے ہیں۔ چونکہ UFW پہلے ہی اس firewall کو manage کرتا ہے، اس لیے انہیں مستقل رکھنے کی درست جگہ /etc/ufw/after.rules ہے۔ File کے آخر میں ایک block شامل کریں:
*filter
:DOCKER-USER - [0:0]
-A DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 8080 --ctdir ORIGINAL ! -s 10.0.0.10 -j DROP
COMMITاس کے بعد sudo ufw reload چلائیں۔ UFW ہر reload اور ہر boot پر اس file کو دوبارہ apply کرتا ہے۔ یوں container filtering، firewall کے باقی rules کے ساتھ اسی جگہ manage ہوتی ہے، اور reboot یا Docker upgrade دونوں کے بعد برقرار رہتی ہے۔
Docker کے iptables integration کو disable کیوں نہیں کرنا چاہیے
اس مسئلے کے بارے میں پرانے جوابات { "iptables": false } کو /etc/docker/daemon.json میں set کرنے کا مشورہ دیتے ہیں۔ ایسا نہ کریں۔ Docker کے firewall rules صرف ports publish کرنے تک محدود نہیں ہوتے۔ masquerade rule containers کو host کے address کے ذریعے outbound internet access فراہم کرتا ہے۔ اس integration کے بند ہونے پر containers images pull نہیں کر سکتے، package mirrors تک نہیں پہنچ سکتے، اور کسی external API (application programming interface) کو call نہیں کر سکتے۔ DNAT rules ہی -p کو کام کرنے کے قابل بناتے ہیں، اس لیے published ports مکمل طور پر کام کرنا بند کر دیتے ہیں۔ الگ Compose networks کو ایک دوسرے سے الگ رکھنے والے isolation rules بھی ختم ہو جاتے ہیں۔ bypass کو درست کرنے کے لیے آپ container networking توڑ دیں گے، اور ان تمام rules کو خود لکھنا اور دستی طور پر maintain کرنا پڑے گا۔ Docker کی اپنی documentation میں اس setting کو ایسے صارفین کے لیے بتایا گیا ہے جو جان بوجھ کر یہی کرنا چاہتے ہیں۔ DOCKER-USER chain اسی لیے موجود ہے کہ کسی کو یہ switch استعمال کرنے کی ضرورت نہ پڑے۔
اسی مسئلے کا IPv6 پہلو
پہلے دیکھیں کہ IPv6 پر published port کی صورت کیا ہے:
sudo ss -tlnp | grep 8080Docker Engine 27 کے بعد Docker بطور ڈیفالٹ ip6tables manage کرتا ہے۔ IPv6 enabled والے Docker network پر published port کو IPv6 tables میں بھی وہی DNAT treatment ملتا ہے۔ اس لیے وہاں بھی یہی bypass موجود ہوتا ہے اور یہی fix لاگو ہوتی ہے: DOCKER-USER chain ip6tables میں بھی موجود ہوتی ہے، لہٰذا اپنی rule کو sudo ip6tables -I DOCKER-USER ... کے ساتھ mirror کریں اور باہر سے اپنے server کے public IPv6 address پر curl چلا کر test کریں، مثلاً curl -6 http://[2001:db8:2a::1]:8080/۔
IPv6 کے بغیر network پر IPv6 clients کو اس کے بجائے docker-proxy handle کرتا ہے۔ یہ ایک عام user-space process ہے جو [::]:8080 پر listen کرتا ہے اور traffic کو IPv4 کے ذریعے container میں forward کرتا ہے۔ Host process تک جانے والا traffic INPUT سے ضرور گزرتا ہے، اس لیے UFW اس path کو filter کر سکتا ہے، لیکن صرف اس وقت جب UFW خود IPv6 manage کر رہا ہو۔ آیا یہ فعال ہے یا نہیں، اور VPS پر IPv6 gap پیدا ہونے کے دیگر طریقے، UFW اور IPv6 guide میں بیان کیے گئے ہیں۔
Loopback پر publishing کرنے سے یہ پورا سوال ختم ہو جاتا ہے: -p 127.0.0.1:8080:80 صرف IPv4 loopback پر bind کرتا ہے، اس لیے کوئی IPv6 listener موجود نہیں ہوتا اور باہر سے کسی بھی stack کے ذریعے وہاں رسائی ممکن نہیں ہوتی۔
وہ طریقہ جو قابلِ اعتماد رہتا ہے
- ہر داخلی port کو
127.0.0.1پر publish کریں تاکہ وہ ابتدا ہی سے public exposure کا شکار نہ ہو۔ - public رسائی ایک ہی reverse proxy کے حوالے کریں، جو ports 80 اور 443 کا مالک ہو۔
- host کے لیے UFW کا default deny برقرار رکھیں، اور SSH اور proxy ports کی اجازت دیں۔
- حقیقتاً public container ports کو
DOCKER-USERمیں filter کریں، جہاں matching اصل destination port کے مطابق ہو، اور اسے/etc/ufw/after.rulesمیں persist کریں۔ - Docker کی iptables integration فعال رکھیں۔
ایک مرتبہ setup مکمل ہونے کے بعد غیر متوقع صورتِ حال ختم ہو جاتی ہے: ufw status host کی وضاحت کرتا ہے، جبکہ DOCKER-USER containers کی وضاحت کرتا ہے۔ کوئی چیز حادثاتی طور پر publish نہیں ہوتی، اور اگلا docker run -p جو آپ لکھیں گے، صرف وہی چیز expose کرے گا جس کا آپ نے ارادہ کیا ہو۔
FAQ
میں اپنے Docker container تک کیسے پہنچ سکتا ہوں جبکہ UFW اس port کو block کرتا ہے؟
کیونکہ Docker، PREROUTING chain میں DNAT rule کے ذریعے port publish کرتا ہے۔ یہ rule filtering شروع ہونے سے پہلے packet کی destination کو container کے address سے تبدیل کر دیتا ہے۔ اس کے بعد packet FORWARD path سے گزرتا ہے، جبکہ UFW کے rules INPUT میں موجود ہوتے ہیں۔ packet اس chain میں داخل ہی نہیں ہوتا۔ اس لیے firewall سے مشاورت نہیں ہوتی اور published container ports پر اس کے deny rules کا کوئی اثر نہیں پڑتا۔
میں UFW سے Docker کے published ports کو کیسے block کر سکتا ہوں؟
UFW خود ایسا نہیں کر سکتا، کیونکہ اس کے rules غلط chain میں موجود ہیں۔ یا تو port expose کرنا بند کریں اور اسے 127.0.0.1:8080:80 کے طور پر publish کریں، تاکہ صرف host اس تک پہنچ سکے، یا DOCKER-USER chain میں ایسا iptables rule لگائیں جو conntrack کے ذریعے original destination port کو match کرے۔ اس rule کو /etc/ufw/after.rules میں مستقل محفوظ کریں، تاکہ reboot اور ufw reload کے بعد بھی برقرار رہے۔
کیا مجھے Docker کے daemon.json میں "iptables": false سیٹ کرنا چاہیے؟
نہیں۔ یہ setting Docker کے تمام firewall اور NAT rules ہٹا دیتی ہے، جس سے bypass کے علاوہ بھی بہت کچھ متاثر ہوتا ہے۔ Containers کی outbound internet access ختم ہو جاتی ہے کیونکہ masquerade rule موجود نہیں رہتا، اور published ports کام کرنا بند کر دیتے ہیں کیونکہ DNAT rules ختم ہو جاتے ہیں۔ اس کے بجائے loopback publishing اور DOCKER-USER chain استعمال کریں۔ یہ container networking کو متاثر کیے بغیر exposure کا مسئلہ حل کرتے ہیں۔
کیا Docker IPv6 پر بھی UFW کو bypass کرتا ہے؟
Docker Engine 27 اور اس کے بعد کے versions میں ip6tables management default طور پر فعال ہوتی ہے۔ اس لیے IPv6-enabled Docker network پر publish کیا گیا port، IPv4 کی طرح UFW کے گرد rewrite ہو جاتا ہے، اور اسی DOCKER-USER rule کو ip6tables کے ساتھ mirror کرنا ضروری ہوتا ہے۔ IPv6 کے بغیر networks میں docker-proxy process، [::] پر listen کرتا ہے۔ یہ traffic INPUT سے گزرتی ہے، جہاں UFW اسے filter کر سکتا ہے، بشرطیکہ UFW IPv6 manage کر رہا ہو۔ 127.0.0.1 پر publishing دونوں صورتوں سے بچاتی ہے، کیونکہ IPv6 پر کچھ بھی listen نہیں کرتا۔