RE:NODE

SSH config ფაილი და ProxyJump: hosts, გასაღებები და jump host-ები

ავტორი RE:NODE-ის გუნდი
16 წუთის საკითხავი


SSH-ის კლიენტის მხარის კონფიგურაციის ფაილია ~/.ssh/config (Windows-ზე - C:\Users\<you>\.ssh\config). თითოეული Host ბლოკი კავშირს მოკლე სახელს აძლევს და ინახავს ყველაფერს, რასაც სხვაგვარად ყოველ ჯერზე აკრეფდი: HostName - რეალური მისამართი, User, Port, IdentityFile - გასაღები, ProxyJump - შუალედური მანქანა. ამის შემდეგ ssh web ცვლის ბრძანებას ssh -i ~/.ssh/id_work -p 2222 deploy@203.0.113.10. იგივე ეხება scp-ს, sftp-ს, rsync-ს, Git-სა და ყველა რედაქტორს, რომელიც SSH-ს იყენებს, რადგან ყველა ერთსა და იმავე ფაილს კითხულობს.

ერთი წესი ხსნის თითქმის ყველა გაუგებრობას: თითოეული პარამეტრისთვის ssh პირველ ნაპოვნ მნიშვნელობას იღებს. კონკრეტული ბლოკები ზემოთ უნდა იყოს, საერთო Host * - ბოლოში, ხოლო ბრძანების ხაზზე მითითებული პარამეტრი ფაილში ჩაწერილს ჯაბნის. თუ ეს გახსოვს, დანარჩენი გზამკვლევი ისეთი ცნობარია, საიდანაც უბრალოდ დააკოპირებ.

ეს სტატია კლიენტის მხარეზეა. გასაღებების გენერაცია, agent და სერვერზე sshd_config-ის გამკაცრება განხილულია სტატიაში SSH გასაღებები და sshd_config-ის გამკაცრება; თუ გასაღები ჯერ არ გაქვს, ჯერ ის წაიკითხე.

სად არის config ფაილი#

ორი ფაილია, ისინი თანმიმდევრობით იკითხება: ჯერ შენი, შემდეგ მთელი სისტემის.

სისტემამომხმარებლის ფაილისისტემის ფაილი
Linux, BSD~/.ssh/config/etc/ssh/ssh_config
macOS~/.ssh/config/etc/ssh/ssh_config
Windows OpenSSH%USERPROFILE%\.ssh\config%ProgramData%\ssh\ssh_config

ფაილი თავისით არ არსებობს, უნდა შექმნა. Linux-სა და macOS-ზე მასზე ჩაწერის უფლება შენ გარდა არავის უნდა ჰქონდეს, წინააღმდეგ შემთხვევაში ssh მას არ გამოიყენებს და დაწერს Bad owner or permissions on /home/you/.ssh/config:

bash
$ mkdir -p ~/.ssh && chmod 700 ~/.ssh$ touch ~/.ssh/config && chmod 600 ~/.ssh/config

დააკვირდი სახელებს: ssh_config კლიენტისაა, sshd_config (d-ით, daemon-იდან) - სერვერის. არასწორი ფაილის გასწორებით ბევრს დაუკარგავს მთელი საღამო, თან სერვერის ფაილი იმავე /etc/ssh საქაღალდეშია.

ორი ფლაგი ფაილს გამჭვირვალეს ხდის. ssh -G name კავშირის გარეშე ბეჭდავს სახელის სრულ, გადათვლილ კონფიგურაციას - ყველა პარამეტრს, ყველა ბლოკისა და ნაგულისხმევი მნიშვნელობის გათვალისწინების შემდეგ. ssh -F /dev/null name ერთვება შენი config-ის იგნორირებით და ერთ ნაბიჯში გეტყვის, პრობლემა ფაილშია თუ არა. -G OpenSSH 6.8-ში გაჩნდა, ამიტომ ყველგან მოგეცემა, სადაც კი შეხვდები.

bash
$ ssh -G web | grep -E '^(hostname|user|port|identityfile|proxyjump) 'hostname 203.0.113.10user deployport 2222identityfile ~/.ssh/id_workproxyjump none

Host ბლოკები და როგორ ირჩევს ssh მნიშვნელობას#

Host ხაზი ბლოკს იწყებს და ის გრძელდება შემდეგ Host ან Match ხაზამდე. შეწევა შენთვისაა და არა ssh-ისთვის. საკვანძო სიტყვებში რეგისტრი არ ითვლება, მნიშვნელობებში უმეტესად ითვლება, და შეგიძლია დაწერო Keyword value ან Keyword=value.

