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

SSH و کار با سرور از راه دور

توی این درس یاد می‌گیری SSH (Secure Shell) راه استاندارد کار با یک سرور از راه دور است: ترمینالِ آن ماشین، روی یک کانال رمزنگاری‌شده. با ssh user@host وصل می‌شوی؛ به‌جای رمز، با یک جفت‌کلید (کلید خصوصی پیش خودت، کلید عمومی روی سرور) که نوع امروزی‌اش ed25519 است وارد می‌شوی؛ با ssh-keygen می‌سازی و با ssh-copy-id روی سرور می‌گذاری؛ در ~/.ssh/config برای هر سرور یک اسم کوتاه تعریف می‌کنی؛ با scp و rsync -avz فایل جابه‌جا می‌کنی؛ و sshd را امن‌تر می‌کنی. تمرین: ورود بدون رمز با کلید به یک سرور را راه می‌اندازی.

مسئله: چطور از راه دور و امن به یک سرور وصل شویم؟

Section titled “مسئله: چطور از راه دور و امن به یک سرور وصل شویم؟”

سرور معمولاً یک ماشین در یک دیتاسنتر دور است؛ فیزیکی به آن دسترسی نداری. باید از راه شبکه وارد شوی، به‌طوری که کسی که ترافیک را می‌شنود نه محتوا را ببیند و نه رمزت را بدزدد، و مطمئن باشی واقعاً به همان سرور وصلی نه یک جعلی. SSH هر دو را حل می‌کند: رمزنگاری و شناسایی هر دو طرف.

کلید عمومی مثل یک قفل است که می‌توانی بدهی دست هر کسی و روی در سرور بزنند؛ قفل خودش به‌تنهایی هیچ‌چیز باز نمی‌کند. کلید خصوصی مثل کلیدی است که فقط تو داری و فقط قفل خودت را باز می‌کند. هر وقت وصل می‌شوی، سرور یک معمای تصادفی می‌دهد که فقط صاحب کلید خصوصی می‌تواند حلش کند، بدون اینکه کلید خصوصی هرگز از دستگاه تو بیرون برود.

ورود با کلید: سرور فقط کلید عمومی تو را دارد. با یک چالش تصادفی و امضای تو (با کلید خصوصی) مطمئن می‌شود تو صاحب آن کلید هستی، بی‌آنکه کلید خصوصی ارسال شود.

برای اینکه همه‌چیز واقعی باشد، هر دو سمت روی ماشین systemd‌دار آزمایشی من هستند: کاربر alice نقش «لپ‌تاپ تو» را دارد و کاربر deploy نقش حساب روی سرور. اتصال از راه شبکه به نام ماشین (server) برقرار می‌شود، پس همان پروتکل و همان پیام‌های واقعی‌اند. چون ssh یک ترمینال تعاملی می‌خواهد (رمز، پرسش اعتماد)، از ترمینال مجازی tmux استفاده می‌کنم تا صفحه‌ی واقعی را نشانت بدهم (در پرسش‌ها و رمزها، تایپ‌شده‌هایم پنهان است مثل ترمینال واقعی).

مثال ۱: اولین اتصال با رمز

Section titled “مثال ۱: اولین اتصال با رمز”

اول دو کاربر را می‌سازم (برای deploy یک رمز آزمایشی می‌گذارم) و دو تابع کمکی ترمینال مجازی را تعریف می‌کنم:

Terminal window
useradd -m -s /bin/bash alice
useradd -m -s /bin/bash deploy
echo "deploy:lab-pass" | chpasswd
PW=lab-pass
run_in_tmux() { tmux -u kill-server 2>/dev/null; tmux -u new-session -d -s t -x "${TW:-100}" -y "${TH:-30}" "$@"; sleep 1; }
screen() { tmux capture-pane -t t -p | sed -e :a -e '/^[[:space:]]*$/{$d;N;ba' -e '}'; }
id alice deploy
خروجی
uid=1002(alice) gid=1002(alice) groups=1002(alice)
uid=1003(deploy) gid=1003(deploy) groups=1003(deploy)

حالا alice (کلاینت) به server وصل می‌شود: ssh user@host، کاربر و میزبان:

Terminal window
TH=42
run_in_tmux "su - alice"
tmux send-keys -t t "ssh deploy@server" Enter; sleep 2
tmux send-keys -t t "yes" Enter; sleep 2
tmux send-keys -t t "$PW" Enter; sleep 3
tmux send-keys -t t "whoami; hostname" Enter; sleep 2
screen
tmux send-keys -t t "exit" Enter; sleep 1
خروجی
alice@server:~$ ssh deploy@server
The authenticity of host 'server (172.17.0.4)' can't be established.
ED25519 key fingerprint is SHA256:eQ9IL4cQjpWrXS4SUG7Tuj+iCCzpOr4DJ70oKKbGM8o.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'server' (ED25519) to the list of known hosts.
deploy@server's password:
Welcome to Ubuntu 24.04.5 LTS (GNU/Linux 7.0.14-linuxkit aarch64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
deploy@server:~$ whoami; hostname
deploy
server
deploy@server:~$

سه چیز اتفاق افتاد:

  1. اعتماد به میزبان: چون این اولین بار است، ssh می‌گوید «اصالت server را نمی‌توانم تأیید کنم» و اثرانگشت (fingerprint) کلید میزبان را نشان داد. این همان «هویت سرور» است. اگر yes بزنی، ذخیره می‌شود و دفعه‌ی بعد خودکار بررسی می‌شود. (روی سرور مهم، اثرانگشت را از مدیر سرور بپرس و با آنچه نشان می‌دهد مقایسه کن؛ فقط وقتی yes بزن که مطمئنی.)
  2. رمز: deploy@server's password: رمز حساب deploy را خواست (هنگام تایپ دیده نمی‌شود).
  3. ورود: پیام خوش‌آمد (MOTD)، علامت deploy@server:~$ و دستورهای whoami و hostname روی سرور اجرا شدند: deploy و server. و exit اتصال را می‌بندد (که روی Ubuntu صفحه را هم پاک می‌کند؛ برای همین صفحه را قبل از آن گرفتم).

اثرانگشت‌ها در فایل ~/.ssh/known_hosts کاربر alice ذخیره شدند:

Terminal window
echo "--- ~/.ssh/known_hosts (اسم‌ها هش شده‌اند؛ هر ستون اول یک میزبان است):"
su - alice -c 'cut -c1-60 ~/.ssh/known_hosts'
echo "--- ssh-keygen -F: آیا server را می‌شناسم؟"
su - alice -c 'ssh-keygen -F server | head -1'
echo "--- بعد از این از پیام‌های طولانی MOTD خلاص می‌شویم (.hushlogin روی سرور):"
touch /home/deploy/.hushlogin
chown deploy:deploy /home/deploy/.hushlogin
خروجی
--- ~/.ssh/known_hosts (اسم‌ها هش شده‌اند؛ هر ستون اول یک میزبان است):
|1|L9GH6KegFkr/xgzbnFjkTnyf9B8=|jh+G7YEbXnufX1rtyc3F7rINARA=
|1|ZDluSQn8b7ePg6WBYJbbXBqsx10=|vWU+LjIfEmavEdAmGgQiG/HU9pI=
|1|qWQuhqfi4Oevucihn+vB8nsABXM=|JKNv19E3fHgyJgPlVjjw3oR1JdM=
--- ssh-keygen -F: آیا server را می‌شناسم؟
# Host server found: line 1
--- بعد از این از پیام‌های طولانی MOTD خلاص می‌شویم (.hushlogin روی سرور):

(.hushlogin در پوشه‌ی خانه‌ی حساب سرور باعث می‌شود پیام خوش‌آمد چاپ نشود. فقط برای کوتاه‌شدن خروجی‌های بعدی این درس.)

مثال ۲: ساختن جفت‌کلید ssh-keygen -t ed25519

Section titled “مثال ۲: ساختن جفت‌کلید ssh-keygen -t ed25519”

ورود با رمز دو ایراد دارد: رمز را می‌شود حدس زد (حمله‌ی brute-force) و باید هر بار تایپ شود. راه بهتر: جفت‌کلید. ssh-keygen یک کلید خصوصی و یک کلید عمومی می‌سازد. -t ed25519 الگوریتم مدرن، کوتاه و سریع را انتخاب می‌کند (توصیه‌ی امروز؛ قدیمی‌ها rsa را با اندازه‌ی بزرگ می‌ساختند) و -C یک توضیح (comment) روی کلید می‌گذارد تا بعداً بدانی کلید کیست:

Terminal window
run_in_tmux "su - alice"
tmux send-keys -t t "ssh-keygen -t ed25519 -C alice@laptop" Enter; sleep 1
echo "(سه بار Enter: مسیر پیش‌فرض، بدون passphrase، تکرار)"
tmux send-keys -t t Enter; sleep 1
tmux send-keys -t t Enter; sleep 1
tmux send-keys -t t Enter; sleep 2
screen
خروجی
(سه بار Enter: مسیر پیش‌فرض، بدون passphrase، تکرار)
alice@server:~$ ssh-keygen -t ed25519 -C alice@laptop
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/alice/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/alice/.ssh/id_ed25519
Your public key has been saved in /home/alice/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:aE8UjJgY0HUkd9It+EjSl2z2nMUXEH/+Tc6xHb7hhOU alice@laptop
The key's randomart image is:
+--[ED25519 256]--+
|.o.+o==*oo .oo.. |
| o ++=oX.. o.. |
| o *.+ o .. .|
| .o. + o |
| o S +o|
| . o =+B|
| . . E*|
| o o|
| o |
+----[SHA256]-----+
alice@server:~$

ssh-keygen سه چیز پرسید: کجا ذخیره کنم (پیش‌فرض ~/.ssh/id_ed25519، همان را پذیرفتم) و passphrase (رمزی که خود فایل کلید خصوصی را قفل می‌کند؛ دو بار). برای این آزمایش آن را خالی گذاشتم، ولی روی کلید واقعی‌ات حتماً passphrase بگذار (اگر لپ‌تاپت دزدیده شد، کلید بی‌passphrase مستقیم قابل‌استفاده است). خروجی هم اثرانگشت کلید و یک تصویر ASCII (randomart) برای مقایسه‌ی چشمی است. فایل‌ها:

Terminal window
run_in_tmux "su - alice"
tmux send-keys -t t "ls -l ~/.ssh" Enter; sleep 1
tmux send-keys -t t "cat ~/.ssh/id_ed25519.pub" Enter; sleep 1
tmux send-keys -t t "ssh-keygen -lf ~/.ssh/id_ed25519.pub" Enter; sleep 1
screen
خروجی
alice@server:~$ ls -l ~/.ssh
total 16
-rw------- 1 alice alice 399 Oct 3 22:04 id_ed25519
-rw-r--r-- 1 alice alice 94 Oct 3 22:04 id_ed25519.pub
-rw------- 1 alice alice 978 Oct 3 22:04 known_hosts
-rw-r--r-- 1 alice alice 142 Oct 3 22:04 known_hosts.old
alice@server:~$ cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINQPotQIugGlbNhln3bqgitwV9QQHnCCALOBmiC01Jhy alice@laptop
alice@server:~$ ssh-keygen -lf ~/.ssh/id_ed25519.pub
256 SHA256:aE8UjJgY0HUkd9It+EjSl2z2nMUXEH/+Tc6xHb7hhOU alice@laptop (ED25519)
alice@server:~$
  • id_ed25519: کلید خصوصی. دسترسی -rw------- (فقط خودت). هرگز آن را کپی نکن، ایمیل نکن، در چت نگذار و در گیت commit نکن. ssh اگر دسترسی‌اش بازتر باشد، آن را رد می‌کند (اشتباهات رایج).
  • id_ed25519.pub: کلید عمومی، یک خط متن: نوع الگوریتم (ssh-ed25519)، خود کلید (رشته‌ی base64) و توضیح (alice@laptop). این را می‌شود به همه داد؛ همین را روی سرور می‌گذاری.

مثال ۳: ssh-copy-id، گذاشتن کلید عمومی روی سرور

Section titled “مثال ۳: ssh-copy-id، گذاشتن کلید عمومی روی سرور”

کلید عمومی باید به فایل ~/.ssh/authorized_keys در حساب سرور اضافه شود. ssh-copy-id این کار را (با ساختن پوشه و دسترسی‌های درست) انجام می‌دهد و یک بار رمز می‌خواهد:

Terminal window
run_in_tmux "su - alice"
tmux send-keys -t t "ssh-copy-id deploy@server" Enter; sleep 3
tmux send-keys -t t "$PW" Enter; sleep 3
screen
خروجی
alice@server:~$ ssh-copy-id deploy@server
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/alice/.ssh/id_ed25519.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are alr
eady installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to inst
all the new keys
deploy@server's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh 'deploy@server'"
and check to make sure that only the key(s) you wanted were added.
alice@server:~$

Number of key(s) added: 1: کلید اضافه شد. حالا ورود بدون رمز:

Terminal window
tmux send-keys -t t "clear" Enter
tmux send-keys -t t "ssh deploy@server" Enter; sleep 3
tmux send-keys -t t "whoami; hostname" Enter; sleep 1
tmux send-keys -t t "cut -c1-60 ~/.ssh/authorized_keys" Enter; sleep 1
tmux send-keys -t t "ls -ld ~/.ssh; ls -l ~/.ssh/authorized_keys" Enter; sleep 1
screen
tmux send-keys -t t "exit" Enter; sleep 1
خروجی
alice@server:~$ ssh deploy@server
deploy@server:~$ whoami; hostname
deploy
server
deploy@server:~$ cut -c1-60 ~/.ssh/authorized_keys
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINQPotQIugGlbNhln3bqgitw
deploy@server:~$ ls -ld ~/.ssh; ls -l ~/.ssh/authorized_keys
drwx------ 2 deploy deploy 4096 Oct 3 22:04 /home/deploy/.ssh
-rw------- 1 deploy deploy 94 Oct 3 22:04 /home/deploy/.ssh/authorized_keys
deploy@server:~$

رمز نپرسید و مستقیم وارد شد. روی سرور هم authorized_keys همان یک خط کلید عمومی است، با دسترسی‌های سخت‌گیرانه: پوشه‌ی ~/.ssh برابر 700 و فایل 600. (sshd اگر این دسترسی‌ها بازتر باشد، به آن اعتماد نمی‌کند؛ اشتباهات رایج.)

⚡ بررسی سریع

کدام کلید را روی سرور می‌گذاری و کدام فقط پیش خودت می‌ماند؟

مثال ۴: اجرای مستقیم دستور، گزینه‌ها و -v

Section titled “مثال ۴: اجرای مستقیم دستور، گزینه‌ها و -v”

بعد از ssh user@host می‌توانی یک دستور بدهی؛ روی سرور اجرا می‌شود و خروجی‌اش برمی‌گردد، بدون باز کردن shell. فلگ‌های پرکاربرد: -p پورت، -i فایل کلید مشخص، -v (verbose) برای عیب‌یابی. (BatchMode=yes یعنی هرگز رمز نپرس؛ در اسکریپت‌ها عالی است: اگر ورود بی‌رمز نشد، به‌جای گیرکردن، خطا بده.)

Terminal window
echo "--- اجرای دو دستور روی سرور:"
su - alice -c 'ssh -o BatchMode=yes deploy@server "uptime -p; df -h / | tail -1"'
echo "--- دستور با خروجی خطا و کد خروج (کد خروج دستور راه دور برمی‌گردد):"
su - alice -c 'ssh -o BatchMode=yes deploy@server "ls /no-such-dir"; echo "کد خروج: $?"'
echo "--- -v: کلید کدام فایل پیشنهاد شد و آیا پذیرفته شد؟ (فقط خط‌های مهم)"
su - alice -c 'ssh -v -o BatchMode=yes deploy@server true 2>&1' | grep -E 'Offering public key|Server accepts key' | cut -c1-110
خروجی
--- اجرای دو دستور روی سرور:
up 1 day, 3 hours, 3 minutes
overlay 453G 22G 408G 6% /
--- دستور با خروجی خطا و کد خروج (کد خروج دستور راه دور برمی‌گردد):
ls: cannot access '/no-such-dir': No such file or directory
کد خروج: 2
--- -v: کلید کدام فایل پیشنهاد شد و آیا پذیرفته شد؟ (فقط خط‌های مهم)
debug1: Offering public key: /home/alice/.ssh/id_ed25519 ED25519 SHA256:aE8UjJgY0HUkd9It+EjSl2z2nMUXEH/+Tc6xHb
debug1: Server accepts key: /home/alice/.ssh/id_ed25519 ED25519 SHA256:aE8UjJgY0HUkd9It+EjSl2z2nMUXEH/+Tc6xHb7

دو خط آخر که از -v گرفتم، همان مکالمه‌ی کلید (شکل بالا) است: «این کلید را پیشنهاد می‌کنم» (Offering) و «سرور آن را پذیرفت» (accepts). وقتی ورود بی‌رمز کار نکند، -v اولین ابزار توست.

مثال ۵: ~/.ssh/config، اسم کوتاه برای هر سرور

Section titled “مثال ۵: ~/.ssh/config، اسم کوتاه برای هر سرور”

مجبور نیستی هر بار ssh -i ... -p ... user@host.example.com بنویسی. فایل ~/.ssh/config را بساز تا برای هر سرور یک اسم مستعار (alias) داشته باشی:

/home/alice/.ssh/config
Host dev
HostName server
User deploy
IdentityFile ~/.ssh/id_ed25519
Terminal window
chown alice:alice /home/alice/.ssh/config
chmod 600 /home/alice/.ssh/config
echo "--- ssh -G dev: تنظیمات مؤثر برای آن اسم:"
su - alice -c 'ssh -G dev | grep -E "^(hostname|user|port|identityfile) "'
echo "--- حالا فقط:"
su - alice -c 'ssh -o BatchMode=yes dev "echo من $(whoami) روی $(hostname) هستم"'
خروجی
--- ssh -G dev: تنظیمات مؤثر برای آن اسم:
Pseudo-terminal will not be allocated because stdin is not a terminal.
user deploy
hostname server
port 22
identityfile ~/.ssh/id_ed25519
--- حالا فقط:
من alice روی server هستم

دستور ssh dev همه‌ی آن‌ها را از فایل می‌خواند. کلیدهای پرکاربرد هر بلوک Host:

کلید معنی
Host اسم مستعاری که روی خط فرمان می‌نویسی (می‌تواند الگو باشد: Host *.example.com)
HostName آدرس یا IP واقعی سرور
User نام کاربری روی سرور
Port پورت (پیش‌فرض ۲۲)
IdentityFile کدام کلید خصوصی
ProxyJump عبور از یک سرور واسط (bastion)
ServerAliveInterval هر چند ثانیه یک بسته‌ی زنده‌بودن بفرست (جلوگیری از قطع شدن اتصال بی‌کار)

فایل config را 600 نگه دار. بعد از این، scp و rsync و git هم همین اسم dev را می‌فهمند.

مثال ۶: انتقال فایل، scp و rsync -avz

Section titled “مثال ۶: انتقال فایل، scp و rsync -avz”

scp (secure copy) مثل cp است ولی از راه ssh؛ یک سمتش host:مسیر است. در ترمینال مجازی تا نوار پیشرفت واقعی را ببینی:

Terminal window
run_in_tmux "su - alice"
tmux send-keys -t t "echo 'سلام از لپ‌تاپ' > notes.txt" Enter; sleep 1
tmux send-keys -t t "scp notes.txt dev:/tmp/" Enter; sleep 2
tmux send-keys -t t "ssh dev cat /tmp/notes.txt" Enter; sleep 2
tmux send-keys -t t "scp dev:/tmp/notes.txt ./notes-back.txt" Enter; sleep 2
tmux send-keys -t t "ls -l notes-back.txt" Enter; sleep 1
screen
خروجی
alice@server:~$ echo 'سلام از لپ‌تاپ' > notes.txt
alice@server:~$ scp notes.txt dev:/tmp/
notes.txt 100% 28 83.4KB/s 00:00
alice@server:~$ ssh dev cat /tmp/notes.txt
سلام از لپ‌تاپ
alice@server:~$ scp dev:/tmp/notes.txt ./notes-back.txt
notes.txt 100% 28 52.6KB/s 00:00
alice@server:~$ ls -l notes-back.txt
-rw-rw-r-- 1 alice alice 28 Oct 3 22:04 notes-back.txt
alice@server:~$

scp فایل dev:/tmp/ بالا می‌برد (upload)، scp dev:/tmp/فایل . پایین می‌آورد (download) و scp -r یک پوشه را بازگشتی می‌برد. ولی برای کپی پوشه‌ها rsync -avz بهتر است: فقط تفاوت‌ها را می‌فرستد. -a (archive: دسترسی‌ها و زمان‌ها حفظ، بازگشتی)، -v پرحرف، -z فشرده‌سازی در راه:

Terminal window
su - alice -c 'mkdir -p proj/sub; echo a > proj/a.txt; echo b > proj/sub/b.txt'
echo "=== بار اول: همه‌چیز می‌رود"
su - alice -c 'rsync -avz proj/ dev:/tmp/proj-rs/'
echo "=== یک فایل تازه اضافه می‌کنم، بار دوم: فقط همان می‌رود"
su - alice -c 'echo c > proj/c.txt; rsync -avz proj/ dev:/tmp/proj-rs/'
خروجی
=== بار اول: همه‌چیز می‌رود
sending incremental file list
created directory /tmp/proj-rs
./
a.txt
sub/
sub/b.txt
sent 226 bytes received 100 bytes 652.00 bytes/sec
total size is 4 speedup is 0.01
=== یک فایل تازه اضافه می‌کنم، بار دوم: فقط همان می‌رود
sending incremental file list
c.txt
sent 194 bytes received 36 bytes 460.00 bytes/sec
total size is 6 speedup is 0.03

بار دوم فقط c.txt رفت (و اشاره‌ی ./ و sub/ برای پوشه‌ها)؛ برای پوشه‌های بزرگ این فرق شب و روز است. دو نکته‌ی مهم rsync:

Terminal window
echo "=== --delete: فایل‌هایی که در مبدأ نیستند در مقصد هم پاک شوند. اول با --dry-run (فقط نشان بده):"
su - alice -c 'rm proj/a.txt; rsync -avz --dry-run --delete proj/ dev:/tmp/proj-rs/'
echo "=== اجرای واقعی:"
su - alice -c 'rsync -avz --delete proj/ dev:/tmp/proj-rs/'
echo "=== اسلش آخر مبدأ (proj/ در برابر proj):"
su - alice -c 'rsync -a proj dev:/tmp/rs-no-slash/ && ssh dev "ls /tmp/rs-no-slash"'
خروجی
=== --delete: فایل‌هایی که در مبدأ نیستند در مقصد هم پاک شوند. اول با --dry-run (فقط نشان بده):
sending incremental file list
deleting a.txt
sent 130 bytes received 29 bytes 318.00 bytes/sec
total size is 4 speedup is 0.03 (DRY RUN)
=== اجرای واقعی:
sending incremental file list
deleting a.txt
sent 130 bytes received 29 bytes 318.00 bytes/sec
total size is 4 speedup is 0.03
=== اسلش آخر مبدأ (proj/ در برابر proj):
proj
  • --dry-run (-n) فقط می‌گوید چه می‌کرد؛ قبل از هر --delete حتماً بزن.
  • اسلش آخر مبدأ مهم است: proj/ یعنی «محتویات پوشه»، و proj یعنی «خود پوشه» (در مقصد یک پوشه‌ی proj ساخته می‌شود).

مثال ۷: تونل، دسترسی به سرویسی که فقط روی localhost سرور است

Section titled “مثال ۷: تونل، دسترسی به سرویسی که فقط روی localhost سرور است”

در درس پورت‌ها دیدی بعضی سرویس‌ها (مثل دیتابیس) عمداً فقط روی 127.0.0.1 گوش می‌دهند و از بیرون در دسترس نیستند. با ssh می‌توانی یک تونل بسازی: پورت یک سرویس سرور را روی ماشین خودت در دسترس می‌کند، از داخل کانال رمزنگاری‌شده. -L محلی:میزبان:دور:

Terminal window
python3 -m http.server 8099 --bind 127.0.0.1 --directory /tmp/proj-rs > /dev/null 2>&1 &
sleep 1
echo "--- مستقیم از بیرون (آدرس شبکه‌ی سرور، پورت 8099): رد می‌شود؟"
curl -s -m 3 -o /dev/null http://server:8099 && echo "پاسخ آمد" || echo "اتصال رد شد (فقط localhost)"
echo "--- تونل: پورت 8081 من ← (از ssh) ← 127.0.0.1:8099 سرور"
su - alice -c 'ssh -f -N -L 8081:127.0.0.1:8099 dev'
sleep 1
curl -s http://127.0.0.1:8081/ | grep -oE '<li><a href="[^"]+"' | head -3
pkill -u alice -f 'ssh -f -N'
pkill -f 'http.server 8099'
خروجی
--- مستقیم از بیرون (آدرس شبکه‌ی سرور، پورت 8099): رد می‌شود؟
اتصال رد شد (فقط localhost)
--- تونل: پورت 8081 من ← (از ssh) ← 127.0.0.1:8099 سرور
<li><a href="c.txt"
<li><a href="sub/"

-N یعنی دستور اجرا نکن، فقط تونل؛ -f یعنی بعد از وصل شدن به پس‌زمینه برو. حالا 127.0.0.1:8081 روی ماشین من در واقع همان سرویس 127.0.0.1:8099 سرور است. این راه استاندارد دسترسی امن به دیتابیس و پنل‌های مدیریتی است.

مثال ۸: امن‌کردن sshd، بستن ورود با رمز

Section titled “مثال ۸: امن‌کردن sshd، بستن ورود با رمز”

وقتی همه با کلید وارد می‌شوند، ورود با رمز را می‌بندیم تا حمله‌ی حدس رمز بی‌اثر شود. تنظیمات sshd در /etc/ssh/sshd_config است و در Ubuntu هر فایل .conf در /etc/ssh/sshd_config.d/ هم خوانده می‌شود (فایل جدا بهتر از ویرایش فایل اصلی است). دو تنظیم مهم: PasswordAuthentication no و PermitRootLogin no (ورود مستقیم root ممنوع؛ با کاربر عادی وارد شو و sudo بزن):

Terminal window
cat > /etc/ssh/sshd_config.d/10-lx.conf <<'EOF'
PasswordAuthentication no
PermitRootLogin no
EOF
echo "--- تنظیمات را قبل از اعمال بررسی کن (sshd -t، بدون خروجی یعنی درست):"
sshd -t && echo "sshd -t: درست است"
systemctl reload ssh
echo "--- مقدار مؤثر:"
sshd -T | grep -E '^(passwordauthentication|permitrootlogin) '
echo "--- ورود با رمز دیگر ممکن نیست:"
su - alice -c 'ssh -o PubkeyAuthentication=no -o NumberOfPasswordPrompts=1 deploy@server true < /dev/null'
echo "--- ولی ورود با کلید سالم است:"
su - alice -c 'ssh -o BatchMode=yes dev "echo ورود با کلید"'
echo "--- آزمایش تمام شد؛ تنظیم را برمی‌دارم تا بقیه‌ی درس (که ورود با رمز می‌خواهد) کار کند:"
rm /etc/ssh/sshd_config.d/10-lx.conf
systemctl reload ssh
sshd -T | grep -E '^passwordauthentication '
خروجی
--- تنظیمات را قبل از اعمال بررسی کن (sshd -t، بدون خروجی یعنی درست):
sshd -t: درست است
--- مقدار مؤثر:
permitrootlogin no
passwordauthentication no
--- ورود با رمز دیگر ممکن نیست:
deploy@server: Permission denied (publickey).
--- ولی ورود با کلید سالم است:
ورود با کلید
--- آزمایش تمام شد؛ تنظیم را برمی‌دارم تا بقیه‌ی درس (که ورود با رمز می‌خواهد) کار کند:
passwordauthentication yes

sshd -T تنظیمات نهایی را چاپ می‌کند (با درنظرگرفتن همه‌ی فایل‌ها)، و Permission denied (publickey) یعنی سرور فقط کلید را قبول می‌کند. هشدار مهم: قبل از بستن ورود با رمز مطمئن شو ورود با کلید کار می‌کند؛ و هنگام تغییر sshd یک نشست باز را نگه دار و در نشست دوم آزمایش کن؛ اگر اشتباه کرده باشی، دیگر راه ورودی نداری (روی VPS با «کنسول» پنل ارائه‌دهنده برمی‌گردی). این را در پروژه‌ی پایانی دوره کامل انجام می‌دهیم.

پشت پرده: رمزنگاری و فایل‌ها

Section titled “پشت پرده: رمزنگاری و فایل‌ها”

اتصال SSH در دو مرحله‌ی اصلی است: اول یک کانال رمزنگاری‌شده با کلیدهای موقت ساخته می‌شود (تبادل کلید) تا کسی ترافیک را نشنود؛ بعد احراز هویت (رمز یا کلید) داخل همان کانال انجام می‌شود. این فایل‌ها نقش دارند:

فایل کجا کار
~/.ssh/id_ed25519 کلاینت کلید خصوصی تو (600)
~/.ssh/id_ed25519.pub کلاینت کلید عمومی تو
~/.ssh/known_hosts کلاینت اثرانگشت سرورهایی که قبلاً تأیید کرده‌ای
~/.ssh/config کلاینت اسم‌های مستعار و تنظیمات
~/.ssh/authorized_keys سرور (در خانه‌ی حساب) کلیدهای عمومی‌ای که اجازه‌ی ورود به آن حساب دارند
/etc/ssh/ssh_host_*_key سرور کلیدهای هویت خود سرور
/etc/ssh/sshd_config و sshd_config.d/ سرور تنظیمات سرویس

سرور هر ورود را در لاگ ثبت می‌کند. بعد از این اتصال‌ها، آخرین رویدادها (که چه کسی با چه کلیدی از کجا وارد شد):

Terminal window
journalctl -u ssh --no-pager -o cat | grep -E 'Accepted|Failed|Invalid' | tail -3 | cut -c1-130
echo "--- کلید میزبان سرور (فقط نوع و نام فایل‌ها):"
ls /etc/ssh/ssh_host_*_key.pub
خروجی
Accepted publickey for deploy from 172.17.0.4 port 47204 ssh2: ED25519 SHA256:aE8UjJgY0HUkd9It+EjSl2z2nMUXEH/+Tc6xHb7hhOU
Accepted publickey for deploy from 172.17.0.4 port 47210 ssh2: ED25519 SHA256:aE8UjJgY0HUkd9It+EjSl2z2nMUXEH/+Tc6xHb7hhOU
Accepted publickey for deploy from 172.17.0.4 port 47222 ssh2: ED25519 SHA256:aE8UjJgY0HUkd9It+EjSl2z2nMUXEH/+Tc6xHb7hhOU
--- کلید میزبان سرور (فقط نوع و نام فایل‌ها):
/etc/ssh/ssh_host_ecdsa_key.pub
/etc/ssh/ssh_host_ed25519_key.pub
/etc/ssh/ssh_host_rsa_key.pub

Accepted publickey for deploy from ... ssh2: ED25519 SHA256:... یعنی ورود با کلید و اثرانگشت همان کلید ثبت شد؛ برای ردیابی «چه کسی» عالی است. و کلیدهای میزبان در /etc/ssh/ssh_host_*: اگر این‌ها عوض شوند (مثلاً سرور را از صفر نصب کنی)، کلاینت‌ها هشدار «REMOTE HOST IDENTIFICATION HAS CHANGED» می‌بینند (اشتباهات رایج).

دستور کار
ssh user@host ورود (پورت پیش‌فرض ۲۲)
ssh -p 2222 user@host پورت دیگر
ssh -i کلید user@host کلید مشخص
ssh user@host 'دستور' اجرای یک دستور روی سرور
ssh -v user@host عیب‌یابی (-vv و -vvv جزئی‌تر)
ssh-keygen -t ed25519 -C "توضیح" ساختن جفت‌کلید
ssh-keygen -lf ~/.ssh/id_ed25519.pub اثرانگشت یک کلید
ssh-copy-id user@host نصب کلید عمومی روی سرور
ssh-keygen -R host پاک‌کردن میزبان از known_hosts
scp فایل host:مسیر / scp -r پوشه host:مسیر کپی با ssh
rsync -avz مبدأ/ host:مقصد/ همگام‌سازی فقط تفاوت‌ها
rsync -avz --delete --dry-run ... آزمایش بدون تغییر
ssh -L 8081:127.0.0.1:8099 host تونل محلی
sudo sshd -t / sudo sshd -T بررسی نحو / تنظیمات مؤثر

۱) دسترسی کلید خصوصی بازتر از 600

