رفتن به محتوا
LoopX

فایروال با ufw

توی این درس یاد می‌گیری فایروال (firewall) تعیین می‌کند چه ترافیکی به ماشین برسد و چه ترافیکی رد شود. در Ubuntu ابزار ساده‌اش ufw (Uncomplicated Firewall) است: سیاست پیش‌فرض (همه‌ی ورودی‌ها رد، مگر مجاز) را می‌گذاری، فقط پورت‌های لازم را با ufw allow باز می‌کنی (مثلاً sudo ufw allow OpenSSH)، با sudo ufw enable فعالش می‌کنی و با sudo ufw status verbose وضعیت را می‌خوانی؛ sudo ufw deny را هم می‌شناسی. مهم‌تر از همه یاد می‌گیری چطور خودت را از SSH بیرون نیندازی. همه‌چیز را از یک ماشین دیگر روی یک سرور واقعی آزمایش می‌کنم. تمرین: فایروال را طوری تنظیم کن که فقط SSH و HTTP باز باشند.

مسئله: هر پورت باز، یک در باز است

Section titled “مسئله: هر پورت باز، یک در باز است”

در درس پورت‌ها دیدی هر سرویس روی یک پورت گوش می‌دهد. ولی سرویسی که فقط برای خودت لازم است (یک پنل مدیریت، دیتابیس، یک برنامه‌ی تستی که فراموش کرده‌ای) اگر روی همه‌ی آدرس‌ها گوش بدهد، برای کل اینترنت باز است. به‌محض اینکه یک سرور آدرس عمومی بگیرد، ربات‌ها شروع می‌کنند پورت‌هایش را اسکن و رمزها را حدس بزنند. فایروال می‌گوید: «از بیرون فقط همین پورت‌ها؛ بقیه رد».

تشبیه: نگهبان درِ ساختمان

Section titled “تشبیه: نگهبان درِ ساختمان”

فایروال مثل نگهبان در ورودی ساختمان است. او یک فهرست قاعده دارد که از بالا به پایین می‌خواند: «واحد ۲۲: بگذر. واحد ۸۰: بگذر. …». هر کسی (بسته‌ای) که برسد، با اولین قاعده‌ی منطبق تصمیم گرفته می‌شود. و اگر هیچ قاعده‌ای نخواندش، قانون پیش‌فرض: «هر که اسمش در فهرست نیست، رد». اگر نگهبان را بیاوری ولی فراموش کنی اسم خودت را در فهرست بنویسی، فردا صبح هم تو را راه نمی‌دهد.

هر بسته‌ی ورودی با قاعده‌ها از بالا به پایین مقایسه می‌شود و اولین قاعده‌ی منطبق حکم می‌دهد؛ اگر هیچ‌کدام منطبق نبود، سیاست پیش‌فرض (در ufw: رد کن) اعمال می‌شود.

برای آزمایش واقعی فایروال به دو ماشین نیاز داریم، چون ترافیک خود ماشین به خودش (از lo) از فایروال معاف است. سرور همان ماشین systemd‌دار آزمایشی من است (با sshd و nginx واقعی و ufw نصب‌شده) و کلاینت ماشین دیگری است که نقش لپ‌تاپ تو را دارد و از بیرون به سرور وصل می‌شود. چون این دو ماشین جدا هستند، دستورها را از بیرون با docker exec می‌زنم؛ برای خوانایی دو تابع تعریف می‌کنم: server یعنی «این دستور را روی سرور اجرا کن» و client یعنی «روی ماشین دیگر». روی سرور واقعی خودت server را نمی‌نویسی؛ فقط خود دستور (با sudo) را می‌زنی.

مثال ۱: قبل از فایروال، همه‌چیز باز است

Section titled “مثال ۱: قبل از فایروال، همه‌چیز باز است”
Terminal window
server() { docker exec lxsys "$@"; }
client() { docker exec -u ali -e TERM=xterm lxlab "$@"; }
SIP=$(server hostname -I | awk '{print $1}')
CIP=$(client hostname -I | awk '{print $1}')
echo "آدرس سرور: $SIP | آدرس کلاینت: $CIP"
echo "--- فایروال خاموش:"
server ufw status
echo "--- از کلاینت: پورت 22 (ssh) و 80 (nginx که روی سرور اجراست)"
client nc -zv -w 2 $SIP 22 2>&1
client nc -zv -w 2 $SIP 80 2>&1
خروجی
آدرس سرور: 172.17.0.4 | آدرس کلاینت: 172.17.0.2
--- فایروال خاموش:
Status: inactive
--- از کلاینت: پورت 22 (ssh) و 80 (nginx که روی سرور اجراست)
Connection to 172.17.0.4 22 port [tcp/ssh] succeeded!
Connection to 172.17.0.4 80 port [tcp/http] succeeded!

Status: inactive: فایروال خاموش است و هر دو پورت از بیرون باز. (آزمایش را با nc -zv از درس پورت‌ها انجام می‌دهم: succeeded یعنی باز.)

مثال ۲: ufw را بشناس، سیاست پیش‌فرض و پروفایل برنامه‌ها