~/.ssh/config
Host web    HostName 203.0.113.10    User deploy    Port 2222    IdentityFile ~/.ssh/id_work    IdentitiesOnly yesHost *.lab    User admin    StrictHostKeyChecking accept-newHost *    ServerAliveInterval 30    AddKeysToAgent yes

როცა აკრიფებ ssh web, ssh ფაილს ზემოდან ქვემოთ გადის. თითოეული ბლოკი, რომლის ნიმუში ემთხვევა, ამატებს იმ პარამეტრებს, რომლებიც ჯერ არ უნახავს. ამიტომ web პორტს თავისი ბლოკიდან იღებს, ServerAliveInterval-ს კი - Host *-დან. თუ Host * ზემოთ იქნებოდა და მასში User root ეწერა, ყველა შემდგომი User ხაზი იგნორირებული იქნებოდა - ეს არის "ჩემი config იგნორირდება" პრობლემის კლასიკური ვარიანტი.

გამონაკლისია პარამეტრების მცირე ჯგუფი, რომლებიც პირველ მნიშვნელობას კი არ იღებენ, არამედ გროვდებიან: IdentityFile, CertificateFile, LocalForward, RemoteForward, DynamicForward და SendEnv. რამდენიმე შესაბამისი ბლოკიდან ჩაწერილი რამდენიმე IdentityFile სიას ქმნის და ისინი თანმიმდევრობით მოწმდება.

ნიმუშები შედარდება იმ სახელს, რომელიც შენ აკრიფე, და არა HostName-ს. მხარდაჭერილია * და ?, რამდენიმე ნიმუში ერთ ხაზზე და უარყოფა !-ით:

~/.ssh/config
Host web db cache    User deployHost 10.0.0.* !10.0.0.1    ProxyJump bastionHost * !github.com    ForwardAgent no

მარტო უარყოფილი ნიმუში არასდროს ემთხვევა არაფერს; ის მხოლოდ იმავე ხაზზე მყოფ სხვა ნიმუშებს გამორიცხავს. Host !github.com ცალკე ისეთი ბლოკია, რომელიც არაფერზე ვრცელდება.

Match იმ პირობებისთვის, რასაც Host ვერ გამოხატავს

Match ბლოკები სახელებს კი არა, პირობებს ამოწმებს. სასარგებლოებია host (სახელი HostName-ის გამოყენების შემდეგ), originalhost (რაც შენ აკრიფე), user, localuser, exec და all.

~/.ssh/config
# Use the office jump host only when not on the office networkMatch host 10.20.* !exec "ip route | grep -q 10.20.0.0/16"    ProxyJump gw.example.com

exec shell ბრძანებას ასრულებს და ემთხვევა, თუ ის ნულოვანი სტატუსით დასრულდა. ის ყოველ კავშირზე გაიშვება, ამიტომ სწრაფი იყოს, და გახსოვდეს, რომ ეს შენს მანქანაზე შესრულებადი shell ბრძანებაა - არასდროს ჩასვა ისეთი, რომელიც არ გესმის.

პარამეტრები, რომლებიც ცოდნად ღირს#

ეს არის საკვანძო სიტყვები, რომლებიც რეალურ config-ში ადგილს იმსახურებს. ნაგულისხმევი მნიშვნელობები OpenSSH-ისაა.

საკვანძო სიტყვანაგულისხმევირას აკეთებს
HostNameაკრეფილი სახელირეალური მისამართი ან DNS სახელი
Userშენი ლოკალური მომხმარებელიდისტანციური ანგარიში
Port22დისტანციური SSH პორტი
IdentityFileსტანდარტული id_* ფაილებიგასაღები, რომელსაც შესთავაზებს
IdentitiesOnlynoშესთავაზოს მხოლოდ დასახელებული გასაღებები
ProxyJumpარაფერიჯერ ამ hosts-ზე გაიაროს
ServerAliveInterval0 (გამორთულია)წამები keepalive-ებს შორის
ServerAliveCountMax3გამოტოვებული keepalive-ები დანებებამდე
ConnectTimeoutსისტემის TCP timeoutწამები დაკავშირების მოლოდინში
StrictHostKeyCheckingaskროგორ მოექცეს უცნობ host key-ებს

HostName ტოკენებს იღებს, ამიტომ ერთი ბლოკი მანქანების მთელ ოჯახს ემსახურება. %h არის სახელი, რომელიც შენ აკრიფე:

