6 คะแนน โดย GN⁺ 2023-08-24 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • SSH เป็นเครื่องมือที่ครอบคลุมตั้งแต่การเข้าถึงระยะไกล ไปจนถึง port forwarding, jump host, ไฟล์ตั้งค่า และการจัดการคีย์ โดยตัวอย่างเว็บเซิร์ฟเวอร์สามารถนำไปประยุกต์ใช้กับ บริการอื่น ๆ เช่น RDP และ SQL ได้เช่นกัน
  • local, remote และ dynamic port forwarding ตั้งค่าด้วย -L, -R, -D ตามลำดับ โดยความแตกต่างสำคัญคือพอร์ตถูกเปิดที่ฝั่งใด และทราฟฟิกไหลไปที่ไหน
  • ในเครือข่ายที่เข้าถึงโดยตรงไม่ได้ สามารถใช้ jump host -J และ ProxyJump เพื่อเชื่อมต่อผ่านหลายจุด SSH ไปยังปลายทางได้
  • ssh-agent และ agent forwarding -A ช่วยให้ใช้คีย์ในเครื่อง local ได้แม้บนโฮสต์ระยะไกล จึงสะดวก แต่ควรตรวจสอบความเสี่ยงด้านความปลอดภัยจากการใช้ agent ในทางที่ผิดก่อน
  • เมื่อใช้ ~/.ssh/config, ssh-copy-id, ssh-keygen และ SSH console ~?·~C ร่วมกัน จะช่วยลดการพิมพ์ออปชันซ้ำ ๆ และทำให้การเพิ่ม forwarding ระหว่างเซสชัน การแจกจ่าย public key รวมถึงการสร้างและตรวจสอบคีย์ทำได้ง่ายขึ้น

สิ่งที่ต้องเข้าใจก่อนเรียนรู้ SSH port forwarding

  • SSH port forwarding เข้าใจทิศทางการไหลได้ยากหากดูแค่ไดอะแกรม จึงจะเข้าใจง่ายขึ้นเมื่อดูคำสั่งจริงควบคู่กับสถานการณ์เครือข่าย
  • ตัวอย่างจะอิงกับการเข้าถึงเว็บเซิร์ฟเวอร์ แต่แนวทางเดียวกันนี้ใช้ได้กับ แทบทุกบริการ เช่น RDP, SQL เป็นต้น
  • ออปชันที่ใช้ซ้ำ ๆ มีความหมายดังนี้
    • -N: ไม่รันคำสั่งบนเซิร์ฟเวอร์ระยะไกล และไม่เปิดเชลล์
    • -f: ส่ง SSH ไปทำงานเบื้องหลัง
    • root@host: ล็อกอินด้วยผู้ใช้และโฮสต์ที่จะใช้สร้าง tunnel

local port forwarding -L

  • local port forwarding คือการส่งต่อพอร์ตของเครื่อง local ไปยังพอร์ตของเซิร์ฟเวอร์ระยะไกล
  • สถานการณ์ตัวอย่าง
    • internal-web.int โฮสต์หน้าเว็บที่เข้าถึงได้เฉพาะผ่าน loopback interface
    • จาก campfire.int สามารถเข้าถึง internal-web.int ผ่าน SSH ได้
    • ต้องการเข้าถึงเว็บเซิร์ฟเวอร์ของ internal-web.int ผ่านพอร์ต local ของ campfire.int
  • คำสั่งที่ใช้
    • ssh -N -f -L 1337:127.0.0.1:80 root@internal-web.int
  • คำอธิบายออปชัน
    • -L: ระบุ local forwarding
    • 1337:127.0.0.1:80: bind พอร์ต local 1337 เข้ากับ 127.0.0.1:80 ฝั่ง remote
  • หลังสร้าง tunnel แล้ว จาก campfire.int สามารถส่งคำขอไปยังพอร์ต local 1337 เพื่อสื่อสารกับพอร์ต 80 ของ internal-web.int ได้
  • สิ่งที่ควรจำคือใน -L พอร์ต local จะอยู่ด้านซ้าย ของที่อยู่