Section titled “۱) دسترسی کلید خصوصی بازتر از 600”
Terminal window
su - alice -c 'chmod 644 ~/.ssh/id_ed25519; ssh -o BatchMode=yes dev true; chmod 600 ~/.ssh/id_ed25519'
خروجی
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/home/alice/.ssh/id_ed25519' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/home/alice/.ssh/id_ed25519": bad permissions
deploy@server: Permission denied (publickey,password).

ssh عمداً کلیدی را که دیگران می‌خوانند رد می‌کند (Permissions 0644 ... are too open) و به‌خاطر نبودن کلید به ورود دیگر (اینجا کلید دیگری نبود) Permission denied می‌دهد. راه‌حل: chmod 600 ~/.ssh/id_ed25519 (و chmod 700 ~/.ssh).

۲) دسترسی بد روی سرور، authorized_keys نادیده گرفته می‌شود

Section titled “۲) دسترسی بد روی سرور، authorized_keys نادیده گرفته می‌شود”
Terminal window
chmod 777 /home/deploy/.ssh
echo "--- ورود با کلید (از دید کلاینت):"
su - alice -c 'ssh -o BatchMode=yes dev true'
echo "--- چرا؟ لاگ سرور:"
journalctl -u ssh --no-pager -o cat | grep 'bad ownership' | tail -1
chmod 700 /home/deploy/.ssh
echo "--- بعد از chmod 700:"
su - alice -c 'ssh -o BatchMode=yes dev "echo درست شد"'
خروجی
--- ورود با کلید (از دید کلاینت):
deploy@server: Permission denied (publickey,password).
--- چرا؟ لاگ سرور:
Authentication refused: bad ownership or modes for directory /home/deploy/.ssh
--- بعد از chmod 700:
درست شد

