Docker UFW বাইপাস করার কারণ ও সমাধান
Docker কীভাবে iptables ব্যবহার করে UFW এর নিয়ম এড়িয়ে যায় তা জানুন। পোর্ট ৮০৮০ ব্লক করার পরও কেন রেসপন্স আসে এবং এর সঠিক সমাধান কী তা এখানে দেখুন।
কেন Docker, UFW বাইপাস করে
Docker, UFW বাইপাস করে কারণ পাবলিশ করা কন্টেইনার পোর্টগুলো UFW দ্বারা পরিচালিত firewall rules অতিক্রম করে না। আপনি যখন docker run -p 8080:80 চালান, Docker কার্নেলের nat টেবিলের PREROUTING চেইনে একটি DNAT (destination network address translation) rule লিখে দেয়। কার্নেল প্যাকেটটি কোথায় যাবে তা নির্ধারণ করার আগেই, সেই rule প্রতিটি প্যাকেটের destination কন্টেইনারের private address-এ পরিবর্তন করে দেয়। এরপর পরিবর্তিত প্যাকেটটি Docker দ্বারা নিয়ন্ত্রিত FORWARD চেইনের মাধ্যমে কন্টেইনারে ফরওয়ার্ড করা হয়। UFW-এর rules গুলো INPUT চেইনে থাকে এবং প্যাকেটটি কখনোই সেখানে প্রবেশ করে না। ফলে ufw status ডিফল্ট deny দেখায়, sudo ufw deny 8080 সফলতার রিপোর্ট দেয়, কিন্তু port 8080 তখনও পুরো ইন্টারনেট থেকে রেসপন্স পায়।
এটি Docker-এর কোনো bug নয় এবং UFW-ও ত্রুটিপূর্ণ নয়। উভয় টুলই একই kernel firewall প্রোগ্রাম করে। Docker-এর rules গুলো প্যাকেটের যাত্রাপথে আরও আগে কাজ করে, তাই UFW-কে কখনোই জিজ্ঞাসা করা হয় না। এই গাইডটি বাইপাস করার বিষয়টি প্রদর্শন করে, এর মেকানিজম ব্যাখ্যা করে এবং এরপর দুটি কার্যকর সমাধান প্রদান করে: 127.0.0.1-এ পোর্ট পাবলিশ করা এবং DOCKER-USER চেইনে ফিল্টারিং করা। আপনি যদি UFW সম্পর্কে নতুন হন, তবে প্রথমে the UFW firewall basics guide দিয়ে এটি সেটআপ করে নিন, কারণ সার্ভারের অন্যান্য সব কাজের জন্য একটি default-deny firewall থাকা প্রয়োজন।
আপনার নিজস্ব সার্ভারে বাইপাসটি দেখুন
এমন একটি VPS থেকে শুরু করুন যেখানে UFW সক্রিয় আছে এবং ইনকামিং ট্রাফিকের জন্য ডিফল্ট deny policy সেট করা আছে। একটি পাবলিশ করা পোর্টসহ একটি 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 নেই। ফায়ারওয়ালের রিপোর্ট অনুযায়ী, পোর্টটি বন্ধ আছে। এখন সার্ভার থেকে নয়, বরং অন্য একটি মেশিন থেকে পরীক্ষা করুন:
curl -I http://your-vps-ip:8080/HTTP/1.1 200 OKকন্টেইনারটি রেসপন্স দিচ্ছে। একটি explicit deny rule যোগ করুন এবং আবার পরীক্ষা করুন:
sudo ufw deny 8080/tcpপোর্টটি এখনও রেসপন্স দিচ্ছে, কারণ deny rule-টি এমন একটি chain-এ আছে যা packet কখনো স্পর্শ করে না। UFW ব্যর্থ হয়নি। এটিকে কখনো জিজ্ঞাসা করা হয়নি। এই কারণেই সমস্যাটি খুব ভালোভাবে লুকিয়ে থাকে: কোথাও কোনো error প্রিন্ট হয় না, deployment সফল হয়, এবং firewall status আউটপুট দেখতে একটি সুস্থ এবং লকড-ডাউন সার্ভারের মতোই মনে হয়।
The mechanism: PREROUTING runs before INPUT
The kernel processes an incoming packet in a fixed order, and the whole problem lives in that order.
PREROUTINGruns first. Rules here may rewrite the packet's destination, and Docker's rule for a published port does exactly that.- The routing decision comes next. A packet addressed to the host itself goes to the
INPUTchain. A packet addressed to any other machine goes to theFORWARDchain. - UFW's rules live in
INPUT. Docker's rules live inFORWARD.
Look at Docker's rule for the container you just started:
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:80The DNAT line is the whole story. Any packet arriving for port 8080 gets its destination rewritten to 172.17.0.2:80, the container's address on Docker's private bridge network. After the rewrite the packet is no longer addressed to the host, so the routing decision sends it down the FORWARD path, where Docker has already added rules that accept traffic into its own networks. Your deny 8080/tcp rule waits in INPUT for a packet that never comes.
On Ubuntu 24.04 the iptables command is a front end over nftables, but the chain order and the outcome are identical. UFW and Docker both write into the same kernel packet pipeline, and Docker's entry point is earlier.
সাধারণ সমাধান: 127.0.0.1-এ port গুলো publish করা
অধিকাংশ container-এর আসলে 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 এখন শুধুমাত্র 127.0.0.1 অ্যাড্রেসযুক্ত packet-এর সাথে match করে। internet থেকে আসা কোনো packet-এ এই destination থাকা সম্ভব নয়, তাই kernel যেকোনো firewall rule চলার আগেই সেটি drop করে দেয়। port টি শুধুমাত্র host থেকে অ্যাক্সেস করা যাবে, অন্য কিছু থেকে নয়। binding টি যাচাই করুন:
sudo ss -tlnp | grep 8080আউটপুটে আপনাকে 127.0.0.1:8080 দেখতে হবে, 0.0.0.0:8080 বা [::]:8080 নয়। এরপর অন্য একটি machine থেকে নিশ্চিত করুন যে curl http://your-vps-ip:8080/ refuse করা হচ্ছে।
যে service গুলো internet-এর জন্য প্রয়োজন, সেগুলোর জন্য একটি reverse proxy ব্যবহার করুন যা 80 এবং 443 port গুলো নিয়ন্ত্রণ করে এবং hostname অনুযায়ী route করে। এছাড়া অন্য কিছু publish করবেন না। Traefik reverse proxy guide এই প্যাটার্নটি অনুসরণ করে তৈরি করা হয়েছে। Nextcloud on a VPS এর মতো self-hosted app গুলোও শুধুমাত্র তাদের proxy এর মাধ্যমেই অ্যাক্সেসযোগ্য থাকে। ports: এন্ট্রি কীভাবে declare করতে হয় এবং Compose workflow সম্পর্কে বিস্তারিত the Docker Compose basics guide এ আলোচনা করা হয়েছে।
যখন প্রতিটি internal container loopback-এ থাকে, তখন UFW তার স্বাভাবিক কাজ করতে পারে: অর্থাৎ host নিজে যে port গুলো সার্ভিস দিচ্ছে সেগুলো রক্ষা করা। এখানে rule set টি তৈরি করুন, তারপর কমান্ডগুলো ক্রমানুসারে চালান:
Real filtering: the DOCKER-USER chain
কখনও কখনও একটি container port নেটওয়ার্কের জন্য প্রকাশ করা (published) থাকা প্রয়োজন কিন্তু সেটি সীমাবদ্ধ রাখা দরকার; উদাহরণস্বরূপ, একটি database replica port যা শুধুমাত্র একটি নির্দিষ্ট office address থেকে অ্যাক্সেস করা যাবে। এর জন্য Docker DOCKER-USER chain প্রদান করে। যেকোনো container-এ যাওয়া প্রতিটি packet Docker-এর নিজস্ব accept rules-এর আগে DOCKER-USER-এর মধ্য দিয়ে যায়, এবং Docker কখনো এতে কোনো rule লেখে না। এই chain-টি আপনার ব্যবহারের জন্য তৈরি করা হয়েছে, এবং Docker daemon restart করার পরেও এটি তার contents অপরিবর্তিত রাখে।
command ব্যবহারের আগে একটি সতর্কতা: একটি packet যখন DOCKER-USER-এ পৌঁছায়, তখন DNAT rewrite ইতিমধ্যে সম্পন্ন হয়ে যায়। Packet-এর destination port হলো container port (আমাদের উদাহরণে 80), প্রকাশিত port (8080) নয়। তাই --dport 8080-এর সাথে মিলে এমন কোনো rule কোনো কিছুর সাথেই মিলবে না। সঠিক পদ্ধতি হলো ক্লায়েন্ট শুরুতে যে port-এ সংযোগ করার চেষ্টা করেছিল তা মেলানো, যা 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এর অর্থ হলো: যে packet-গুলো eth0-এ প্রবেশ করেছে এবং এমন একটি connection-এর অংশ যার original destination port ছিল 8080, 10.0.0.10 ছাড়া বাকি সব কিছু drop করুন। --ctdir ORIGINAL match rule-টিকে শুধুমাত্র client-to-container direction-এর মধ্যে সীমাবদ্ধ রাখে, যাতে ভুলবশত reply packet-গুলো আটকে না যায়। eth0-এর পরিবর্তে আপনার public interface ব্যবহার করুন; ip route | grep default এটি চিহ্নিত করে। আগের মতোই পরীক্ষা করুন: অনুমোদিত address থেকে curl সফল হবে, কিন্তু অন্য যেকোনো স্থান থেকে connection time out হবে।
iptables command দিয়ে যোগ করা rules reboot করার পর মুছে যায়। যেহেতু UFW ইতিমধ্যে এই firewall পরিচালনা করছে, তাই এগুলো স্থায়ীভাবে সংরক্ষণ করার সঠিক স্থান হলো /etc/ufw/after.rules। ফাইলের শেষে একটি 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-এর সময় ওই ফাইলটি পুনরায় কার্যকর করে, তাই আপনার container filtering এখন আপনার firewall-এর অন্যান্য অংশের মতোই একই স্থানে থাকবে এবং এটি reboot ও Docker upgrade উভয় ক্ষেত্রেই টিকে থাকবে।
কেন আপনার Docker-এর iptables integration নিষ্ক্রিয় করা উচিত নয়
এই সমস্যার পুরনো উত্তরগুলোতে /etc/docker/daemon.json-এ { "iptables": false } সেট করার পরামর্শ দেওয়া হয়েছে। তা করবেন না। Docker-এর firewall rules শুধুমাত্র port publish করার মধ্যেই সীমাবদ্ধ নয়। Masquerade rule-এর মাধ্যমেই container-গুলো host-এর address ব্যবহার করে outbound internet access পায়। তাই এই integration বন্ধ থাকলে container-গুলো image pull করতে পারবে না, package mirrors-এ পৌঁছাতে পারবে না, অথবা কোনো external API (application programming interface) কল করতে পারবে না। DNAT rules না থাকলে -p কাজ করবে না, ফলে published ports পুরোপুরি অকেজো হয়ে যাবে। এছাড়া আলাদা Compose network-গুলোকে আলাদা রাখার জন্য যে isolation rules থাকে, সেগুলোও আর থাকবে না। এই bypass করার ফলে আপনি container networking নষ্ট করে ফেলবেন, এবং প্রতিটি rule আপনাকে ম্যানুয়ালি লিখতে এবং রক্ষণাবেক্ষণ করতে হবে। Docker-এর নিজস্ব documentation অনুযায়ী, এই setting-টি শুধুমাত্র তাদের জন্য যারা ঠিক এই কাজটিই করতে চান। DOCKER-USER chain-টি বিশেষভাবে তৈরি করা হয়েছে যাতে কারো এই switch-টি ব্যবহার করার প্রয়োজন না হয়।
একই সমস্যার IPv6 দিক
প্রথমে IPv6-এ প্রকাশিত port-টি কেমন তা পরীক্ষা করুন:
sudo ss -tlnp | grep 8080Docker Engine 27 থেকে Docker ডিফল্টভাবে ip6tables পরিচালনা করে। IPv6 সক্ষম থাকা অবস্থায় একটি Docker network-এ, একটি প্রকাশিত port IPv6 tables-এ একই DNAT ট্রিটমেন্ট পায়। ফলে সেখানেও একই বাইপাস সমস্যা বিদ্যমান এবং একই সমাধান প্রযোজ্য: ip6tables-এও DOCKER-USER chain বিদ্যমান। তাই আপনার rule-টি sudo ip6tables -I DOCKER-USER ... দিয়ে মিরর করুন এবং সার্ভারের পাবলিক IPv6 অ্যাড্রেস, যেমন curl -6 http://[2001:db8:2a::1]:8080/, ব্যবহার করে বাইরে থেকে curl দিয়ে পরীক্ষা করুন।
IPv6 ছাড়া কোনো network-এ, IPv6 ক্লায়েন্টগুলো docker-proxy দ্বারা পরিচালিত হয়। এটি একটি সাধারণ user-space প্রসেস যা [::]:8080-এ লিসেন করে এবং IPv4-এর মাধ্যমে ট্রাফিক কন্টেইনারে ফরওয়ার্ড করে। হোস্ট প্রসেসের ট্রাফিক INPUT-এর মধ্য দিয়ে যায়, তাই UFW সেই পাথ ফিল্টার করতে পারে, তবে শুধুমাত্র তখনই যখন UFW IPv6 পরিচালনা করে। এটি কি না, এবং VPS-এ IPv6 গ্যাপ তৈরি হওয়ার অন্যান্য উপায়গুলো সম্পর্কে UFW এবং IPv6 গাইড দেখুন।
Loopback-এ পাবলিশ করলে এই প্রশ্নটি আর থাকে না: -p 127.0.0.1:8080:80 শুধুমাত্র IPv4 loopback-এর সাথে বাইন্ড হয়, তাই কোনো IPv6 listener থাকে না এবং কোনো স্ট্যাক থেকেই বাইরে থেকে এতে পৌঁছানো সম্ভব নয়।
কার্যকর প্যাটার্ন
- প্রতিটি internal port
127.0.0.1-এ publish করুন, যাতে সেগুলো সরাসরি উন্মুক্ত না হয়। - public side হিসেবে একটি reverse proxy ব্যবহার করুন যা 80 এবং 443 port নিয়ন্ত্রণ করে।
- Host-এর জন্য UFW default deny রাখুন, তবে SSH এবং proxy port-এর জন্য অনুমতি দিন।
DOCKER-USER-এ প্রকৃত public container ports ফিল্টার করুন, যা original destination port-এর ভিত্তিতে/etc/ufw/after.rules-এ সংরক্ষিত থাকে।- Docker-এর iptables integration চালু রাখুন।
একবার সেটআপ করে ফেললে, অপ্রত্যাশিত সমস্যা আর থাকবে না: ufw status হোস্টের বর্ণনা দেয়, এবং DOCKER-USER কন্টেইনারের বর্ণনা দেয়। কোনো কিছু ভুলবশত publish হবে না, এবং পরবর্তীবার যখন আপনি docker run -p টাইপ করবেন, তখন শুধুমাত্র আপনার কাঙ্ক্ষিত অংশটিই উন্মুক্ত হবে।
FAQ
UFW পোর্ট ব্লক করার পরেও আমি কেন আমার Docker container-এ অ্যাক্সেস করতে পারছি?
কারণ Docker PREROUTING chain-এ একটি DNAT rule ব্যবহার করে পোর্টটি পাবলিশ করে। এটি ফিল্টারিং হওয়ার আগেই প্যাকেটটির destination অ্যাড্রেস পরিবর্তন করে container-এর অ্যাড্রেস করে দেয়। এরপর প্যাকেটটি FORWARD পথ অনুসরণ করে। UFW-এর নিয়মগুলো INPUT chain-এ থাকে, যেখানে প্যাকেটটি কখনো পৌঁছায় না। ফলে firewall-এর সাথে প্যাকেটটির কোনো যোগাযোগ হয় না এবং এর deny rules পাবলিশ করা container port-এর ওপর কোনো প্রভাব ফেলে না।
আমি কীভাবে UFW দিয়ে Docker-এর পাবলিশ করা পোর্টগুলো ব্লক করতে পারি?
UFW নিজে এটি করতে পারে না, কারণ এর rules ভুল chain-এ থাকে। আপনি হয় পোর্টটি এক্সপোজ করা বন্ধ করতে পারেন; পোর্টটি 127.0.0.1:8080:80 হিসেবে পাবলিশ করুন যাতে শুধুমাত্র host অ্যাক্সেস করতে পারে। অথবা, conntrack ব্যবহার করে original destination port ম্যাচ করে DOCKER-USER chain-এ একটি iptables rule দিয়ে ফিল্টার করুন। রিবুট এবং ufw reload এর পরেও নিয়মটি কার্যকর রাখতে এটিকে /etc/ufw/after.rules-এ সেভ করে রাখুন।
Docker-এর daemon.json ফাইলে "iptables": false সেট করা উচিত কি?
না। এই সেটিংসটি Docker-এর সমস্ত firewall এবং NAT rule সরিয়ে ফেলে, যা পোর্ট বাইপাস করার চেয়ে অনেক বেশি ক্ষতি করে। masquerade rule না থাকায় container-এর outbound internet অ্যাক্সেস বন্ধ হয়ে যায় এবং DNAT rule না থাকায় পাবলিশ করা পোর্টগুলো কাজ করা বন্ধ করে দেয়। এর পরিবর্তে loopback publishing এবং DOCKER-USER chain ব্যবহার করুন; এগুলো container networking নষ্ট না করেই এক্সপোজার সমস্যার সমাধান করে।
Docker কি IPv6-এর ক্ষেত্রেও UFW বাইপাস করে?
Docker Engine 27 এবং পরবর্তী ভার্সনগুলোতে, ip6tables ম্যানেজমেন্ট ডিফল্টভাবে অন থাকে। তাই IPv6-enabled Docker network-এ পাবলিশ করা পোর্টটি IPv4-এর মতোই UFW বাইপাস করে এবং এর জন্য ip6tables দিয়ে DOCKER-USER rule-টি মিরর করা প্রয়োজন। যেসব নেটওয়ার্কে IPv6 নেই, সেখানে docker-proxy প্রসেসটি [::]-এ লিসেন করে এবং সেই ট্রাফিক INPUT দিয়ে প্রবাহিত হয়; যদি UFW IPv6 ম্যানেজ করে, তবে সেখানে এটি ফিল্টার করা সম্ভব। 127.0.0.1-এ পাবলিশ করলে উভয় ক্ষেত্রেই সমস্যা সমাধান হয়, কারণ সেখানে IPv6-এ কোনো কানেকশন লিসেন করে না।