remote port forwarding -R

  • remote port forwarding คือการนำพอร์ตที่เข้าถึงได้จากฝั่ง local ไปเปิดเผยเป็นพอร์ตบนเซิร์ฟเวอร์ระยะไกล
  • สถานการณ์ตัวอย่าง
    • internal-web.int โฮสต์หน้าเว็บที่เข้าถึงได้เฉพาะจาก loopback
    • campfire.int เข้าถึง internal-web.int โดยตรงไม่ได้เพราะ firewall
    • vuln-server.int เข้าถึงได้จากทั้ง campfire.int และ internal-web.int
  • คำสั่งที่ใช้
    • ssh -N -f -R 3000:127.0.0.1:80 root@vuln-server.int
  • คำอธิบายออปชัน
    • -R: ระบุ remote forwarding
    • 3000:127.0.0.1:80: bind พอร์ต 3000 ของ vuln-server.int เข้ากับ 127.0.0.1:80 ฝั่ง local
  • หลังจากนั้น หากส่งคำขอ curl ไปยัง vuln-server.int:3000 ก็จะเข้าถึงหน้าเว็บภายในที่ทำงานอยู่บนพอร์ต 80 ของ internal-web.int ได้
  • สิ่งที่ควรจำคือใน -R พอร์ต local จะอยู่ด้านขวา ของที่อยู่

dynamic port forwarding -D และ SOCKS proxy

  • dynamic port forwarding คือการใช้ตัวเลือก -D เพื่อสร้าง SOCKS proxy และส่งทราฟฟิกผ่าน SSH hop
  • สถานการณ์ตัวอย่าง
    • internal-web.int โฮสต์เว็บแอปพลิเคชันที่เข้าถึงได้เฉพาะในเครือข่ายภายใน
    • vuln-server.int อยู่ในเครือข่ายภายในเดียวกัน และเข้าถึง internal-web.int ได้
    • ต้องการ proxy ทราฟฟิกจาก campfire.int ผ่าน vuln-server.int
  • การตั้งค่า /etc/proxychains.conf ต้องตรงกับพอร์ตของคำสั่ง SSH
    • socks5: ให้ proxychains ใช้ SOCKS5
    • 127.0.0.1: ใช้ localhost
    • 8080: ต้องตรงกับพอร์ตที่กำหนดใน SSH -D
  • คำสั่งที่ใช้
    • ssh -N -f -D 8080 root@vuln-server.int
  • หลังสร้าง forwarding แล้ว เมื่อตั้งค่า socks5 127.0.0.1 8080 ก็สามารถใช้ proxychains curl 192.168.1.185 เพื่อเข้าถึงหน้าเว็บภายในได้
  • DNS ผ่าน SOCKS อาจทำงานได้ไม่ดีขึ้นอยู่กับสภาพแวดล้อม ตัวอย่างจึงใช้ IP address แทน hostname
  • ใน Firefox ก็สามารถใช้ SOCKS proxy ผ่านการตั้งค่า proxy แบบ manual ได้
    • เส้นทาง: Settings → Privacy & Security → Network Settings
    • เลือก Manual proxy configuration
    • ติ๊ก “Proxy DNS when using SOCKS V5”
    • ตั้ง SOCKS host เป็น 127.0.0.1 และ port เป็น 8080

jump host -J

  • jump host ช่วยให้เชื่อมต่อไปยังปลายทางที่เข้าถึงโดยตรงจากโฮสต์ปัจจุบันไม่ได้ โดยผ่าน SSH hop หลายจุด
  • ตัวอย่าง chain คือ campfire.intvuln-server.intinternal-web.intdns.int
  • คำสั่งที่ใช้
    • ssh -J root@vuln-server.int,root@internal-web.int root@dns.int
  • จุดหมาย jump หลายรายการคั่นด้วย เครื่องหมายจุลภาค

agent forwarding -A

  • ssh-agent ช่วยให้เพิ่ม private key หรือ ID บนเครื่อง local ได้ด้วย ssh-add <private_key_file>
  • สามารถตรวจสอบคีย์ที่เพิ่มแล้วได้ด้วย ssh-add -l
  • เมื่อเพิ่มคีย์เข้า ssh-agent แล้ว จะสามารถเชื่อมต่อ SSH ด้วยคีย์นั้นโดยไม่ต้องป้อนรหัสผ่านซ้ำ ซึ่งมีประโยชน์ทั้งสำหรับผู้ใช้และ service account
  • agent forwarding -A ทำให้บนเครื่อง remote ที่เชื่อมต่ออยู่สามารถใช้คีย์จาก local agent ได้
  • ก่อนใช้งานควรตรวจสอบความเสี่ยงด้านความปลอดภัยจาก Zero Effort Private Key Compromise: Abusing SSH-Agent for Lateral Movement
  • คำสั่งตัวอย่าง
    • ssh -A -J root@vuln-server.int root@internal-web.int
  • คำสั่งนี้เชื่อมต่อไปยัง internal-web.int ผ่าน vuln-server.int พร้อมทำให้สามารถใช้คีย์ที่อยู่ใน SSH agent ของ campfire.int ฝั่ง local ได้
  • หลังจากนั้น เมื่อรัน ssh root@dns.int จาก internal-web.int ก็สามารถเชื่อมต่อได้โดยไม่ต้องระบุ private key หรือกรอก credentials