~/.ssh/config
Host *.prod    HostName %h.example.com

ეს ზუსტად იმას არ აკეთებს, რასაც ხალხი მოელის: ssh api.prod უკავშირდება api.prod.example.com-ს. თუ შენი რეალური სახელები .prod-ს არ შეიცავს, თითოეულ host-ს ცალკე ბლოკი დაუწერე. ჭკვიანური ნიმუშები ხაზებს ზოგავს და სიცხადეს ხარჯავს, config კი ფაილია, რომელსაც ღამის ორზე წაიკითხავ.

User ყოველ ბლოკში ღირს მითითებად, რადგან ნაგულისხმევი შენი ლოკალური ლოგინია. Windows-ის ლეპტოპზე ის ხშირად დიდი ასოთი დაწყებული სახელია ან ელფოსტის მსგავსი, რომელიც არც ერთ Linux სერვერზე არ არსებობს, შეცდომა კი უბრალო Permission denied-ია, რომელიც მომხმარებლის სახელზე არაფერს გიჩვენებს.

Port მნიშვნელოვანია ძირითადად იმ მანქანებისთვის, რომლებმაც SSH 22-დან სხვა პორტზე გადაიტანეს ლოგების დასაწყნარებლად, და ყველაფერისთვის, რაც სხვა პორტზე საკუთარ SSH-ზე მომუშავე სერვისს ამუშავებს. ბრძანების ხაზზე ssh -p 2222 ფაილს ჯაბნის, როგორც ყველა ბრძანების ხაზის პარამეტრი.

IdentityFile და IdentitiesOnly

IdentityFile კერძო გასაღებს ასახელებს და მისამართი შეიძლება ~-ით დაიწყოს. მის გარეშე ssh ~/.ssh-ში სტანდარტულ სახელებს ცდის - id_rsa, id_ecdsa, id_ed25519 და მათი security-key ვარიანტები - და ასევე ყველაფერს, რაც agent-შია ჩატვირთული.

ერთი გასაღების შემთხვევაში ეს ნაგულისხმევი ქცევა კარგია. რამდენიმეს შემთხვევაში კი იწვევს მარცხს, რომელსაც ყველა შეხვდება, ვისაც სამუშაო და პირადი გასაღები აქვს: ssh agent-ის ყველა გასაღებს სათითაოდ სთავაზობს, თითოეული შეთავაზება ავტორიზაციის მცდელობად ითვლება, და სერვერი MaxAuthTries 3-ით გთიშავს შეტყობინებით Too many authentication failures, სანამ სწორ გასაღებამდე მივა. IdentitiesOnly yes ssh-ს ეუბნება, რომ შესთავაზოს მხოლოდ ამ host-ისთვის დასახელებული გასაღებები, თუნდაც agent-ში მეტი იყოს. ჩასვი ის Host *-ში, ბოლოსკენ, და გასაღები დაასახელე ყოველ ბლოკში, რომელსაც ის სჭირდება.

IdentityFile ასევე იღებს .pub ფაილს, როცა კერძო ნაწილი სადღაც სხვაგანაა, მაგალითად password manager-ში, რომელიც agent-ის როლს ასრულებს. ssh საჯარო გასაღებს იყენებს იმის ასარჩევად, agent-ის რომელი გასაღები შესთავაზოს - ეს IdentitiesOnly-ისა და ათეულობით გასაღების მქონე agent-ის შეხამების ყველაზე სუფთა გზაა.

Host key-ების შემოწმება "yes-yes-yes" ჩვევის გარეშე

StrictHostKeyChecking accept-new (OpenSSH 7.6 და ახალი) უკითხავად ამატებს key-ებს host-ებისთვის, რომლებიც არასდროს გინახავს, და მაინც ხმამაღლა უარს ამბობს, თუ ცნობილი host-ის key შეიცვალა. ეს გონივრული პარამეტრია ერთჯერადი მანქანების ლაბორატორიისთვის და ცუდი - პროდუქციულ სერვერზე პირველად შესვლისას, სადაც შეკითხვა ერთადერთი მომენტია fingerprint-ის შესადარებლად. შეზღუდე ის ნიმუშით და ნუ ჩასვამ Host *-ში. UserKnownHostsFile host-ების ჯგუფს ცალკე ფაილზე მიმართავს, რაც გამოსადეგია სატესტო ქსელისთვის, რომელიც ყოველ კვირას თავიდან შენდება:

~/.ssh/config
Host *.lab    StrictHostKeyChecking accept-new    UserKnownHostsFile ~/.ssh/known_hosts_lab