کلاینت فقط Permission denied (publickey) می‌بیند، ولی لاگ سرور علت واقعی را می‌گوید: Authentication refused: bad ownership or modes for directory. راه‌حل: پوشه‌ی ~/.ssh برابر 700 و authorized_keys برابر 600 و هر دو مال همان کاربر. (و یادت باشد: علت «ورود با کلید کار نمی‌کند» را اول در لاگ سرور و ssh -v بجو.)

۳) «REMOTE HOST IDENTIFICATION HAS CHANGED»

Section titled “۳) «REMOTE HOST IDENTIFICATION HAS CHANGED»”
Terminal window
echo "--- سرور از صفر نصب شد (کلیدهای میزبان تازه ساخته می‌شوند):"
rm -f /etc/ssh/ssh_host_*
ssh-keygen -A > /dev/null
systemctl restart ssh
su - alice -c 'ssh -o BatchMode=yes dev true'
خروجی
--- سرور از صفر نصب شد (کلیدهای میزبان تازه ساخته می‌شوند):
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ED25519 key sent by the remote host is
SHA256:vxDsVghlEvCE0R361l+DmMcEZZQKxJzSt6ZFYIUpQB8.
Please contact your system administrator.
Add correct host key in /home/alice/.ssh/known_hosts to get rid of this message.
Offending ECDSA key in /home/alice/.ssh/known_hosts:3
remove with:
ssh-keygen -f '/home/alice/.ssh/known_hosts' -R 'server'
Host key for server has changed and you have requested strict checking.
Host key verification failed.