การจัดสรรคำสั่ง TTY -t

  • ตัวเลือก -t มีประโยชน์เมื่ออยากรันคำสั่งที่ต้องมีการโต้ตอบบนเซิร์ฟเวอร์ระยะไกลอย่างรวดเร็ว
  • ตัวอย่างคือคำสั่งที่ต้องใช้ TTY เช่น Vim หรือ top
  • คำสั่งที่ใช้
    • ssh root@internal-web.int -t top
  • เมื่อรันแล้วจะได้รับ TTY บนเซิร์ฟเวอร์ระยะไกลพร้อมคำสั่ง top

-g ที่ทำให้โฮสต์ภายนอกเชื่อมต่อเข้าพอร์ต forwarding ฝั่ง local ได้

  • ตัวเลือก -g ทำให้โฮสต์ระยะไกลสามารถเชื่อมต่อเข้าพอร์ตที่ถูก local forwarding ได้
  • คล้ายกับ local port forwarding -L แต่ต่างกันตรงที่เครื่องภายนอกก็สามารถใช้พอร์ต “local” นั้นได้
  • สถานการณ์ตัวอย่าง
    • มี shell access ไปยัง vuln-server.int
    • ต้องการ proxy การเชื่อมต่อที่เข้ามายังพอร์ต 2222 ของ vuln-server.int ไปยังพอร์ต 22 ของ internal-web.int
  • คำสั่งที่ใช้
    • ssh -N -f -g -L 2222:localhost:22 root@internal-web.int
  • คำอธิบายออปชัน
    • -g: อนุญาตให้โฮสต์ remote เชื่อมต่อเข้าพอร์ต local forwarding ได้
    • -L: ระบุ local forwarding
  • แม้จะ SSH เข้าไปที่พอร์ต 2222 ของ vuln-server.int แต่ shell จริงจะอยู่บน internal-web.int

SSH console ~? และ forwarding ระหว่างเซสชัน

  • SSH console เป็นฟีเจอร์ซ่อนที่ใช้ควบคุม SSH เองโดยไม่ต้องโต้ตอบกับระบบ remote โดยตรง
  • มีประโยชน์เมื่อ shell เสียหรือเมื่อจำเป็นต้องควบคุม SSH session เอง
  • เปิด help console ได้ด้วย ~?
  • ออปชันที่มีประโยชน์
    • ~.: ปิด SSH session ปัจจุบัน
    • ~C: เปิด SSH console เพื่อเพิ่มออปชัน forwarding ได้
  • แม้จะเชื่อมต่อกับ vuln-server.int อยู่แล้วด้วยคำสั่ง ssh ปกติ ก็สามารถกด ~C แล้วพิมพ์ -D 8080 เพื่อใช้ session นั้นเหมือนเป็น dynamic forwarding session ได้
  • หากบน campfire.int ตั้งค่า /etc/proxychains.conf ให้ใช้พอร์ต 8080 ไว้ ก็สามารถใช้ proxychains ได้เหมือนเริ่มด้วย ssh -D ตั้งแต่แรก

ไฟล์ตั้งค่า SSH ~/.ssh/config

  • ไฟล์ตั้งค่า SSH อยู่ที่ ~/.ssh/config และช่วยประหยัดเวลาโดยเก็บออปชัน SSH ที่ต้องพิมพ์ซ้ำ ๆ
  • เมื่อเชื่อมต่อ SSH ระบบจะ parse ไฟล์นี้ และหากมีการตั้งค่า host ที่ตรงกับปลายทาง ก็จะใช้ออปชันเหล่านั้น
  • argument บน command line มีลำดับความสำคัญสูงกว่าไฟล์ตั้งค่า
    • เช่น แม้ในไฟล์ตั้งค่าจะระบุผู้ใช้ของ internal-web.int เป็น root แต่ถ้ารัน ssh graham@internal-web.int ก็จะพยายามล็อกอินเป็น graham
  • ตัวอย่างการตั้งค่าพื้นฐาน