ProxyJump და jump host-ები#

jump host (ასევე bastion) არის ერთი მანქანა, რომელსაც ინტერნეტიდან უკავშირდები და რომელიც სხვების წინ დგას, მათთან კი პირდაპირ ვერ მიხვალ: ბაზა კერძო ქსელში, თამაშის სერვერი firewall-ის უკან, რომელიც SSH-ს მხოლოდ bastion-იდან უშვებს, ან პატარა კომპანიის ყველა შიდა მანქანა. ProxyJump ამ ნახტომს უხილავს ხდის.

~/.ssh/config
Host bastion    HostName 203.0.113.10    User jump    IdentityFile ~/.ssh/id_ed25519Host db    HostName 10.0.0.20    User deploy    IdentityFile ~/.ssh/id_ed25519    ProxyJump bastion

ssh db ახლა უკავშირდება bastion-ს, სთხოვს მას TCP კავშირის გახსნას 10.0.0.20:22-ზე და ამ გვირაბში მეორე, სრულ SSH სესიას აწარმოებს. მთავარი თვისებაა ის, თუ სად ხდება ავტორიზაცია: შენს ლეპტოპზე, ბოლოდან ბოლომდე, ორივე ნახტომისთვის. bastion მხოლოდ დაშიფრულ ბაიტებს გადასცემს და არასდროს ხედავს შენს კერძო გასაღებს, agent-ს ან სესიას. სწორედ ამიტომ ჩაანაცვლა ProxyJump-მა agent forwarding მეორე მანქანასთან მისასვლელად.

SSH სესია 1მხოლოდ TCP forwardSSH სესია 2, პირველის შიგნითშენი კომპიუტერიგასაღებები აქააbastionსაჯარო, პორტი 22db10.0.0.20, კერძო
ProxyJump ორივე ავტორიზაციას შენთან ტოვებს

რაც უნდა იცოდე:

  • ის OpenSSH 7.3-ში გაჩნდა. უფრო ძველზე ექვივალენტია ProxyCommand ssh -W %h:%p bastion, რომელიც იგივეს აკეთებს და 2017 წლამდე დაწერილ უმეტეს გაკვეთილში ჩანს. ერთ host-ზე ორივეს ნუ დააყენებ; რომელსაც ssh პირველად წაიკითხავს, ის იმოქმედებს.
  • ყოველი ნახტომი config-ში იძებნება. ProxyJump bastion იყენებს bastion ბლოკის მისამართს, მომხმარებელს, პორტსა და გასაღებს. ნახტომი inline-ადაც შეგიძლია დაწერო: user@host:port.
  • ჯაჭვები მძიმეებით იწერება. ProxyJump bastion,inner ორივეს გაივლის თანმიმდევრობით და თითოეული ნახტომი ისევ config-იდან გადაითვლება.
  • ბრძანების ხაზზე ის `-J`-ა. ssh -J jump@203.0.113.10 deploy@10.0.0.20 ერთჯერადი ფორმაა, scp და sftp-იც იღებენ -J-ს.
  • `ProxyJump none` უფრო კონკრეტულ ბლოკში თიშავს jump-ს, რომელიც უფრო ქვემოთ მდებარე ფართო ნიმუშიდან მემკვიდრეობით მოდის.

სამიზნე ხედავს კავშირს, რომელიც bastion-ის კერძო მისამართიდან მოდის, ამიტომ შიდა მანქანის firewall-მა SSH უნდა დაუშვას მხოლოდ ამ მისამართიდან. თვით bastion-ზე sshd-მა TCP forwarding უნდა დაუშვას: AllowTcpForwarding no ან DisableForwarding yes მის sshd_config-ში ყველა jump-ს ამტვრევს შეტყობინებით channel 0: open failed: administratively prohibited.

bastion-ის ანგარიში, რომელსაც მხოლოდ გადაცემა შეუძლია

bastion უფრო სასარგებლოა, როცა მასზე ანგარიში forwarding-ის გარდა არაფერს აკეთებს. bastion-ზე jump მომხმარებლის authorized_keys-ში გამკაცრების გზამკვლევის გასაღების პარამეტრები ზუსტად ამას აკეთებს:

/home/jump/.ssh/authorized_keys (on the bastion)
restrict,port-forwarding,permitopen="10.0.0.20:22",permitopen="10.0.0.21:22" ssh-ed25519 AAAAC3Nza... laptop