ssh اثرانگشت جدید را با قدیمی در known_hosts مقایسه کرد و نخواست ادامه بدهد، چون ممکن است کسی خود را جای سرور جا زده باشد (حمله‌ی مرد میانی). اگر مطمئنی سرور واقعاً عوض شده (نصب دوباره، تغییر IP)، آن میزبان را از فایل پاک کن و دوباره وصل شو:

Terminal window
su - alice -c 'ssh-keygen -R server'
su - alice -c 'ssh -o StrictHostKeyChecking=accept-new -o BatchMode=yes dev "echo دوباره وصل شدم"'
خروجی
# Host server found: line 1
# Host server found: line 2
# Host server found: line 3
/home/alice/.ssh/known_hosts updated.
Original contents retained as /home/alice/.ssh/known_hosts.old
Warning: Permanently added 'server' (ED25519) to the list of known hosts.
دوباره وصل شدم

هرگز بدون بررسی این هشدار را رد نکن؛ آیا واقعاً سرور را تغییر داده‌اند؟

۴) Connection refused یا Permission denied (publickey)

Section titled “۴) Connection refused یا Permission denied (publickey)”
Terminal window
echo "--- پورت اشتباه (سرور روی 22 گوش می‌دهد، نه 2222):"
su - alice -c 'ssh -p 2222 -o BatchMode=yes deploy@server true'
echo "--- کلیدی که سرور نمی‌شناسد:"
su - alice -c 'ssh-keygen -q -t ed25519 -N "" -f ~/.ssh/id_other; ssh -i ~/.ssh/id_other -o IdentitiesOnly=yes -o BatchMode=yes deploy@server true'
خروجی
--- پورت اشتباه (سرور روی 22 گوش می‌دهد، نه 2222):
ssh: connect to host server port 2222: Connection refused
--- کلیدی که سرور نمی‌شناسد:
deploy@server: Permission denied (publickey,password).