# You can put comments with a `#` at the beginning of the line only.
host internal-web.int
    User root
    IdentityFile /home/smores/ssh_agent/internal-web-no-pw
    Port 2222
  • ลำดับการประมวลผลเมื่อรัน ssh internal-web.int
    • จับคู่ internal-web.int จาก command line กับ host internal-web.int ใน ~/.ssh/config
    • หากตรงกัน จะดึงออปชันที่ไม่ได้ระบุใน command line จากไฟล์ตั้งค่ามาใช้
    • หากไม่ตรงกัน จะใช้เฉพาะออปชันที่กำหนดใน command line

คีย์เวิร์ด SSH config ที่ใช้บ่อย

  • IdentityFile /path/to/private_key
    • ระบุ private key ที่จะใช้กับโฮสต์
    • ทำหน้าที่เดียวกับ ssh -i
  • ForwardAgent
  • ProxyJump root@internal-web.int
    • ระบุเซิร์ฟเวอร์ที่จะใช้ proxy ทราฟฟิก
    • ทำหน้าที่เดียวกับตัวเลือก -J
    • ในตัวอย่าง แสดงให้เห็นว่าระบบจะขอ authentication ของ vuln-server.int ก่อน เพื่อให้ทราฟฟิกผ่านโฮสต์นั้น
  • Match
    • ใช้คีย์เวิร์ด SSH config ตามเงื่อนไข
    • ในตัวอย่าง หาก exit code ของคำสั่ง export | grep PROXYME=TRUE เป็น 0 จะใช้ ProxyJump ใต้บล็อก Match
    • หากไม่มี environment variable PROXYME จะใช้เฉพาะบล็อก host internal-web.int ปกติ
    • หลังตั้งค่า export PROXYME=TRUE แล้วรัน ssh internal-web.int เดิม ก็จะผ่าน authentication ของ vuln-server.int ก่อน จากนั้นได้รับ shell ของ internal-web.int
  • โดยทั่วไป scp และยูทิลิตีบางตัวที่ใช้ SSH เป็นฐานก็สามารถใช้ SSH config ได้เช่นกัน
    • ในสภาพแวดล้อมที่ไม่ได้ใช้อัตโนมัติ สามารถระบุได้ด้วย -F ~/.ssh/config

การคัดลอก public key ด้วย ssh-copy-id

  • ssh-copy-id เป็นยูทิลิตีขนาดเล็กสำหรับอัปโหลด public key ไปยังเซิร์ฟเวอร์อย่างรวดเร็ว
  • คำสั่งที่ใช้
    • ssh-copy-id -i internal-web root@internal-web.int
  • คำอธิบายออปชัน
    • -i internal-web: ระบุชื่อ private key ที่จะใช้สำหรับ authentication กับเซิร์ฟเวอร์
    • root@internal-web.int: ระบุเซิร์ฟเวอร์ที่จะอัปโหลด public key ไป

การสร้างและตรวจสอบคีย์ด้วย ssh-keygen

  • ssh-keygen เป็นยูทิลิตีสำหรับสร้างคู่ private key และ public key
  • โดยทั่วไปแนะนำให้ใช้ตัวเลือก -b เพื่อระบุขนาดคีย์ที่ใหญ่ขึ้น
  • ขนาดคีย์เริ่มต้นในสภาพแวดล้อมตัวอย่างคือ 3072
  • อัลกอริทึมเริ่มต้นคือ RSA แต่สามารถใช้ flag -t เพื่อระบุอัลกอริทึมอื่นได้
    • ตัวอย่าง: ssh-keygen -t ecdsa -b 521
  • สามารถตรวจสอบ fingerprint และขนาดเป็นไบต์ของคีย์ได้ด้วยคำสั่งต่อไปนี้
    • ssh-keygen -lf <file-name>