Section titled “مثال ۲: ufw را بشناس، سیاست پیش‌فرض و پروفایل برنامه‌ها”

ufw برای برنامه‌های رایج پروفایل (profile) آماده دارد: به‌جای شماره‌ی پورت می‌توانی اسم برنامه را بگویی:

Terminal window
server() { docker exec lxsys "$@"; }
echo "--- پروفایل‌های موجود:"
server ufw app list
echo "--- یک پروفایل چه پورتی را باز می‌کند؟"
server ufw app info OpenSSH
خروجی
--- پروفایل‌های موجود:
Available applications:
Nginx Full
Nginx HTTP
Nginx HTTPS
OpenSSH
--- یک پروفایل چه پورتی را باز می‌کند؟
Profile: OpenSSH
Title: Secure shell server, an rshd replacement
Description: OpenSSH is a free implementation of the Secure Shell protocol.
Port:
22/tcp

پروفایل OpenSSH یعنی پورت 22/tcp. حالا سیاست پیش‌فرض را تعیین می‌کنم که پایه‌ی هر فایروال خوب است: ورودی‌ها رد (deny) و خروجی‌ها مجاز (allow):

Terminal window
server() { docker exec lxsys "$@"; }
server ufw default deny incoming
server ufw default allow outgoing
خروجی
Default incoming policy changed to 'deny'
(be sure to update your rules accordingly)
Default outgoing policy changed to 'allow'
(be sure to update your rules accordingly)

هنوز ufw فعال نشده، فقط سیاست ذخیره شد. incoming ترافیکی که به سرور می‌رسد و outgoing ترافیکی که از سرور بیرون می‌رود (مثلاً apt update، که اگر ببندیش، سرور برای به‌روزرسانی هم راهی ندارد).

مثال ۳: اول SSH را مجاز کن، بعد فایروال را روشن کن

Section titled “مثال ۳: اول SSH را مجاز کن، بعد فایروال را روشن کن”

مهم‌ترین قاعده‌ی این درس: قبل از ufw enable، پورت SSH را باز کن، وگرنه بعد از فعال شدن، دیگر نمی‌توانی از راه دور وارد شوی. با ufw allow OpenSSH شروع می‌کنم و ufw show added قاعده‌های اضافه‌شده را نشان می‌دهد:

Terminal window
server() { docker exec lxsys "$@"; }
server ufw allow OpenSSH
echo "--- قاعده‌های اضافه‌شده (هنوز اعمال نشده‌اند):"
server ufw show added
خروجی
Rules updated
Rules updated (v6)
--- قاعده‌های اضافه‌شده (هنوز اعمال نشده‌اند):
Added user rules (see 'ufw status' for running firewall):
ufw allow OpenSSH

دو خط Rules updated را ببین: یکی برای IPv4 و یکی (v6) برای IPv6؛ ufw هر قاعده را برای هر دو می‌نویسد. حالا روشنش می‌کنم. روی سرور واقعی معمولاً از راه دور و با ssh وارد شده‌ای، پس همین را هم شبیه‌سازی می‌کنم: از کلاینت با ssh به سرور وارد می‌شوم (یک کاربر deploy با دسترسی sudo می‌سازم) و داخل همان نشست ssh فایروال را فعال می‌کنم، دقیقاً مثل کار روی یک سرور دور (ترمینال مجازی tmux تا صفحه‌ی واقعی را ببینی):

Terminal window
server() { docker exec lxsys "$@"; }
client() { docker exec -u ali -e TERM=xterm lxlab "$@"; }
SIP=$(server hostname -I | awk '{print $1}')
PW=lab-pass
server useradd -m -s /bin/bash -G sudo deploy
server sh -c "echo deploy:$PW | chpasswd"
server touch /home/deploy/.hushlogin
server chown deploy:deploy /home/deploy/.hushlogin
cscreen() { client tmux capture-pane -t c -p | sed -e :a -e '/^[[:space:]]*$/{$d;N;ba' -e '}'; }
client tmux -u new-session -d -s c -x 100 -y 20 "bash --norc"; sleep 1
client tmux send-keys -t c "ssh -o StrictHostKeyChecking=accept-new deploy@$SIP" Enter; sleep 2
client tmux send-keys -t c "$PW" Enter; sleep 3
client tmux send-keys -t c "sudo ufw enable" Enter; sleep 2
client tmux send-keys -t c "$PW" Enter; sleep 2
cscreen
خروجی
bash-5.2$ ssh -o StrictHostKeyChecking=accept-new deploy@172.17.0.4
deploy@172.17.0.4's password:
deploy@server:~$ sudo ufw enable
[sudo] password for deploy:
Command may disrupt existing ssh connections. Proceed with operation (y|n)?

دو چیز مهم:

  • ufw وقتی دید از یک نشست ssh صدایش می‌زنی هشدار داد: «Command may disrupt existing ssh connections، ادامه بدهم؟». این همان خطری است که بعداً می‌بینی.
  • رمز دوم، رمز sudo بود (فقط برای deploy؛ خود ufw enable root می‌خواهد).