Connection refused: روی آن پورت سرویسی نیست (پورت اشتباه یا sshd خاموش؛ درس پورت‌ها). Permission denied (publickey): سرور رسید و کلید را نشناخت (کلید اشتباه یا کلید عمومی‌ات روی سرور نیست). و اگر timed out بود، فایروال یا مسیر. این سه را همان‌طور که در درس پورت‌ها دیدی از هم تشخیص بده.

۵) گیرکردن بعد از قطع‌شدن اتصال

Section titled “۵) گیرکردن بعد از قطع‌شدن اتصال”

اتصال بی‌کار گاهی توسط فایروال یا NAT قطع می‌شود و ترمینال «فریز» می‌ماند. راه خروج: دکمه‌ی Enter سپس ~ و . (تیلدا و نقطه) اتصال را می‌بندد؛ و جلوگیری با ServerAliveInterval 30 در ~/.ssh/config.

✎ تمرینآسان

بدون باز کردن shell، با یک دستور ssh فقط کاربر و نام ماشین سرور را چاپ کن (از کلید و اسم dev استفاده کن).

دیدن جواب
Terminal window
su - alice -c 'ssh -o BatchMode=yes dev "whoami; hostname"'
خروجی
deploy
server
✎ تمرینمتوسط

تمرین اصلی: ورود بدون رمز با کلید به یک سرور را راه بینداز. برای کاربر جدید bob (کلاینت) یک جفت‌کلید ed25519 بساز، با ssh-copy-id (رمز آزمایشی lab-pass) روی حساب deploy بگذار و ثابت کن ssh -o BatchMode=yes (بدون رمز) کار می‌کند. (sshpass نصب نیست، پس ssh-copy-id را در ترمینال مجازی بزن.)