1 ความคิดเห็น

 
GN⁺ 2023-08-24
ความคิดเห็นจาก Hacker News
  • ที่นี่ขาด directive ที่เรียบง่ายอย่างน่าทึ่งไปอย่างหนึ่ง: ตั้งค่าใน sshd_config เช่น AuthorizedKeysCommand /usr/bin/php /etc/ssh/auth.php %u แล้วให้สคริปต์ดึง https://github.com/{$user}.keys ของ GitHub มาก็พอ
    แน่นอนว่าไม่ใช่โค้ดคุณภาพระดับ production แต่แสดงแก่นของแนวคิดได้
    หลังจากตรวจสอบว่าสังกัดองค์กร/กลุ่มของ GitHub หรือไม่ ถ้าผู้ใช้มีอยู่จริงและถูกแมปด้วยอย่าง nss-ato ก็สามารถอนุญาตให้ล็อกอินเข้าเซิร์ฟเวอร์ได้
    เวลา onboarding/offboarding คน เพียงเพิ่ม/ลบออกจาก กลุ่ม GitHub ก็สามารถให้หรือเพิกถอนสิทธิ์เข้าถึงเครื่องได้ ช่วยลดความยุ่งยาก

    • Amazon Linux ก็ทำสิ่งคล้ายกัน แต่คงเพราะเป็น คุณภาพระดับ production จึงซับซ้อนกว่ามาก
      ใน Amazon Linux 2 หรือต่ำกว่า จะเรียกใช้คำสั่ง openssl จาก command line เพื่อตรวจรูปแบบคีย์แต่ละตัวในไฟล์ authorized_keys แต่ถูก hardcode ไว้เป็น RSA ดังนั้นแม้เวอร์ชัน OpenSSH จะรองรับ ed25519 ก็ไม่สามารถยืนยันตัวตนด้วย ed25519 บนโฮสต์ Amazon Linux 2 ได้
      ในทางทฤษฎีมันช่วยเปิดทางให้ฟีเจอร์เจ๋ง ๆ¹ ได้ ซึ่งก็ดี แต่ในทางปฏิบัติมันกลับทำให้ฟังก์ชันพื้นฐานพังแม้กับคนที่ไม่ได้ใช้ฟีเจอร์เหล่านั้น ทำให้ไว้ใจ Amazon Linux น้อยลง
      ตอนเจอปัญหานี้ครั้งแรก ผมกำลังพยายาม SSH เข้าเครื่องที่เป็นของเพื่อนร่วมงานสาย DevOps ที่ cloud-first และเพราะผมแตะเครื่องโดยตรงไม่ได้จึงวินิจฉัยยาก
      เขารู้จัก AWS ดีแต่รู้ Linux น้อยกว่า จึงไม่รู้ว่าควรดูตรงไหน และเลือก Amazon Linux เพราะคิดว่าเป็นดิสโทรที่เจ้าของแพลตฟอร์มคลาวด์ทำเอง น่าจะ “เข้ากันได้มากกว่า” แต่ตรงนี้ “เข้ากันได้มากกว่า” จริง ๆ หมายถึง “มี surprises โง่ ๆ มากกว่า”
      ¹ https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/connect-...
    • เห็นแบบนี้แล้วก็เสียดายที่อยู่ฝั่ง network
      เพราะขอบเขตงานคือ “เครือข่ายทำงานไม่ถูกต้อง” เลยพลาดฟีเจอร์เท่ ๆ แบบนี้ไป
    • ถ้าเป็นการใช้งานแบบนี้ ควรใช้ SSH certificates แทนจะดีกว่า
  • น่าจะมีคนจำนวนมากที่ไม่รู้ว่า parser คอนฟิกของ OpenSSH จะละเว้น directive ที่ซ้ำกัน และมีผลเฉพาะ ตัวแรก ของ directive เดียวกันเท่านั้น
    โดยปกติใน parser คอนฟิกหรือ rules engine มักเป็น directive ที่มาทีหลังเขียนทับตัวก่อนหน้า จึงค่อนข้างขัดกับสัญชาตญาณ
    อาจดูเหมือนเรื่องเล็ก แต่เวลาเปลี่ยนค่า default อย่าง /etc/ssh/sshd_config คนและซอฟต์แวร์มักมีแนวโน้มจะต่อท้ายการเปลี่ยนแปลงไว้ท้ายไฟล์หรือท้ายบล็อก directive และคาดหวังว่ามันจะถูกนำไปใช้
    บริษัทหรือองค์กรด้านความปลอดภัย รวมถึงผลิตภัณฑ์ SSH bastion หลายตัวก็พลาดเรื่องนี้ และคำแนะนำ CIS Benchmarks กับเครื่องมือตรวจ audit CIS ของ third-party ส่วนใหญ่ก็ไม่คำนึงถึงลำดับความสำคัญหรือจัดการผิด
    ถ้าต้องการตรวจว่า directive คอนฟิกของ OpenSSH ถูกกำหนดตามที่คาดหรือไม่ อย่าไล่อ่านไฟล์คอนฟิกเอง แต่ควร dump คอนฟิกภายใน ที่ถูก derive ออกมาด้วย sshd -T หรือ ssh -G

    • ไฟล์ sudoers ก็ทำงานแบบเดียวกัน
      สำหรับซอฟต์แวร์ยืนยันตัวตน/อนุญาตสิทธิ์ผู้ใช้ ผมมองว่าวิธีนี้เป็นสิ่งที่พึงประสงค์
      เพราะทำให้สร้างการตั้งค่าที่ไม่สามารถถูก override ได้ง่าย แม้จะเพิ่มไฟล์ใหม่ในไดเรกทอรี whatever.conf.d
      แค่กำหนดค่านั้นในไฟล์คอนฟิกหลักก่อนโหลด whatever.conf.d/* แล้วใส่การป้องกันพิเศษให้ไฟล์นั้นไว้ก็พอ
      แม้จะไม่ใช่กรณีที่มีคนพยายามเลี่ยงการควบคุม วิธีนี้ก็เป็นประโยชน์ต่อ configuration management เพราะแม้พนักงานใหม่ที่ไม่รู้บริบททั้งหมดจะเพิ่มไฟล์ใหม่ หรือแพ็กเกจบางตัวติดตั้งค่า default แปลก ๆ สำหรับบริการของตัวเอง ค่ามาตรฐานก็ยังคงมีสิทธิก่อน
      เหตุผลที่ในบริบทอื่นเรามักเห็นพฤติกรรมตรงข้ามบ่อยกว่า คือสิ่งที่ต้องการไม่ใช่ “ค่ามาตรฐาน” แต่เป็นค่า default ในความหมายเคร่งครัดที่ใช้เมื่อผู้ใช้/นักพัฒนา/ผู้ดูแลไม่ได้ตั้งค่าไว้อย่างชัดเจน
    • directive บางตัวถือว่าซ้ำได้ เช่น AllowUsers
      แต่เมื่อวาน NixOS เปลี่ยนลำดับการ merge แบบกะทันหัน ทำให้ AllowUsers ในไฟล์ของผมถูกย้ายไปอยู่ใต้ Match ของไฟล์อื่นแล้วถูกล็อกไป
  • คำอธิบายในบทความที่ว่า -L คือ local forwarding ที่ local อยู่ทางซ้ายของ address และ -R คือ remote forwarding ที่ local port อยู่ทางขวาของ address เป็นประโยคที่สำคัญและกระชับที่สุด
    ผมสับสนกับ -L และ -R มาตั้งแต่แรก และการที่ instance ของ port ไหนเป็น local จะเปลี่ยนไปตาม L/R นั้นค่อนข้างน่ารำคาญ
    ผมเข้าใจว่า -L กับ -R เปลี่ยนทิศทางตามเจตนา กล่าวคือผู้เริ่มต้นกับผู้ตอบสนองอยู่ที่ไหน แต่ก็น่าจะโอเคถ้าให้ port:address:port หมายถึง local:binding:remote เสมอ แล้วให้ -L/-R เป็นตัวกำหนดว่าฝั่งไหนเป็นผู้รับฟังและฝั่งไหนเป็นผู้ส่ง

    • วิธีจำที่ง่ายที่สุดคือ -L รับฟังที่ local port ของตัวเลขที่ตามมาทันที และ -R รับฟังที่ remote port ของตัวเลขที่ตามมาทันที
      ส่วน host:port ที่เหลือเป็นเพียงรูปแบบทั่วไปที่บอกว่าจะเชื่อมต่อไปที่ไหน
      เนื่องจากเป็นการทำ port forwarding ผ่าน SSH tunnel จึงเป็นธรรมชาติด้วยที่ host จะถูกติดต่อจากฝั่งที่มี receiving port อยู่คนละด้านกับ tunnel
  • SSH มีฟีเจอร์ที่ไม่ค่อยเป็นที่รู้จักแต่มีประโยชน์มากอย่างหนึ่งคือ การมัลติเพล็กซ์การเชื่อมต่อ
    แทนที่จะสร้าง TCP connection ใหม่และผ่านขั้นตอนการยืนยันตัวตนใหม่ทุกครั้ง สามารถตั้งให้ใช้ connection เดิมซ้ำได้
    ตัวโปรโตคอลเองมีแนวคิดเรื่อง channel อยู่แล้ว และแต่ละ data frame จะมี metadata สำหรับแยก stream ต่าง ๆ ออกจากกัน ฟีเจอร์นี้ก็ใช้กลไกนั้น
    ข้อดีใหญ่คือ session ถัด ๆ ไปไม่ต้องทำการยืนยันตัวตนเต็มรูปแบบอีกครั้ง
    เหมาะเป็นพิเศษในกรณีที่เครื่องปลายทางไม่มี tmux อะไรทำนองนั้น และต้องใช้หลาย panel ผ่านหน้าต่าง terminal หลายอัน ยิ่งถ้าการยืนยันตัวตนมีขั้นตอนที่กินเวลาหลายวินาที เช่น passphrase หรือแตะ HSM ก็จะยิ่งรู้สึกได้ชัด
    ยังมีการตั้งค่า “คงการเชื่อมต่อไว้” ด้วย จึงไม่ต้องยืนยันตัวตนใหม่ทุกครั้งเมื่อสลับไปมาระหว่าง server ไม่กี่เครื่อง
    โดยรวมถือว่าเป็นฟีเจอร์ที่มีไว้ก็ดี แต่ไม่ได้ถึงขั้นเปลี่ยนชีวิต
    บาง server ปิดฟีเจอร์นี้ไว้ ซึ่งพอปิดจะรู้สึกถึงการไม่มีมันชัดกว่าตอนที่เปิดอยู่
    ดูเพิ่มเติม: https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing

    • ถ้า latency ระหว่าง client กับ server สูง ความแตกต่างจะชัดมาก
      SSH เป็นโปรโตคอลที่ค่อนข้างคุยเยอะ ต่างจาก TLS ที่ optimize เพื่อลดจำนวนรอบไป-กลับ และ ตัวเลือกมัลติเพล็กซ์ นี้แทบจะเป็นข้อยกเว้นเดียว
    • เมื่อผ่าน bastion host ด้วย ProxyJump จะทำให้ใช้ Ansible ได้สะดวกขึ้นมาก
  • ถ้ามี host จำนวนมากใน ~/.ssh/config สามารถใช้ directive Include ที่รองรับ wildcard เพื่อป้องกันไม่ให้ไฟล์รกเกินไปได้
    เช่น ใน ~/.ssh/config ใส่ Include config.d/*.conf แล้วแยกการตั้งค่า host, hostname, user ไว้ใน ~/.ssh/config.d/work.conf หรือ client1.conf ได้

    • directive Host ก็รองรับ wildcard เช่นกัน
      เช่น เพิ่ม host *_work แล้วใส่การตั้งค่าร่วมสำหรับ host งานทั้งหมดอย่าง host1_work ได้
    • เคล็ดลับเพิ่มเติมคือสามารถใส่ Include ไว้ข้างใน directive Host/Match ได้
      เช่น ถ้าใส่ Host proj1.*.corp กับ Include ~/.ssh/proj1.conf ใน ~/.ssh/config ก็จะวาง match แยกตาม project ไว้ใกล้ ๆ ด้านบนได้ และลดความจำเป็นต้องไล่เปิดไฟล์ย่อยจำนวนมากเวลาตรวจสอบ
  • เวลา forwarding แทบไม่ใช้ -f เลย
    มันทำให้ยากที่จะรู้ว่า forwarding ไหนยังเปิดอยู่และกำลังทำงานอยู่ จนอาจกลายเป็น ปืนใต้เท้าตัวเอง ได้
    -t เป็นทริกที่เจ๋ง และเป็นฟีเจอร์ที่ไม่เคยรู้มาก่อน
    จุดสำคัญที่มักพลาดในรายการคำสั่ง escape ของ ~ คือ แม้อยู่ใน session ซ้อนกัน ก็ยังสามารถซ้อน escape ได้ด้วย
    มีประโยชน์เวลาที่ไม่ใช้ -J ไม่ว่าด้วยเหตุผลใดก็ตาม
    รายการนี้ตรงกับสิ่งที่มีประโยชน์ดี และยังได้เรียนรู้อะไรเพิ่มอีกสองสามอย่าง

    • -t ยอดเยี่ยมมาก ผมใช้ประมาณ ssh -t my-dev-vps 'tmux new-session -A -s main' เพื่อให้ทุกครั้งที่รัน กลับไปยังตำแหน่งเดิมใน tmux session ได้ทันที
    • แม้เปิด shell หลายอันบน server ปลายทาง ก็ยังมีปัญหาเดียวกันอยู่
      นอกจากต้องไปไล่ดู process list เองแล้ว ถ้า SSH ส่งออกสถานะ forwarding ออกมาในรูปแบบที่สมเหตุสมผลได้ก็คงดี
  • ตอนนี้มี pull request ที่เพิ่มการรองรับ AF_UNIX อยู่ ถ้า merge เข้ามาแล้วจะทำให้ forwarding ที่น่าสนใจสารพัดแบบเป็นไปได้
    เพราะจะทำให้ proxy การเชื่อมต่อ SSH ผ่าน local process ใด ๆ ได้ง่ายขึ้น และ process นั้นจะจัดการส่งข้อมูลต่อไปจนถึงปลายทางระยะไกลอย่างไรก็ได้ตามต้องการ
    https://github.com/openssh/openssh-portable/pull/431

    • สิ่งที่สนใจคือ -D ที่ใช้ AF_UNIX แต่การที่ทุกอย่างสามารถทำงานบน AF_UNIX ได้ก็เป็นเรื่องดี
      ดูเหมือนว่าตั้งแต่ประมาณ 1 ปีก่อน curl สามารถใช้ AF_UNIX SOCKS ผ่าน syntax ของ ALL_PROXY แบบ socks5://localhost/path หรือ socks5h ได้แล้ว
      น่าจะเพิ่มเข้ามาเพราะ Tor ใช้ AF_UNIX SOCKS proxy
      ถ้าสามารถตั้งค่าการเข้าถึง network ด้วย permission มาตรฐานของ Unix ได้ก็คงดี และโดยส่วนตัว ถ้าผลัก TCP/IP ออกจาก kernel ได้หมดก็คงยิ่งดี
  • ตอนเห็น SSH console ครั้งแรกนี่ตกใจมาก
    เพื่อนร่วมงานโชว์ ~# ให้ดู รู้สึกเหมือนค้นพบ เมนูลับสูตรโกง ที่น่าจะโผล่มาในเกม SEGA Genesis

  • ทำไมต้องเป็น tilde? เพราะ rlogin, rsh ใช้มัน
    แล้วทำไม rlogin, rsh ถึงใช้ tilde? เพราะ cu ใช้มัน
    แล้วทำไมเป็น cu? ถ้ามี modem หรือ serial line ก็ใช้ cu สื่อสาร และต้องส่ง Hayes code แต่ถ้าใช้ escape sequence ของ Hayes code ก็จะหลุดออกไปที่ modem ดังนั้นจึงต้องมีสัญญาณแยกต่างหากสำหรับออกจาก cu
    ทำไมไม่ใช่ ^[? เพราะนั่นเป็นของ telnet
    ดังนั้นถ้าเชื่อมต่อเข้า host ผ่าน telnet แล้วต่อไปยัง modem ด้วย cu ก็ต้องมี ไวยากรณ์ escape แยกต่างหาก เพื่อกลับไปที่ cu โดยไม่หลุดออกไปที่ telnet
    สุดท้ายจึงกลายเป็นโครงสร้างที่มีไวยากรณ์ escape ซ้อนกันไม่รู้จบ
    และในทางปฏิบัติ มันไม่ใช่ tilde หรอก แต่เป็น tilde นั่นแหละ

    • ผมนึกว่าลำดับคือ CR, tilde, . มาตลอด หรือที่ผ่านมาผมใช้ผิดมาตลอด?
  • ส่วน ssh-copy-id ตอนแรกอธิบายว่าคำสั่งนี้อัปโหลด public key แล้วจู่ ๆ ก็เปลี่ยนเป็นอัปโหลด private key ดูเหมือนจะเป็น typo
    อีกอย่าง คำสั่งนี้ไม่ได้แค่อัปโหลด key เฉย ๆ แต่ append เข้า ~/.ssh/authorized_keys ด้วย จึงมีประโยชน์กว่ามาก
    สุดท้าย ในส่วน ssh-keygen จากข้อมูลที่ได้อ่านมา ช่วงนี้ ed25519 เป็นที่นิยมกว่า ecdsa