حالا تأیید می‌کنم (y) و بلافاصله می‌بینیم نشست ssh خودمان هنوز زنده است:

Terminal window
client() { docker exec -u ali -e TERM=xterm lxlab "$@"; }
cscreen() { client tmux capture-pane -t c -p | sed -e :a -e '/^[[:space:]]*$/{$d;N;ba' -e '}'; }
client tmux send-keys -t c "y" Enter; sleep 3
client tmux send-keys -t c "clear" Enter
client tmux send-keys -t c "echo نشست هنوز زنده است: \$(whoami)@\$(hostname)" Enter; sleep 1
client tmux send-keys -t c "sudo ufw status verbose" Enter; sleep 2
cscreen
خروجی
deploy@server:~$ echo نشست هنوز زنده است: $(whoami)@$(hostname)
نشست هنوز زنده است: deploy@server
deploy@server:~$ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp (OpenSSH) ALLOW IN Anywhere
22/tcp (OpenSSH (v6)) ALLOW IN Anywhere (v6)
deploy@server:~$

Firewall is active and enabled on system startup: فعال شد و بعد از ریبوت هم فعال می‌ماند. نشست ssh من هم قطع نشد (چرا؟ در «پشت پرده»). و status verbose وضعیت کامل را می‌دهد:

  • Default: deny (incoming), allow (outgoing), deny (routed): همان سیاستی که گذاشتم؛ routed ترافیکی است که فقط از این ماشین عبور می‌کند (وقتی نقش مسیریاب دارد) و آن هم پیش‌فرض رد است.
  • Logging: on (low): ثبت لاگ روشن است.
  • جدول قاعده‌ها: 22/tcp (OpenSSH) برای IPv4 و IPv6.

مثال ۴: باز کردن یک پورت دیگر، ufw allow

Section titled “مثال ۴: باز کردن یک پورت دیگر، ufw allow”

nginx روی سرور بالاست (پورت ۸۰)، ولی فایروال فقط ۲۲ را اجازه داده. از کلاینت امتحان کن، بعد پورت ۸۰ را باز کن:

Terminal window
server() { docker exec lxsys "$@"; }
client() { docker exec -u ali -e TERM=xterm lxlab "$@"; }
SIP=$(server hostname -I | awk '{print $1}')
echo "--- پورت 22 (مجاز) و 80 (هنوز مجاز نیست):"
client nc -zv -w 2 $SIP 22 2>&1
client nc -zv -w 2 $SIP 80 2>&1
echo "--- ufw allow 80/tcp:"
server ufw allow 80/tcp
client nc -zv -w 2 $SIP 80 2>&1
echo "--- جدول شماره‌دار قاعده‌ها:"
server ufw status numbered
خروجی
--- پورت 22 (مجاز) و 80 (هنوز مجاز نیست):
Connection to 172.17.0.4 22 port [tcp/ssh] succeeded!
nc: connect to 172.17.0.4 port 80 (tcp) timed out: Operation now in progress
--- ufw allow 80/tcp:
Rule added
Rule added (v6)
Connection to 172.17.0.4 80 port [tcp/http] succeeded!
--- جدول شماره‌دار قاعده‌ها:
Status: active
To Action From
-- ------ ----
[ 1] OpenSSH ALLOW IN Anywhere
[ 2] 80/tcp ALLOW IN Anywhere
[ 3] OpenSSH (v6) ALLOW IN Anywhere (v6)
[ 4] 80/tcp (v6) ALLOW IN Anywhere (v6)

timed out: پورت ۸۰ با اینکه nginx پشتش زنده بود از بیرون بی‌صدا دور ریخته می‌شد (چون سیاست پیش‌فرض ورودی «رد» است)؛ بعد از ufw allow 80/tcp باز شد. به‌جای عدد می‌شد نوشت sudo ufw allow 'Nginx HTTP' (پروفایل)، یا sudo ufw allow http (اسم سرویس از /etc/services). شکل‌های رایج:

دستور معنی
ufw allow 80/tcp پورت ۸۰ فقط TCP، از هرجا
ufw allow 53 پورت ۵۳ هم TCP و هم UDP
ufw allow 6000:6010/tcp یک بازه از پورت‌ها
ufw allow OpenSSH با پروفایل برنامه
ufw allow from 203.0.113.5 همه‌ی پورت‌ها فقط از یک IP
ufw allow from 203.0.113.5 to any port 22 proto tcp یک پورت، فقط از یک IP
ufw allow from 203.0.113.0/24 to any port 5432 از یک شبکه
ufw deny 23/tcp رد (بی‌صدا)
ufw reject 23/tcp رد با پاسخ خطا (مثال ۶)
ufw delete allow 80/tcp پاک‌کردن قاعده
ufw status numbered نمایش با شماره (برای ufw delete N و ufw insert N)

مثال ۵: ترتیب قاعده‌ها، اولین منطبق برنده است

Section titled “مثال ۵: ترتیب قاعده‌ها، اولین منطبق برنده است”

