Docker UFW کو کیوں bypass کرتا ہے اور اسے کیسے ٹھیک کریں
Docker iptables میں DNAT قاعدہ لکھتا ہے جو UFW قواعد کو نظرانداز کرتا ہے، چناچہ denied port پھر بھی انٹرنیٹ پر جواب دیتا ہے۔ میکانزم اور دونوں حل یہاں پڑھیں۔
Docker UFW کو کیوں نظرانداز کرتا ہے
Docker UFW کو نظرانداز کرتا ہے کیونکہ شائع کردہ کنٹینر پورٹس کبھی بھی ان فائر وال قواعد سے نہیں گزرتیں جنہیں UFW منظم کرتا ہے۔ جب آپ docker run -p 8080:80 چلاتے ہیں، تو Docker کے kernel کے nat table میں PREROUTING chain میں ایک DNAT (destination network address translation) قاعدہ لکھتا ہے۔ وہ قاعدہ ہر پیکٹ کی منزل کو کنٹینر کے نجی پتے میں تبدیل کر دیتا ہے، اس سے پہلے کہ kernel فیصلہ کرے کہ پیکٹ کہاں جا رہا ہے۔ تبدیل شدہ پیکٹ کو پھر FORWARD chain کے ذریعے کنٹینر میں بھیجا جاتا ہے، جسے Docker کنٹرول کرتا ہے۔ UFW کے قواعد INPUT chain میں موجود ہیں، اور پیکٹ کبھی اس میں داخل نہیں ہوتا۔ لہذا ufw status default deny ظاہر کرتا ہے، sudo ufw deny 8080 کامیابی کی اطلاع دیتا ہے، اور port 8080 پھر بھی پورے انٹرنیٹ کا جواب دیتا ہے۔
یہ Docker کی کوئی خامی نہیں ہے، اور نہ ہی UFW خراب ہے۔ دونوں ٹولز ایک ہی kernel فائر وال کو پروگرام کرتے ہیں۔ Docker کے قواعد پیکٹ کے راستے پر صرف ایک ابتدائی نقطے پر عمل کرتے ہیں، اس لیے UFW سے کبھی پوچھا ہی نہیں جاتا۔ یہ رہنمائی اس نظراندازی کو ظاہر کرتی ہے، میکانزم کی وضاحت کرتی ہے، اور پھر ان دو حل کو بیان کرتی ہے جو کام کرتے ہیں: 127.0.0.1 پر پورٹس شائع کرنا، اور DOCKER-USER chain میں فلٹرنگ کرنا۔ اگر UFW خود آپ کے لیے نیا ہے، تو پہلے اسے UFW فائر وال بنیادی رہنمائی کے ساتھ ترتیب دیں، کیونکہ ایک default-deny فائر وال سرور پر ہر چیز کے لیے صحیح بنیاد ہے۔
اپنے سرور پر بائی پاس کو دیکھیں
ایک ایسے VPS سے شروع کریں جہاں UFW فعال ہو اور آنے والی ٹریفک کے لیے ڈیفالٹ ڈینائی پالیسی لاگو ہو۔ ایک ویب کنٹینر کو شائع شدہ پورٹ کے ساتھ چلائیں:
sudo ufw status verbose
docker run -d --name web -p 8080:80 nginx:1.29-alpineufw status verbose میں Default: deny (incoming), allow (outgoing) دکھاتا ہے اور پورٹ 8080 کے لیے کوئی رول نہیں ہے۔ فائر وال کی اپنی رپورٹ کے مطابق یہ پورٹ بند ہے۔ اب کسی دوسری مشین سے ٹیسٹ کریں، سرور خود سے نہیں:
curl -I http://your-vps-ip:8080/HTTP/1.1 200 OKکنٹینر جواب دیتا ہے۔ ایک واضح ڈینائی رول شامل کریں اور دوبارہ ٹیسٹ کریں:
sudo ufw deny 8080/tcpپورٹ اب بھی جواب دیتی ہے، کیونکہ ڈینائی رول ایک ایسی چین میں ہے جس میں پیکیٹ کبھی نہیں جاتا۔ UFW ناکام نہیں ہوا۔ اس سے کبھی مشورہ نہیں کیا گیا۔ یہی وجہ ہے کہ یہ مسئلہ اتنی اچھی طرح چھپا رہتا ہے: کہیں کوئی ایرر نہیں چھپتا، ڈپلائے کام کرتا ہے، اور فائر وال کی اسٹیٹس آؤٹ پٹ بالکل ایک صحت مند، محفوظ سرور جیسی دکھتی ہے۔
طریقہ کار: PREROUTING، INPUT سے پہلے چلتا ہے
کرنل ایک آنے والے پیکیٹ کو مقررہ ترتیب میں پروسیس کرتا ہے۔ پورا مسئلہ اسی ترتیب میں چھپا ہے۔
PREROUTINGسب سے پہلے چلتا ہے۔ یہاں کے قواعد پیکیٹ کی منزل کو تبدیل کر سکتے ہیں۔ Docker کا شائع شدہ پورٹ کا قاعدہ بالکل یہی کرتا ہے۔- اس کے بعد روٹنگ کا فیصلہ آتا ہے۔ میزبان کے لیے بھیجا گیا پیکیٹ
INPUTچین میں جاتا ہے۔ کسی اور مشین کے لیے بھیجا گیا پیکیٹFORWARDچین میں جاتا ہے۔ - UFW کے قواعد
INPUTمیں موجود ہیں۔ Docker کے قواعدFORWARDمیں موجود ہیں۔
Docker کے اس قاعدے کو دیکھیں جو آپ نے ابھی شروع کیا ہے:
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 لائن پوری کہانی ہے۔ پورٹ 8080 کے لیے آنے والا کوئی بھی پیکیٹ اپنی منزل سے 172.17.0.2:80 میں تبدیل ہو جاتا ہے، جو کہ Docker کے پرائیویٹ برج نیٹ ورک پر کنٹینر کا پتہ ہے۔ تبدیلی کے بعد پیکیٹ اب میزبان کے نام نہیں رہتا۔ لہٰذا روٹنگ فیصلہ اسے FORWARD راستے پر بھیج دیتا ہے، جہاں Docker پہلے ہی اپنے نیٹ ورکس میں آنے والی ٹریفک کو قبول کرنے کے قواعد شامل کر چکا ہوتا ہے۔ آپ کا deny 8080/tcp قاعدہ INPUT میں اس پیکیٹ کا انتظار کرتا رہتا ہے جو کبھی نہیں آتا۔
Ubuntu 24.04 پر iptables کمانڈ nftables کا فرنٹ اینڈ ہے۔ لیکن چین کی ترتیب اور نتیجہ بالکل یکساں ہے۔ UFW اور Docker دونوں ایک ہی کرنل پیکیٹ پائپ لائن میں لکھتے ہیں۔ Docker کا داخلہ مقام پہلے ہے۔
روزمرہ کا حل: ports کو 127.0.0.1 پر شائع کریں
زیادہ تر containers کو شروع سے ہی عوامی بننے کی ضرورت نہیں تھی۔ ایک database، reverse proxy کے پیچھے app server، ایک admin panel، ایک metrics endpoint: ان میں سے کوئی بھی براہ راست انٹرنیٹ کا جواب نہیں دینا چاہیے۔ انہیں loopback address پر شائع کریں:
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 اب صرف 127.0.0.1 کو بھیجے گئے packets سے میل کھاتا ہے، اور انٹرنیٹ سے آنے والا packet کبھی بھی قانونی طور پر وہ destination نہیں رکھ سکتا، اس لیے kernel اسے کسی بھی firewall rule کے چلنے سے پہلے drop کر دیتا ہے۔ port host سے قابل رسائی ہے، اور کسی اور چیز سے نہیں۔ binding کی تصدیق کریں:
sudo ss -tlnp | grep 8080آپ کو output میں 127.0.0.1:8080 چاہیے، نہ کہ 0.0.0.0:8080 یا [::]:8080۔ پھر کسی دوسری مشین سے تصدیق کریں کہ curl http://your-vps-ip:8080/ مسترد کر دیا گیا ہے۔
ان خدمات کے لیے جہاں انٹرنیٹ کا سامنا کرنا چاہیے، ایک reverse proxy چلائیں جو ports 80 اور 443 کا مالک ہو اور hostname کے لحاظ سے route کرے، اور اس کے علاوہ کچھ بھی شائع نہ کریں۔ یہ وہی پیٹرن ہے جو Traefik reverse proxy guide بناتا ہے، اور اسی طرح ایک self-hosted app جیسے Nextcloud on a VPS اپنے proxy کے بغیر ناقابل رسائی رہتا ہے۔ ports: entries کو کیسے declare کیا جاتا ہے، اور باقی Compose workflow، اسے the Docker Compose basics guide میں بیان کیا گیا ہے۔
ہر internal container کو loopback پر رکھنے کے ساتھ، UFW دوبارہ اپنا معمول کا کام کرنے لگتا ہے: ان ports کی حفاظت کرنا جو خود host پیش کرتا ہے۔ یہ rule set یہاں بنائیں، پھر commands کو ترتیب سے چلائیں:
حقیقی فلٹرنگ: DOCKER-USER چین
بعض اوقات ایک کنٹینر پورٹ کو نیٹ ورک پر شائع شدہ رکھنا ضروری ہوتا ہے لیکن اس تک رسائی محدود ہونی چاہیے۔ مثال کے طور پر، ایک ڈیٹا بیس ریپلیکا پورٹ جس تک صرف ایک دفتر کا پتہ رسائی حاصل کر سکے۔ اس کے لیے، Docker DOCKER-USER چین فراہم کرتا ہے۔ کسی بھی کنٹینر کی طرف جانے والا ہر پیکٹ Docker کے اپنے قبول کرنے کے قوانین سے پہلے DOCKER-USER سے گزرتا ہے، اور Docker اس میں کبھی قوانین نہیں لکھتا۔ یہ چین آپ کے لیے موجود ہے، اور Docker ڈیمن دوبارہ شروع ہونے پر اس کے مواد کو تبدیل نہیں کرتا۔
کمانڈ سے پہلے ایک اہم نکتہ: جب پیکٹ DOCKER-USER تک پہنچتا ہے، DNAT تبدیلی پہلے ہی ہو چکی ہوتی ہے۔ پیکٹ کی منزل پورٹ کنٹینر پورٹ ہوتی ہے (ہماری مثال میں 80)، شائع شدہ پورٹ نہیں (8080)۔ لہٰذا --dport 8080 سے ملنے والا کوئی بھی قاعدہ کچھ نہیں ملاتا۔ قابل اعتماد طریقہ یہ ہے کہ اس پورٹ سے ملایا جائے جسے کلائنٹ نے اصل میں کنکٹ کیا تھا، جسے کرنل کا کنکشن ٹریکر یاد رکھتا ہے:
sudo iptables -I DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 8080 --ctdir ORIGINAL ! -s 10.0.0.10 -j DROPاسے یوں پڑھیں: جو پیکٹ eth0 پر داخل ہوئے ہوں اور ایک ایسے کنکشن سے تعلق رکھتے ہوں جس کی اصل منزل پورٹ 8080 تھی، ان میں سے 10.0.0.10 سے بھیجے گئے کے علاوہ باقی سب کو ڈراپ کر دیں۔ --ctdir ORIGINAL میچ اس قاعدے کو کلائنٹ سے کنٹینر کی سمت تک محدود کرتا ہے، تاکہ جوابی پیکٹس غلطی سے نہ پکڑے جائیں۔ eth0 کو اپنے پبلک انٹرفیس سے تبدیل کریں؛ ip route | grep default اس کا نام بتاتا ہے۔ اسے پہلے کی طرح ٹیسٹ کریں: اجازت شدہ پتے سے curl کامیاب ہوتا ہے، اور کسی اور جگہ سے کنکشن ٹائم آؤٹ ہو جاتا ہے۔
iptables کمانڈ سے شامل کردہ قواعد ریبوٹ پر غائب ہو جاتے ہیں۔ چونکہ UFW پہلے ہی اس فائر وال کو منظم کر رہا ہے، انہیں محفوظ رکھنے کی بہترین جگہ /etc/ufw/after.rules ہے۔ فائل کے آخر میں ایک بلاک شامل کریں:
*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 ہر ری لوڈ اور ہر بوٹ پر اس فائل کو دوبارہ لاگو کرتا ہے، لہٰذا آپ کی کنٹینر فلٹرنگ اب آپ کے باقی فائر وال کے ساتھ ایک ہی جگہ پر موجود ہے، اور یہ ریبوٹ اور Docker اپ گریڈ دونوں سے بچ جاتی ہے۔
آپ کو Docker کی iptables انٹیگریشن کو غیر فعال نہیں کرنا چاہیے
اس مسئلے کے پرانے جوابات { "iptables": false } کو /etc/docker/daemon.json میں سیٹ کرنے کی تجویز دیتے ہیں۔ ایسا نہ کریں۔ Docker کے فائر وال قواعد پورٹس شائع کرنے سے کہیں زیادہ کام کرتے ہیں۔ مسکیورڑ (masquerade) قاعدہ ہی کنٹینرز کو میزبان کے پتے کے ذریعے باہر انٹرنیٹ تک رسائی دیتا ہے، لہذا انٹیگریشن بند ہونے پر کنٹینرز امیجز نہیں کھینچ سکتے، پیکیج مررز تک نہیں پہنچ سکتے، یا کوئی بیرونی API (ایپلیکیشن پروگرامنگ انٹرفیس) کو کال نہیں کر سکتے۔ DNAT قواعد ہی -p کو بالکل کام کرنے دیتے ہیں، لہذا شائع شدہ پورٹس مکمل طور پر کام کرنا بند کر دیتی ہیں۔ الگ الگ Compose نیٹ ورکس کو جدا رکھنے والے آئسولیشن قواعد بھی ختم ہو جاتے ہیں۔ آپ بائی پاس کو ٹھیک اس طرح کرتے ہیں کہ کنٹینر نیٹ ورکنگ توڑ دیتے ہیں، اور ان میں سے ہر قاعدہ کو خود لکھ کر برقرار رکھنا آپ کی ذمہ داری بن جاتا ہے۔ Docker کی اپنی دستاویزات اس سیٹنگ کو بالکل یہی کرنے والے لوگوں کے لیے بیان کرتی ہے۔ DOCKER-USER چین اسی لیے موجود ہے تاکہ کسی کو اس سوئچ کی ضرورت نہ رہے۔
اسی مسئلے کا IPv6 پہلو
پہلے چیک کریں کہ شائع شدہ پورٹ IPv6 پر کیسی نظر آتی ہے:
sudo ss -tlnp | grep 8080Docker Engine 27 سے، Docker بطورِ پیش فرض ip6tables کا انتظام کرتا ہے۔ IPv6 فعال Docker نیٹ ورک پر، شائع شدہ پورٹ IPv6 ٹیبلز میں بھی وہی DNAT عمل سے گزرتی ہے۔ اس لیے وہی بائی پاس وہاں بھی موجود ہے اور وہی اصلاح لاگو ہوتی ہے: DOCKER-USER چین ip6tables میں بھی موجود ہے۔ اس لیے اپنے رول کو sudo ip6tables -I DOCKER-USER ... سے بھی عکس بنا دیں اور باہر سے curl کے ذریعے اپنے سرور کے عوامی IPv6 پتے کے خلاف ٹیسٹ کریں، مثلاً curl -6 http://[2001:db8:2a::1]:8080/۔
IPv6 کے بغیر نیٹ ورک پر، IPv6 کلائنٹس کو بجائے اس کے docker-proxy سنبھال لیتا ہے۔ یہ ایک عام یوزر اسپیس پروسیس ہے جو [::]:8080 پر سنتی ہے اور ٹریفک کو IPv4 کے ذریعے کنٹینر میں فارورڈ کرتا ہے۔ ہوسٹ پروسیس کی طرف ٹریفک واقعی INPUT سے گزرتا ہے، اس لیے UFW اس راستے کو فلٹر کر سکتا ہے، لیکن صرف اس وقت جب UFW بالکل بھی IPv6 کا انتظام کر رہا ہو۔ یہ کہ وہ کر رہا ہے یا نہیں، اور VPS پر IPv6 کا فرق کھلنے کے دیگر طریقے، UFW اور IPv6 گائیڈ کا موضوع ہیں۔
لوپ بیک پر شائع کرنا پورے سوال کو نظر انداز کر دیتا ہے: -p 127.0.0.1:8080:80 صرف IPv4 لوپ بیک سے بائنڈ ہوتا ہے، اس لیے کوئی IPv6 لسنر نہیں ہوتا اور نہ ہی کسی اسٹیک پر باہر سے پہنچنے کے لیے کچھ ہوتا ہے۔
وہ پیٹرن جو قائم رہتا ہے
- ہر اندرونی پورٹ کو
127.0.0.1پر شائع کریں، تاکہ وہ پہلے ہی کبھی بے نقاب نہ ہو۔ - عوامی طرف ایک ریورس پراکسی کو دیں جو پورٹس 80 اور 443 کا مالک ہو۔
- ہوسٹ کے لیے UFW کی ڈیفالٹ ڈینائی رکھیں، SSH اور پراکسی پورٹس کی اجازت دیں۔
- واقعی عوامی کنٹینر پورٹس کو
DOCKER-USERمیں فلٹر کریں، جو اصل منزل پورٹ سے ملائی گئی ہوں، اور/etc/ufw/after.rulesمیں محفوظ کی گئی ہوں۔ - Docker کا iptables انٹیگریشن آن چھوڑیں۔
اسے ایک بار سیٹ اپ کرنے سے یہ غیر متوقع صورتیں ختم ہو جاتی ہیں: ufw status ہوسٹ کو بیان کرتا ہے، اور DOCKER-USER کنٹینرز کو بیان کرتا ہے۔ کچھ بھی غلطی سے شائع نہیں ہوتا، اور آپ جو اگلا docker run -p ٹائپ کرتے ہیں وہ بالکل وہی بے نقاب کرتا ہے جس کی آپ نے intending کی تھی۔
FAQ
جب UFW پورٹ کو بلاک کرتا ہے تو میں اپنے Docker کنٹینر تک کیسے پہنچ سکتا ہوں؟
اس لیے کہ Docker پورٹ کو PREROUTING چین میں ایک DNAT رول کے ساتھ شائع کرتا ہے، جو کسی بھی فلٹرنگ سے پہلے پیکٹ کی منزل کو کنٹینر کے پتے پر تبدیل کر دیتا ہے۔ پیکٹ پھر FORWARD پاتھ سے گزرتا ہے، اور UFW کے رولز INPUT میں موجود ہوتے ہیں، جو ایسا چین ہے جس میں پیکٹ کبھی داخل نہیں ہوتا۔ فائر وال سے کبھی رابطہ نہیں ہوتا، اس لیے اس کے انکار کے رولز شائع شدہ کنٹینر پورٹس پر کوئی اثر نہیں رکھتے۔
میں UFW کو Docker کی شائع شدہ پورٹس کو بلاک کرنے کے لیے کیسے کہوں؟
UFW خود نہیں کر سکتا، کیونکہ اس کے رولز غلط چین میں ہیں۔ یا تو پورٹ کو ظاہر کرنا بند کریں، اسے 127.0.0.1:8080:80 کے طور پر شائع کر کے تاکہ صرف ہوسٹ تک پہنچ سکے، یا DOCKER-USER چین میں ایک iptables رول کے ساتھ فلٹر کریں جو conntrack کے ذریعے اصل منزل پورٹ سے میل کھاتا ہو۔ اس رول کو /etc/ufw/after.rules میں محفوظ کریں تاکہ یہ ریبوٹ اور ufw reload میں برقرار رہے۔
کیا مجھے Docker کی daemon.json میں "iptables": false سیٹ کرنا چاہیے؟
نہیں۔ یہ سیٹنگ Docker کے تمام فائر وال اور NAT رولز ہٹا دیتی ہے، جو بائی پاس سے کہیں زیادہ چیزوں کو خراب کرتا ہے۔ کنٹینرز باہر انٹرنیٹ تک رسائی کھو دیتے ہیں کیونکہ masquerade رول ختم ہو جاتا ہے، اور شائع شدہ پورٹس کام کرنا بند کر دیتی ہیں کیونکہ DNAT رولز ختم ہو جاتے ہیں۔ اس کے بجائے loopback شائع کرنے اور DOCKER-USER چین کا استعمال کریں؛ یہ کنٹینر نیٹ ورکنگ کو خراب کیے بغیر نمائش کو ٹھیک کرتے ہیں۔
کیا Docker IPv6 پر بھی UFW کو بائی پاس کرتا ہے؟
Docker Engine 27 اور اس کے بعد کے ورژنز میں، ip6tables مینجمنٹ بطور ڈیفالٹ فعال ہے، اس لیے IPv6-enabled Docker نیٹ ورک پر شائع کردہ پورٹ کو UFW کے گرد بالکل IPv4 کی طرح تبدیل کر دیا جاتا ہے، اور اسے DOCKER-USER رول کو ip6tables کے ساتھ عکس بند کرنے کی ضرورت ہوتی ہے۔ IPv6 کے بغیر نیٹ ورکس پر، docker-proxy پروسیس [::] پر سنتی ہے اور وہ ٹریفک INPUT سے گزرتا ہے، جہاں UFW اسے فلٹر کر سکتا ہے اگر UFW IPv6 کو مینج کرتا ہے۔ 127.0.0.1 پر شائع کرنا دونوں صورتحال سے بچتا ہے، کیونکہ IPv6 پر کچھ بھی نہیں سنتا۔