دیدن جواب
Terminal window
useradd -m -s /bin/bash bob
su - bob -c 'ssh-keygen -q -t ed25519 -N "" -C bob@laptop -f ~/.ssh/id_ed25519'
TH=14
run_in_tmux "su - bob"
tmux send-keys -t t "ssh-copy-id -o StrictHostKeyChecking=accept-new deploy@server" Enter; sleep 3
tmux send-keys -t t "$PW" Enter; sleep 3
screen
echo "--- آزمایش بدون رمز:"
su - bob -c 'ssh -o BatchMode=yes deploy@server "echo ورود بدون رمز کار کرد: $(whoami)"'
echo "--- کلیدهای مجاز حساب deploy (فقط نوع و توضیح):"
awk '{print $1, $NF}' /home/deploy/.ssh/authorized_keys
خروجی
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/bob/.ssh/id_ed25519.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are alr
eady installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to inst
all the new keys
deploy@server's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh -o 'StrictHostKeyChecking=accept-new' 'deploy@server'
"
and check to make sure that only the key(s) you wanted were added.
bob@server:~$
--- آزمایش بدون رمز:
ورود بدون رمز کار کرد: bob
--- کلیدهای مجاز حساب deploy (فقط نوع و توضیح):
ssh-ed25519 alice@laptop
ssh-ed25519 bob@laptop
✎ تمرینسخت