restrict ყველა არასავალდებულოს თიშავს, port-forwarding forwarding-ს ისევ ჩართავს, permitopen კი მას ჩამოთვლილი დანიშნულებებით ზღუდავს. ამ გასაღებით მარტო ssh bastion shell-ს არ იძლევა, ssh db კი მუშაობს. მოპარული გასაღები ორ კერძო მანქანაზე ორ SSH პორტს ხსნის, რომლებსაც მაინც თავიანთი გასაღებები სჭირდება.

Keepalive-ები და გაყინული კავშირები#

სესია, რომელიც რამდენიმე წუთის უმოქმედობის შემდეგ იკეტება კი არა, ჭედავს, თითქმის ყოველთვის სახლის როუტერის, მობილური ქსელის ან კორპორატიული firewall-ის NAT ცხრილის ჩანაწერია, რომელიც ჩუმად წაიშალა. გამოსწორება კლიენტის მხარეზეა:

~/.ssh/config
Host *    ServerAliveInterval 30    ServerAliveCountMax 3

ყოველ 30 წამიან სიჩუმეში ssh დაშიფრულ არხში მოთხოვნას აგზავნის და პასუხს ელოდება. ეს ტრაფიკი შუალედურ მოწყობილობებს კავშირის დავიწყებას უშლის, ხოლო თუ ზედიზედ სამი პასუხი დაიკარგა, ssh სესიას დაახლოებით 90 წამში ხურავს და გაყინულ ტერმინალს არ ტოვებს. არ აურიო TCPKeepAlive-ში, რომელიც ნაგულისხმევად ჩართულია და TCP დონეზე მუშაობს; მისი გაყალბება უფრო ადვილია და დროის ათვლას ოპერაციული სისტემა განსაზღვრავს, ხშირად ორ საათს, რაც არაფერში გამოგადგება როუტერის წინააღმდეგ, რომელიც ხუთ წუთში ივიწყებს.

ConnectTimeout 10 სკრიპტების თანმხლები პარამეტრია. მის გარეშე გამორთულ მანქანასთან დაკავშირება ოპერაციული სისტემის TCP timeout-ს ელოდება, რაც შეიძლება წუთზე მეტი იყოს, და backup სკრიპტი, რომელიც host-ებს ციკლში გადის, პირველსავე მკვდარზე ჩერდება.

კავშირის ხელახლა გამოყენება ControlMaster-ით#

მულტიპლექსირება საშუალებას აძლევს იმავე host-თან შემდგომ სესიებს პირველის TCP კავშირზე იაროს. მეორე ssh web, ყოველი scp და Ansible-ის გაშვების ან git pull-ის ყოველი ნაბიჯი მყისიერად იწყება და თავიდან აღარ ავტორიზდება. ნელ არხზე ან jump host-ის გავლით სხვაობა ბრძანებაზე წამებია.

~/.ssh/config
Host *    ControlMaster auto    ControlPath ~/.ssh/cm-%C    ControlPersist 10m

ControlMaster auto პირველ კავშირს master-ად აქცევს და შემდგომებს მის გამოყენებას ანიჭებს. ControlPath ლოკალური socket ფაილია; %C კავშირის დეტალების (ლოკალური host, დისტანციური host, პორტი, მომხმარებელი) hash-ია, ამიტომ თითოეული დანიშნულებისთვის უნიკალურია და ყოველთვის მოკლე. ეს მნიშვნელოვანია: Unix socket-ების მისამართს დაახლოებით ასი სიმბოლოს ზღვარი აქვს, ძველი %r@%h:%p სტილი კი გრძელ hostname-ებზე ვარდება. ControlPersist 10m master-ს ბოლო სესიის დახურვის შემდეგ ათი წუთით ფონზე ღიად ტოვებს.

master-ს -O-თი მართავ:

bash
$ ssh -O check web     # is a master running for this host?Master running (pid=48213)$ ssh -O exit web      # close it and every session on it