قاعده‌ها از بالا به پایین خوانده می‌شوند و اولین منطبق حکم می‌دهد. پس یک deny که بعد از یک allow بنویسی بی‌اثر است. می‌خواهم کلاینت (که آدرسش را دارم) از پورت ۸۰ ممنوع شود:

Terminal window
server() { docker exec lxsys "$@"; }
client() { docker exec -u ali -e TERM=xterm lxlab "$@"; }
SIP=$(server hostname -I | awk '{print $1}')
CIP=$(client hostname -I | awk '{print $1}')
echo "--- deny را بعد از allow می‌گذارم:"
server ufw deny from $CIP to any port 80 proto tcp
client nc -zv -w 2 $SIP 80 2>&1
server ufw status numbered | grep -E '80/tcp|^Status'
خروجی
--- deny را بعد از allow می‌گذارم:
Rule added
Connection to 172.17.0.4 80 port [tcp/http] succeeded!
Status: active
[ 2] 80/tcp ALLOW IN Anywhere
[ 3] 80/tcp DENY IN 172.17.0.2
[ 5] 80/tcp (v6) ALLOW IN Anywhere (v6)

با اینکه قاعده‌ی DENY برای کلاینت هست، کلاینت هنوز وصل می‌شود، چون ALLOW 80/tcp (برای همه) بالاتر است و اول منطبق می‌شود. راه‌حل: deny را بالا بگذار با ufw insert 1. (اول قاعده‌ی بی‌اثر را پاک می‌کنم، چون ufw قاعده‌ی تکراری را قبول نمی‌کند.)

Terminal window
server() { docker exec lxsys "$@"; }
client() { docker exec -u ali -e TERM=xterm lxlab "$@"; }
SIP=$(server hostname -I | awk '{print $1}')
CIP=$(client hostname -I | awk '{print $1}')
server ufw delete deny from $CIP to any port 80 proto tcp
server ufw insert 1 deny from $CIP to any port 80 proto tcp
echo "--- حالا اولین قاعده deny است:"
server ufw status numbered | grep -E '80/tcp|^Status'
client nc -zv -w 2 $SIP 80 2>&1
echo "--- برمی‌گردانم:"
server ufw delete deny from $CIP to any port 80 proto tcp
client nc -zv -w 2 $SIP 80 2>&1
خروجی
Rule deleted
Rule inserted
--- حالا اولین قاعده deny است:
Status: active
[ 1] 80/tcp DENY IN 172.17.0.2
[ 3] 80/tcp ALLOW IN Anywhere
[ 5] 80/tcp (v6) ALLOW IN Anywhere (v6)
nc: connect to 172.17.0.4 port 80 (tcp) timed out: Operation now in progress
--- برمی‌گردانم:
Rule deleted
Connection to 172.17.0.4 80 port [tcp/http] succeeded!

بعد از insert 1، قاعده‌ی DENY بالای فهرست رفت و کلاینت timed out شد. این الگوی «استثنا بالا، قاعده‌ی کلی پایین» در هر فایروالی هست.

هر دو ترافیک را رد می‌کنند، ولی شکل رد کردن فرق دارد و روی تجربه‌ی کلاینت اثر می‌گذارد. یک وب‌سرور ساده روی پورت ۸۰۹۹ سرور راه می‌اندازم (که تا الآن هم بسته است چون مجاز نیست) و هر سه حالت را از کلاینت می‌سنجم:

Terminal window
server() { docker exec lxsys "$@"; }
client() { docker exec -u ali -e TERM=xterm lxlab "$@"; }
SIP=$(server hostname -I | awk '{print $1}')
server sh -c 'python3 -m http.server 8099 --bind 0.0.0.0 > /dev/null 2>&1 &'
sleep 1
echo "--- 1) مجاز (allow):"
server ufw allow 8099/tcp > /dev/null
client nc -zv -w 2 $SIP 8099 2>&1
echo "--- 2) deny (بسته‌ها بی‌صدا دور ریخته می‌شوند):"
server ufw deny 8099/tcp > /dev/null
client nc -zv -w 2 $SIP 8099 2>&1
echo "--- 3) reject (به فرستنده خطا برمی‌گردد):"
server ufw reject 8099/tcp > /dev/null
client nc -zv -w 2 $SIP 8099 2>&1
خروجی
--- 1) مجاز (allow):
Connection to 172.17.0.4 8099 port [tcp/*] succeeded!
--- 2) deny (بسته‌ها بی‌صدا دور ریخته می‌شوند):
nc: connect to 172.17.0.4 port 8099 (tcp) timed out: Operation now in progress
--- 3) reject (به فرستنده خطا برمی‌گردد):
nc: connect to 172.17.0.4 port 8099 (tcp) failed: Connection refused

این همان فرق «timed out» و «Connection refused» درس پورت‌هاست، این بار از طرف فایروال: deny بی‌صدا دور می‌ریزد (کلاینت باید تا timeout صبر کند؛ برای فرد مهاجم اسکن را کند می‌کند و وجود ماشین را کمتر لو می‌دهد) و reject با یک پاسخ «نه» فوراً خطا می‌دهد (برای شبکه‌ی داخلی که نمی‌خواهی کاربران معطل شوند). برای اینترنت عمومی معمولاً deny (پیش‌فرض) بهتر است. و حالا محدودکردن به یک آدرس (مثلاً «فقط دفتر شرکت»): ابتدا قاعده‌ی قبلی را عوض می‌کنم و ۸۰۹۹ را فقط برای یک IP دیگر (که کلاینت من نیست) باز می‌کنم تا اثرش را ببینیم، بعد برای کلاینت:

Terminal window
server() { docker exec lxsys "$@"; }
client() { docker exec -u ali -e TERM=xterm lxlab "$@"; }
SIP=$(server hostname -I | awk '{print $1}')
CIP=$(client hostname -I | awk '{print $1}')
server ufw delete reject 8099/tcp > /dev/null
echo "--- فقط از 10.0.0.5 (IP دفتر) مجاز:"
server ufw allow from 10.0.0.5 to any port 8099 proto tcp > /dev/null
client nc -zv -w 2 $SIP 8099 2>&1
echo "--- حالا فقط از IP خود کلاینت ($CIP) هم مجاز:"
server ufw allow from $CIP to any port 8099 proto tcp > /dev/null
client nc -zv -w 2 $SIP 8099 2>&1
server ufw status | grep -E '8099|^Status'
خروجی
--- فقط از 10.0.0.5 (IP دفتر) مجاز:
nc: connect to 172.17.0.4 port 8099 (tcp) timed out: Operation now in progress
--- حالا فقط از IP خود کلاینت (172.17.0.2) هم مجاز:
Connection to 172.17.0.4 8099 port [tcp/*] succeeded!
Status: active
8099/tcp ALLOW 10.0.0.5
8099/tcp ALLOW 172.17.0.2

allow from IP to any port N: «از این IP، به هر آدرس این ماشین، پورت N». اول از آدرس دیگری اجازه داده شد و کلاینت من رد شد؛ بعد خودش اضافه شد و رسید. (این همان الگوی «پورت SSH فقط از IP دفتر» است.)

مثال ۷: خطر قفل‌شدن بیرون از SSH

Section titled “مثال ۷: خطر قفل‌شدن بیرون از SSH”

حالا چیزی که تمام این درس برایش است. فرض کن به هر دلیلی قاعده‌ی SSH پاک شد (یا از اول نبود). نشست ssh که الآن باز دارم کار می‌کند، ولی ببین یک اتصال جدید چه می‌شود:

Terminal window
server() { docker exec lxsys "$@"; }
client() { docker exec -u ali -e TERM=xterm lxlab "$@"; }
SIP=$(server hostname -I | awk '{print $1}')
cscreen() { client tmux capture-pane -t c -p | sed -e :a -e '/^[[:space:]]*$/{$d;N;ba' -e '}'; }
echo "--- قاعده‌ی SSH را پاک می‌کنم (در عمل: فراموش کرده بودم باز کنم):"
server ufw delete allow OpenSSH
echo "--- نشست ssh باز قبلی هنوز جواب می‌دهد؟"
client tmux send-keys -t c "clear" Enter
client tmux send-keys -t c "echo هنوز وصلم: \$(date +%T)" Enter; sleep 1
cscreen
echo "--- ولی یک اتصال جدید به پورت 22:"
client nc -zv -w 3 $SIP 22 2>&1
client ssh -o ConnectTimeout=3 -o BatchMode=yes deploy@$SIP true 2>&1
خروجی
--- قاعده‌ی SSH را پاک می‌کنم (در عمل: فراموش کرده بودم باز کنم):
Rule deleted
Rule deleted (v6)
--- نشست ssh باز قبلی هنوز جواب می‌دهد؟
deploy@server:~$ echo هنوز وصلم: $(date +%T)
هنوز وصلم: 07:19:31
deploy@server:~$
--- ولی یک اتصال جدید به پورت 22:
nc: connect to 172.17.0.4 port 22 (tcp) timed out: Operation now in progress
ssh: connect to host 172.17.0.4 port 22: Connection timed out

نشست قبلی کار کرد (چرا؟ در «پشت پرده») ولی اتصال جدید timed out شد: ssh به ماشین راه ندارد. اگر آن نشست را ببندم، دیگر نمی‌توانم وارد شوم و باید به کنسول سرور (پنل ارائه‌دهنده‌ی VPS) بروم. به همین دلیل:

  1. قبل از ufw enable همیشه ufw allow OpenSSH (یا 22/tcp) را بزن و با ufw show added ببین.
  2. هر تغییر فایروال را با نشست دوم آزمایش کن و نشست اول را باز نگه دار.
  3. اگر SSH را روی پورت دیگری برده‌ای، آن پورت را باز کن.

حالا از «کنسول» (همان server) درستش می‌کنم و ورود را دوباره آزمایش می‌کنم:

Terminal window
server() { docker exec lxsys "$@"; }
client() { docker exec -u ali -e TERM=xterm lxlab "$@"; }
SIP=$(server hostname -I | awk '{print $1}')
echo "--- از کنسول: ufw allow OpenSSH"
server ufw allow OpenSSH
client nc -zv -w 3 $SIP 22 2>&1
خروجی
--- از کنسول: ufw allow OpenSSH
Rule added
Rule added (v6)
Connection to 172.17.0.4 22 port [tcp/ssh] succeeded!
⚡ بررسی سریع

قبل از sudo ufw enable روی یک سرور دور، مهم‌ترین کار چیست؟

پشت پرده: ufw چطور کار می‌کند؟

Section titled “پشت پرده: ufw چطور کار می‌کند؟”

ufw یک پوشش ساده روی iptables (فایروال خود هسته‌ی لینوکس) است: قاعده‌هایی را که می‌نویسی به قاعده‌های iptables ترجمه و در زنجیره‌هایی مثل ufw-user-input می‌گذارد. ببین:

Terminal window
server() { docker exec lxsys "$@"; }
echo "--- ufw show added (آنچه تو نوشته‌ای):"
server ufw show added
echo "--- زنجیره‌ی iptables که ufw برایت ساخته (ufw-user-input):"
server iptables -S ufw-user-input
echo "--- چرا نشست ssh باز بعد از فعال‌شدن قطع نشد؟ قاعده‌ی conntrack در before.rules:"
server grep -n 'ctstate RELATED,ESTABLISHED' /etc/ufw/before.rules | head -2
echo "--- فایل‌های تنظیمات:"
server sh -c "ls /etc/ufw | grep -v '[0-9]\{8\}_'"
خروجی
--- ufw show added (آنچه تو نوشته‌ای):
Added user rules (see 'ufw status' for running firewall):
ufw allow 80/tcp
ufw allow from 10.0.0.5 to any port 8099 proto tcp
ufw allow from 172.17.0.2 to any port 8099 proto tcp
ufw allow OpenSSH
--- زنجیره‌ی iptables که ufw برایت ساخته (ufw-user-input):
-N ufw-user-input
-A ufw-user-input -p tcp -m tcp --dport 80 -j ACCEPT
-A ufw-user-input -s 10.0.0.5/32 -p tcp -m tcp --dport 8099 -j ACCEPT
-A ufw-user-input -s 172.17.0.2/32 -p tcp -m tcp --dport 8099 -j ACCEPT
-A ufw-user-input -p tcp -m tcp --dport 22 -m comment --comment "\'dapp_OpenSSH\'" -j ACCEPT
--- چرا نشست ssh باز بعد از فعال‌شدن قطع نشد؟ قاعده‌ی conntrack در before.rules:
25:-A ufw-before-input -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
26:-A ufw-before-output -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
--- فایل‌های تنظیمات:
after.init
after.rules
after6.rules
applications.d
before.init
before.rules
before6.rules
sysctl.conf
ufw.conf
user.rules
user6.rules

نکته‌ها:

  • هر ufw allow یک خط -A ufw-user-input ... -j ACCEPT شد؛ یکی برای هر پورت. ترتیب خط‌ها همان ترتیب قاعده‌هاست (اولین منطبق برنده).
  • بالای همه‌ی قاعده‌های تو، before.rules ترافیک ESTABLISHED,RELATED را می‌پذیرد: یعنی اتصال‌هایی که از قبل برقرار بودند (مثل نشست ssh باز) عبور می‌کنند و فقط اتصال‌های جدید با قاعده‌ها سنجیده می‌شوند. برای همین فایروال نشست باز را قطع نکرد ولی اتصال تازه را رد کرد.
  • قاعده‌های تو در /etc/ufw/user.rules و user6.rules (برای IPv6)، و تنظیمات عمومی (مثل IPV6=yes) در /etc/default/ufw است.
  • ufw فقط یک ابزار ورودی/خروجی ماشین خودت است؛ بعضی برنامه‌ها (مثل Docker) خودشان قاعده‌ی iptables می‌نویسند که ممکن است از ufw رد شود؛ در درس‌های Docker روی این جزئیات مفصل نیستم و برای اطمینان همیشه از بیرون پورت‌ها را با nc -zv آزمایش کن.
دستور کار
sudo ufw status verbose وضعیت، سیاست‌ها و قاعده‌ها
sudo ufw status numbered قاعده‌ها با شماره
sudo ufw default deny incoming / allow outgoing سیاست پیش‌فرض
sudo ufw allow OpenSSH / 22/tcp باز کردن SSH
sudo ufw allow 80/tcp باز کردن یک پورت
sudo ufw allow from IP to any port N proto tcp یک پورت فقط از یک IP
sudo ufw deny ... / sudo ufw reject ... رد بی‌صدا / رد با خطا
sudo ufw insert 1 deny ... افزودن قاعده در بالای فهرست
sudo ufw delete allow 80/tcp حذف با شرح قاعده
sudo ufw show added قاعده‌های نوشته‌شده
sudo ufw app list / app info NAME پروفایل برنامه‌ها
sudo ufw enable / disable روشن/خاموش (پایدار بعد از ریبوت)
sudo ufw reset پاک‌کردن همه‌چیز (و خاموش‌کردن)

۱) روشن‌کردن فایروال بدون مجاز‌کردن SSH

Section titled “۱) روشن‌کردن فایروال بدون مجاز‌کردن SSH”

مثال ۷. راه‌حل: ufw allow OpenSSH قبل از ufw enable، و نشست دوم برای آزمایش. اگر قفل شدی: کنسول ارائه‌دهنده‌ی سرور.

Terminal window
docker exec lxsys ufw allow ssh2
خروجی
ERROR: Could not find a profile matching 'ssh2'

ERROR: Could not find a profile matching 'ssh2': ufw app list را ببین (اسم‌ها حساس به حروف‌اند: OpenSSH، Nginx HTTP با فاصله و داخل گیومه). راه‌حل: اسم دقیق یا شماره‌ی پورت (22/tcp).

مثال ۵: قاعده‌ی اول برنده است. راه‌حل: ufw insert 1 deny ... برای استثناها.

۴) فراموش‌کردن پروتکل: allow 7000 هم TCP هم UDP

Section titled “۴) فراموش‌کردن پروتکل: allow 7000 هم TCP هم UDP”
Terminal window
docker exec lxsys sh -c 'ufw allow 7000 > /dev/null; ufw allow 7001/tcp > /dev/null; ufw status | grep -E "7000|7001" | grep -v v6; ufw delete allow 7000 > /dev/null; ufw delete allow 7001/tcp > /dev/null'
خروجی
7000 ALLOW Anywhere
7001/tcp ALLOW Anywhere

بدون /tcp، قاعده برای هر دو پروتکل است (بازتر از لازم). راه‌حل: همیشه پروتکل را بنویس، مگر واقعاً هر دو را بخواهی (مثل DNS روی ۵۳).

فایروال را درست باز کرده‌ای ولی Connection refused می‌گیری: یعنی فایروال مشکلی ندارد و هیچ برنامه‌ای گوش نمی‌دهد (درس پورت‌ها: ss -tlnp). فایروال فقط می‌گوید ترافیک اجازه دارد برسد، نه اینکه کسی آنجاست.

۶) فایروال را ابزار «امنیت کامل» دانستن

Section titled “۶) فایروال را ابزار «امنیت کامل» دانستن”

فایروال راه ورود را می‌بندد، ولی رمز ضعیف SSH، نرم‌افزار بی‌به‌روزرسانی یا سرویس آسیب‌پذیر روی پورت باز را درست نمی‌کند. لایه‌های دیگر (کلید SSH، fail2ban، به‌روزرسانی خودکار) در پروژه‌ی پایانی می‌آیند.

✎ تمرینآسان

وضعیت فعلی ufw را بخوان و بگو سیاست پیش‌فرض ورودی و خروجی چیست و چند قاعده دارد.

دیدن جواب
Terminal window
docker exec lxsys ufw status verbose
خروجی
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip
To Action From
-- ------ ----
80/tcp ALLOW IN Anywhere
8099/tcp ALLOW IN 10.0.0.5
8099/tcp ALLOW IN 172.17.0.2
22/tcp (OpenSSH) ALLOW IN Anywhere
80/tcp (v6) ALLOW IN Anywhere (v6)
22/tcp (OpenSSH (v6)) ALLOW IN Anywhere (v6)

اگر Status: active و Default: deny (incoming), allow (outgoing) است، یعنی ورودی‌ها رد و خروجی‌ها مجازند؛ جدول پایین قاعده‌هاست (هر قاعده یک خط برای IPv4 و یک خط (v6)).

✎ تمرینمتوسط

تمرین اصلی: فایروال را طوری تنظیم کن که فقط SSH و HTTP باز باشند. از صفر (reset) شروع کن، سیاست‌ها را بگذار، SSH و ۸۰ را مجاز کن، فعال کن، و از کلاینت ثابت کن ۲۲ و ۸۰ باز و ۸۰۹۹ (وب‌سرور آزمایشی) بسته است.

دیدن جواب
Terminal window
server() { docker exec lxsys "$@"; }
client() { docker exec -u ali -e TERM=xterm lxlab "$@"; }
SIP=$(server hostname -I | awk '{print $1}')
server ufw --force reset > /dev/null
server ufw default deny incoming
server ufw default allow outgoing
server ufw allow OpenSSH
server ufw allow 80/tcp
server ufw --force enable
server ufw status verbose
echo "--- آزمایش از کلاینت:"
client nc -zv -w 2 $SIP 22 2>&1
client nc -zv -w 2 $SIP 80 2>&1
client nc -zv -w 2 $SIP 8099 2>&1
خروجی
Default incoming policy changed to 'deny'
(be sure to update your rules accordingly)
Default outgoing policy changed to 'allow'
(be sure to update your rules accordingly)
Rules updated
Rules updated (v6)
Rules updated
Rules updated (v6)
Firewall is active and enabled on system startup
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp (OpenSSH) ALLOW IN Anywhere
80/tcp ALLOW IN Anywhere
22/tcp (OpenSSH (v6)) ALLOW IN Anywhere (v6)
80/tcp (v6) ALLOW IN Anywhere (v6)
--- آزمایش از کلاینت:
Connection to 172.17.0.4 22 port [tcp/ssh] succeeded!
Connection to 172.17.0.4 80 port [tcp/http] succeeded!
nc: connect to 172.17.0.4 port 8099 (tcp) timed out: Operation now in progress
✎ تمرینسخت

SSH را فقط از IP کلاینت مجاز کن (نه همه‌جا)، HTTP را برای همه، و با یک deny صریح 23/tcp (telnet) را ببند. ثابت کن کلاینت به ۲۲ و ۸۰ می‌رسد و سپس SSH را برای یک IP دیگر (10.0.0.5) بگذار تا ببینی کلاینت رد می‌شود. در پایان فایروال را reset کن.

دیدن جواب
Terminal window
server() { docker exec lxsys "$@"; }
client() { docker exec -u ali -e TERM=xterm lxlab "$@"; }
SIP=$(server hostname -I | awk '{print $1}')
CIP=$(client hostname -I | awk '{print $1}')
server ufw --force reset > /dev/null
server ufw default deny incoming > /dev/null
server ufw allow from $CIP to any port 22 proto tcp > /dev/null
server ufw allow 80/tcp > /dev/null
server ufw deny 23/tcp > /dev/null
server ufw --force enable > /dev/null
server ufw status | grep -vE 'v6|^$'
echo "--- کلاینت مجاز:"
client nc -zv -w 2 $SIP 22 2>&1
client nc -zv -w 2 $SIP 80 2>&1
echo "--- حالا SSH فقط برای 10.0.0.5:"
server ufw delete allow from $CIP to any port 22 proto tcp > /dev/null
server ufw allow from 10.0.0.5 to any port 22 proto tcp > /dev/null
client nc -zv -w 2 $SIP 22 2>&1
server ufw --force reset > /dev/null
خروجی
Status: active
To Action From
-- ------ ----
22/tcp ALLOW 172.17.0.2
80/tcp ALLOW Anywhere
23/tcp DENY Anywhere
--- کلاینت مجاز:
Connection to 172.17.0.4 22 port [tcp/ssh] succeeded!
Connection to 172.17.0.4 80 port [tcp/http] succeeded!
--- حالا SSH فقط برای 10.0.0.5:
nc: connect to 172.17.0.4 port 22 (tcp) timed out: Operation now in progress
؟ آزمونک
  1. سیاست پیش‌فرض ufw برای ترافیک ورودی چیست و چرا برای امنیت خوب است؟

  2. قبل از sudo ufw enable روی یک سرور دور چه باید کرد؟

  3. اگر allow 80/tcp بالاتر از deny from IP to any port 80 باشد، آن IP چه می‌شود؟

  4. فرق deny و reject؟

  5. ufw allow 53 چه می‌کند؟

  6. چرا بعد از فعال‌شدن فایروال نشست ssh باز قطع نشد ولی اتصال جدید رد شد؟

  • فایروال ترافیک ورودی (و خروجی) را با قاعده‌ها کنترل می‌کند؛ ufw پوششی ساده روی iptables است. سیاست پایه: deny incoming، allow outgoing.
  • ترتیب کار: ufw default deny incoming ← ufw allow OpenSSH ← سایر پورت‌های لازم (ufw allow 80/tcp) ← ufw enable ← ufw status verbose.
  • قاعده‌ها از بالا به پایین خوانده می‌شوند و اولین منطبق برنده است (ufw insert 1 برای استثنا). deny بی‌صدا دور می‌ریزد (timeout)، reject خطا برمی‌گرداند (refused).
  • بدون پروتکل، قاعده برای TCP و UDP هر دو است؛ from IP to any port N برای محدود کردن به یک آدرس.
  • خطر قفل‌شدن: بدون قاعده‌ی SSH اتصال‌های جدید رد می‌شوند (نشست باز تا بسته‌شدنش زنده می‌ماند). همیشه نشست دوم برای آزمایش و راه کنسول را داشته باش.
  • با nc -zv از ماشین دیگر آزمایش کن؛ آزمایش از خود سرور از فایروال معاف است.
برگه‌ی تقلب این درس
دستورکاری که می‌کند
sudo ufw status verbose / status numberedوضعیت / با شماره
sudo ufw default deny incomingورودی‌ها رد، مگر مجاز
sudo ufw default allow outgoingخروجی‌ها آزاد
sudo ufw allow OpenSSHاول این!
sudo ufw allow 80/tcp / sudo ufw allow "Nginx HTTP"باز کردن وب
sudo ufw allow from IP to any port 22 proto tcpSSH فقط از یک IP
sudo ufw deny 23/tcp / sudo ufw reject 23/tcpرد بی‌صدا / رد با خطا
sudo ufw insert 1 deny from IPاستثنا در بالای فهرست
sudo ufw delete allow 80/tcpحذف قاعده
sudo ufw show addedقاعده‌های نوشته‌شده
sudo ufw enable / sudo ufw disable / sudo ufw resetروشن / خاموش / پاک‌کردن همه
nc -zv -w 2 SERVER 80آزمایش از بیرون