یک بکاپ ساده بساز: با rsync -avz پوشه‌ی ~/docs را از لپ‌تاپ (alice) به /tmp/backup/ روی سرور (با اسم dev) بفرست. بعد یک فایل را تغییر بده و دوباره اجرا کن و ثابت کن فقط همان فایل رفت.

دیدن جواب
Terminal window
su - alice -c 'mkdir -p ~/docs && echo "گزارش ۱" > ~/docs/report.txt && echo "یادداشت" > ~/docs/note.txt'
echo "=== بکاپ اول"
su - alice -c 'rsync -avz ~/docs/ dev:/tmp/backup/'
echo "=== تغییر یک فایل و بکاپ دوم"
su - alice -c 'echo "گزارش ۲ (ویرایش‌شده)" > ~/docs/report.txt; rsync -avz ~/docs/ dev:/tmp/backup/'
echo "=== محتوای روی سرور:"
su - alice -c 'ssh dev "cat /tmp/backup/report.txt"'
خروجی
=== بکاپ اول
sending incremental file list
created directory /tmp/backup
./
note.txt
report.txt
sent 223 bytes received 91 bytes 628.00 bytes/sec
total size is 29 speedup is 0.09
=== تغییر یک فایل و بکاپ دوم
sending incremental file list
report.txt
sent 195 bytes received 41 bytes 472.00 bytes/sec
total size is 53 speedup is 0.22
=== محتوای روی سرور:
گزارش ۲ (ویرایش‌شده)
؟ آزمونک
  1. کدام کلید را روی سرور می‌گذاری؟

  2. ssh-keygen -t ed25519 چه می‌کند؟

  3. ssh می‌گوید «Permissions 0644 for id_ed25519 are too open». چه می‌کنی؟

  4. اثرانگشت میزبان را هنگام اولین اتصال چرا بررسی می‌کنیم؟

  5. rsync -avz proj/ dev:/tmp/x/ در مقابل rsync -avz proj dev:/tmp/x/؟

  6. با PasswordAuthentication no و ورود با کلید، «Permission denied (publickey)» می‌بینی. محتمل‌ترین علت؟

  • SSH ترمینال رمزنگاری‌شده‌ی یک ماشین دور است: ssh user@host، یا با دستور: ssh user@host 'دستور'. اولین بار اثرانگشت میزبان را تأیید می‌کنی (known_hosts).
  • ورود بهتر: جفت‌کلید. ssh-keygen -t ed25519 -C "توضیح" (با passphrase)، عمومی روی سرور (ssh-copy-id user@host ← ~/.ssh/authorized_keys)، خصوصی هرگز بیرون نمی‌رود (600).
  • ~/.ssh/config با Host، HostName، User، IdentityFile، Port… اسم کوتاه می‌دهد: ssh dev.
  • فایل: scp (کپی ساده) و rsync -avz (فقط تفاوت‌ها؛ --dry-run قبل از --delete؛ اسلش آخر مبدأ مهم است). تونل: ssh -L.
  • امن‌کردن: PasswordAuthentication no، PermitRootLogin no، sshd -t قبل از reload، و همیشه یک نشست باز نگه دار.
  • عیب‌یابی: ssh -v، لاگ سرور (journalctl -u ssh)، دسترسی‌های ~/.ssh (۷۰۰/۶۰۰)، و فرق refused، timed out و Permission denied.
برگه‌ی تقلب این درس
دستورکاری که می‌کند
ssh user@hostورود
ssh -p 2222 -i ~/.ssh/key user@hostپورت و کلید مشخص
ssh user@host "uptime; df -h /"اجرای دستور روی سرور
ssh-keygen -t ed25519 -C "alice@laptop"ساختن جفت‌کلید
ssh-copy-id user@hostگذاشتن کلید عمومی روی سرور
ssh-keygen -R hostپاک‌کردن اثرانگشت قدیمی از known_hosts
ssh devبا اسم از ~/.ssh/config
scp فایل dev:/tmp/ / scp -r پوشه dev:/tmp/کپی با ssh
rsync -avz --dry-run --delete src/ dev:/dst/آزمایش همگام‌سازی
rsync -avz src/ dev:/dst/همگام‌سازی
ssh -f -N -L 8081:127.0.0.1:8099 devتونل به سرویس localhost سرور
sudo sshd -t && sudo systemctl reload sshاعمال امن تنظیمات sshd