ნაკლოვანებები რეალურია და სწორედ ამიტომ ტოვებს ზოგი ამას გამორთულს:

  • ყველაფერი ერთ კავშირს იზიარებს. თუ ქსელი გაითიშა, მასზე მყოფი ყველა სესია ერთდროულად კვდება.
  • პარამეტრებს master განსაზღვრავს. ახალი სესია სხვა პარამეტრებით, მაგალითად port forward-ით, არსებულ კავშირს უერთდება, ხოლო კავშირის დონეზე მოქმედი პარამეტრები, მათ შორის მომხმარებელი და jump-ის გზა, master-ისაა. როცა config-ის ცვლილება არაფერს ცვლის, ჯერ გაუშვი ssh -O exit.
  • socket რწმუნების საშუალებაა. ვისაც ფაილის გახსნა შეუძლია, შენს უკვე ავტორიზებულ კავშირს გამოიყენებს, ამიტომ ის საქაღალდეში უნდა იყოს, რომელსაც მხოლოდ შენ კითხულობ.
  • Windows OpenSSH მას არ უჭერს მხარს. ჩაშენებულ კლიენტს Unix-socket მულტიპლექსირება არ აქვს და ControlMaster ხაზი სიჩქარეს კი არა, შეცდომებს იძლევა. გამოიყენე Match exec დამცავი, შეინახე ცალკე ფაილში Linux-ისა და macOS-ის მანქანებისთვის, ან გამოიყენე WSL.

Windows OpenSSH: მისამართები და ხაფანგები#

Windows 10 (1809 და ახალი) და Windows 11 OpenSSH კლიენტს აგზავნიან როგორც C:\Windows\System32\OpenSSH\ssh.exe და ის იმავე ფორმატის config-ს კითხულობს. მომხმარებლის ფაილია C:\Users\<you>\.ssh\config, და ~ IdentityFile-ში მუშაობს. ინტერვალის შემცველ მისამართებს ორმაგი ბრჭყალები სჭირდება; წინა დახრილი ხაზები (/) escape-ის საკითხს სრულად გვერდს უვლის:

C:\Users\you\.ssh\config
Host web    HostName 203.0.113.10    User deploy    IdentityFile ~/.ssh/id_ed25519    IdentitiesOnly yesHost db    HostName 10.0.0.20    User deploy    IdentityFile "C:/Users/you/Key Store/id_db"    ProxyJump web

ყველა ხაფანგი სინტაქსის გარეთაა.

კოდირება. Windows PowerShell 5.1-ში echo "Host web" > config UTF-16-ს წერს და ssh ფაილს უარყოფს გაურკვეველი Bad configuration option შეცდომით პირველ ხაზზე. შექმენი ფაილი New-Item -ItemType File $HOME\.ssh\config-ით და შეცვალე რედაქტორში, რომელიც ჩვეულებრივ UTF-8-ში ინახავს. PowerShell 7 ნაგულისხმევად UTF-8-ს წერს და ამ პრობლემას არ აქვს.

გასაღებების უფლებები. Windows-ზე ssh კერძო გასაღებების ACL-ს ამოწმებს და უარყოფს იმას, რომლის წაკითხვაც სხვა ანგარიშებს შეუძლიათ, შეტყობინებით UNPROTECTED PRIVATE KEY FILE. ეს ჩვეულებრივ სხვაგან დაკოპირებულ გასაღებს ემართება. მოხსენი მემკვიდრეობით მიღებული უფლებები და მხოლოდ საკუთარ თავს მიანიჭე:

code
icacls "$HOME\.ssh\id_ed25519" /inheritance:ricacls "$HOME\.ssh\id_ed25519" /grant:r "$($env:USERNAME):(R)"

agent სერვისია. AddKeysToAgent yes არაფერს აკეთებს, სანამ OpenSSH Authentication Agent არ გაეშვება. ამაღლებული PowerShell-იდან:

code
Get-Service ssh-agent | Set-Service -StartupType AutomaticStart-Service ssh-agent

ორი ssh პროგრამა. Git for Windows საკუთარ ssh-ს აგზავნის C:\Program Files\Git\usr\bin-ში. ის იმავე ~/.ssh/config-ს კითხულობს, მაგრამ საკუთარ agent-ს იყენებს, ამიტომ Windows-ის ssh-add-ით დამატებული გასაღებები Git-ისთვის უხილავია. Git-ის სისტემურ კლიენტზე მიმართვა ამას ასწორებს: git config --global core.sshCommand "C:/Windows/System32/OpenSSH/ssh.exe". VS Code-ის Remote-SSH გაფართოებაც იმავე config ფაილს კითხულობს, ამიტომ ყველა Host სახელი, რომელსაც განსაზღვრავ, მის სიაში ჩნდება.

რეცეპტები, რომლებიც მუდმივად გჭირდება#

ორი GitHub ანგარიში ერთ მანქანაზე

GitHub გიცნობს გასაღებით, ყველა ანგარიში კი git@github.com-ად უკავშირდება, ამიტომ მეორე ანგარიშს მეორე გასაღები და alias სჭირდება:

~/.ssh/config
Host github-work    HostName github.com    User git    IdentityFile ~/.ssh/id_work    IdentitiesOnly yes

დაკლონე hostname-ის ნაცვლად alias-ით - git clone git@github-work:company/app.git - და შეამოწმე ssh -T github-work-ით. IdentitiesOnly-ის გარეშე agent შეიძლება პირადი გასაღები შესთავაზოს პირველად, GitHub მიიღებს და არასწორი ადამიანის სახელით გააგზავნი ცვლილებებს.

ბაზასთან მისვლა SSH-ით

ბაზა, რომელიც სერვერზე მხოლოდ 127.0.0.1-ზე უსმენს, შენი ლეპტოპიდან forward-ით მიიღწევა, რომელიც config-ში ინახება:

~/.ssh/config
Host web-pg    HostName 203.0.113.10    User deploy    LocalForward 15432 127.0.0.1:5432    SessionType none

ssh web-pg შემდეგ გვირაბს shell-ის გარეშე ღიად აჭერს (SessionType none OpenSSH 8.7-ს მოითხოვს; ძველ კლიენტებზე გამოიყენე ssh -N web-pg), და შენს მანქანაზე კლიენტი უკავშირდება localhost:15432-ს. ეს ხშირად უკეთესი პასუხია, ვიდრე ბაზის პორტის ინტერნეტისთვის გახსნა, რასაც PostgreSQL-ის დისტანციური კავშირები სათანადოდ განიხილავს.

ფაილის დაყოფა Include-ით

Include (OpenSSH 7.3 და ახალი) სხვა ფაილებს კითხულობს, wildcard-ებით, ფარდობითი მისამართები კი ~/.ssh-იდან აითვლება. ჩასვი ის ყველაზე ზემოთ, ბლოკების გარეთ, თორემ ის მხოლოდ იმ ბლოკზე იმოქმედებს, რომელშიც დგას:

~/.ssh/config
Include config.d/*Host *    IdentitiesOnly yes    ServerAliveInterval 30

თითო ფაილი კლიენტზე ან პროექტზე ~/.ssh/config.d/-ში გრძელ config-ს კითხვადს ხდის და საშუალებას გაძლევს პროექტის ყველა host ერთ ნაბიჯში წაშალო.

მართული სერვერები SFTP-ით

თამაშის, აპლიკაციის ან ვებ სერვერს hosting panel-ზე ჩვეულებრივ shell არ აქვს, მაგრამ მისი SFTP წვდომაც ბლოკით უკეთესდება. RE:NODE-ზე SFTP ყველა სერვერზეა ხელმისაწვდომი, მონაცემებით, რომლებიც ანგარიშზე კი არა, თითო სერვერზე გაიცემა, ამიტომ panel-ში ნაჩვენები host-ით, პორტითა და მომხმარებლის სახელით დაწერილი თითო ბლოკი sftp mc-world-ს გრძელი სტრიქონის ნაცვლად აქცევს, და config-ის მკითხველი რედაქტორები მას სახელით აჩვენებენ. პაროლი მაინც ყოველ ჯერზე მოითხოვება; ასეთ ბლოკზე გასაღების IdentitiesOnly-ით იძულებას ნუ სცდი. SFTP და ფაილ მენეჯერი კლიენტებსა და panel-ის მხარეს განიხილავს.

სრული config, საიდანაც დაიწყებ#

ყველაფერი ერთად, config ერთი VDS-ისთვის, მის უკან მყოფი კერძო მანქანისთვის და GitHub-ისთვის ასე გამოიყურება:

~/.ssh/config
Include config.d/*Host vds    HostName 203.0.113.10    User deploy    IdentityFile ~/.ssh/id_ed25519Host db    HostName 10.0.0.20    User deploy    IdentityFile ~/.ssh/id_ed25519    ProxyJump vdsHost github.com    User git    IdentityFile ~/.ssh/id_ed25519Host *    IdentitiesOnly yes    AddKeysToAgent yes    ServerAliveInterval 30    ServerAliveCountMax 3    ConnectTimeout 10    ControlMaster auto    ControlPath ~/.ssh/cm-%C    ControlPersist 10m

Windows-ზე სამი Control ხაზი წაშალე. თუ სერვერი ახალია, ახალ VDS-ზე პირველი საათი მეორე მხარეს ანგარიშსა და firewall-ს განიხილავს, ufw-ის გზამკვლევი კი გაჩვენებს, როგორ დაუშვა SSH მხოლოდ bastion-იდან, როცა jump host უკვე მზადაა.

config-ის პრობლემების აღმოფხვრა#

სიმპტომიჩვეული მიზეზი
პარამეტრები იგნორირდებაისინი ადრინდელმა ბლოკმა პირველმა დააყენა
Too many authentication failuresagent ყველა გასაღებს სთავაზობს; დაამატე IdentitiesOnly yes
Bad owner or permissionsconfig ან გასაღები სხვებისთვისაც ჩაწერადია
Bad configuration option პირველ ხაზზეPowerShell 5.1-ის UTF-16 ფაილი
administratively prohibited jump-ზეbastion-ზე forwarding გამორთულია
config-ის ცვლილება არაფერს ცვლისControlMaster კავშირი ისევ ღიაა
Could not resolve hostnameსახელს Host ბლოკი არ აქვს, ან მასში შეცდომაა

დანარჩენი ყველაფრისთვის მეთოდი იგივე სამი ნაბიჯია. ssh -G name გიჩვენებს, ssh რა პარამეტრებად თვლის. ssh -v name გიჩვენებს, config-ის რომელი ხაზები გამოიყენა, რომელი გასაღებები შესთავაზა და სად გაჩერდა კავშირი; ProxyJump-ის შემთხვევაში გამოტანა ორივე ნახტომს მოიცავს, ამიტომ წაიკითხე, რომელმა ვერ მოახერხა. ssh -F /dev/null user@address ფაილს სრულად გამორიცხავს ან დაადასტურებს.

FAQ#

იყენებს თუ არა scp, rsync და Git ჩემს SSH config-ს?

დიახ. ყველა სისტემურ ssh კლიენტს უშვებს, ის კი ~/.ssh/config-ს კითხულობს, ამიტომ Host alias მუშაობს ყველგან, სადაც hostname-ს დაწერდი: scp file web:/tmp/, rsync -a dir/ web:/srv/, git@github-work:org/repo.git. ხელსაწყოებმა, რომლებსაც საკუთარი SSH იმპლემენტაცია აქვთ, მაგალითად ზოგიერთმა GUI SFTP კლიენტმა, ფაილი შეიძლება იგნორირება გაუკეთონ ან მხოლოდ ნაწილი წაიკითხონ.

რა განსხვავებაა ProxyJump-სა და agent forwarding-ს შორის?

ProxyJump-ის დროს ორივე ავტორიზაცია შენს მანქანაზე ხდება და შუა host მხოლოდ ბაიტებს გადასცემს. agent forwarding-ის (ssh -A) დროს შუა host-ზე შედიხარ და შემდეგ იქიდან იყენებ შენს agent-ს, ამიტომ იმ მანქანაზე root-ის მქონე ნებისმიერს შეუძლია შენი გასაღებები ისესხოს, სანამ დაკავშირებული ხარ. გამოიყენე ProxyJump ყოველთვის, როცა მიზანი სხვა მანქანასთან მისვლაა.

შემიძლია ერთდროულად სხვა პორტიც გამოვიყენო და jump host-იც?

დიახ. ბლოკში Port ამ ბლოკის host-ზე ვრცელდება და თითოეული ნახტომი ცალკე გადაითვლება, ამიტომ 2222-ზე მყოფ bastion-სა და 22-ზე მყოფ სამიზნეს მხოლოდ თავისი Port ხაზები სჭირდება. inline-ად ProxyJump jump@203.0.113.10:2222 იგივეს აკეთებს.

უსაფრთხოა თუ არა ControlMaster-ის Host star-ში ჩასმა?

Linux-სა და macOS-ზე ინტერაქტიული გამოყენებისთვის უსაფრთხოა, თუ ControlPath შენს საკუთარ ~/.ssh საქაღალდეში მიუთითებს. ზოგი გრძელი ფაილების გადაცემისთვის გამოყენებულ host-ებს გამორიცხავს, რადგან ერთი გაჭედილი master მასზე მყოფ ყველა სესიას ჭედავს. Windows-ზე სრულად გამოტოვე.

რატომ მეკითხება ssh მაინც რომელი გასაღები, ან რატომ იყენებს არასწორს?

ჩვეულებრივ agent-ს რამდენიმე გასაღები უჭირავს და IdentitiesOnly არ არის დაყენებული, ან IdentityFile-ის მისამართში შეცდომაა და ssh ჩუმად ნაგულისხმევებზე გადადის. ssh -v თანმიმდევრობით ჩამოთვლის ყველა შეთავაზებულ გასაღებს, ssh -G კი აჩვენებს, რომელი identityfile ხაზები გადათვალა სინამდვილეში.

გამოგადგათ?