การแยกวิเคราะห์โฮสต์ที่รู้จัก ssh ด้วย PowerShell และนิพจน์ทั่วไป
ช่วงนี้ฉันใช้เวลาเรียนรู้เพิ่มเติมเกี่ยวกับ ssh น่าเศร้าที่ฉันแทบไม่จำเป็นต้องเรียนรู้และใช้ ssh แต่แน่นอนว่า ด้วย PowerShell 7 และการทำงานระยะไกลแบบ ssh ถึงเวลาปรับปรุงเกมของฉันแล้ว ฉันได้เริ่มปรับใช้ส่วนประกอบเซิร์ฟเวอร์ ssh กับเซิร์ฟเวอร์ทดสอบ Windows ของฉัน (ฉันจะเขียนเกี่ยวกับเรื่องนั้นในวันอื่น) และสำรวจวิธีใช้ ssh ในการทำงานอัตโนมัติและงานเขียนสคริปต์ของฉัน
ในระหว่างการทดสอบล่าสุด ฉันพยายามเชื่อมต่อกับโฮสต์ระยะไกลที่ฉันติดตั้ง ssh อีกครั้ง ซึ่งส่งผลให้เกิดคำเตือนด้านความปลอดภัยว่ารหัสประจำตัวไม่ตรงกับรายการโฮสต์ที่รู้จักในปัจจุบัน ดังนั้น ในที่สุดฉันก็ได้เรียนรู้ว่าการกำหนดค่าโฮสต์ระยะไกลถูกจัดเก็บไว้ที่ไหน (ฉันยอมรับอย่างอิสระว่ามีอะไรที่ฉันไม่รู้เกี่ยวกับ ssh มากกว่าสิ่งที่ฉันทำ) ไคลเอนต์ ssh เก็บข้อมูลโฮสต์ระยะไกลไว้ในไฟล์ข้อความในไดเร็กทอรี .ssh ซึ่งอยู่ในรูทของโฮมโฟลเดอร์ของผู้ใช้ คุณสามารถอ้างอิงไฟล์นี้เป็น ~\.ssh\known_hosts
หากคุณเปิดไฟล์ในโปรแกรมแก้ไขข้อความ คุณจะเห็นสิ่งนี้:
localhost ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGCbSDjrVNB/oGITy7qKovJam+k2HKYCtJzyiYTjevW/mYIF5umy/1eOG7Nb2AtNgpI0p6ahZtChttdT/hcZfAU=
192.168.3.199 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOZuPhlOpxsbkWNT2YKr4OgjSz05B0CYfHbmPlFeshOVo3r5GaXDZ+N5aRfbbPf6VYqMK/XPgO7VQ2rNcHdAxOw=
192.168.3.200 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOmQ3X0Xa74ntBTAbTwCK64ZkQqkU2q0xBNWAp00xAMbSNwAvWMxysq6zWkHUx0/+yS/Rewxs8vjiHpzhuf+f5A=นี่คือเนื้อหาจากไฟล์บนเดสก์ท็อป Windows 10 ของฉัน ในไคลเอนต์บางราย คุณอาจเห็นเนื้อหาในรูปแบบนี้:
|1|p+R0sPINIOBsfxHaXM42p+ndtJY=|6TxeZvVc26Emf4yLsKnLJjdxd2w= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBO5YNHNaeOKpLorx4BqqXRBr/kK2+ZEd1OKHMSmozLLt7yS4hEHVG5Dg71qBVHZiE1dIUxeMmNEgexix4Odj/hM=
|1|0yLDq/BJ0VCDcNlshrO5ngGMLOQ=|hhWDD/C5KvfhpYzA+6q6XgEgMMA= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHswks7/UNgwKica/vF0cswPYoEVuMznbLKXLRXs+WWAULXZeEcUEgHm5NDeHGs+oU+7UeIoQ8xjiwl8T7MF/iw=ในไฟล์ ssh_config ของคุณ มีการตั้งค่าชื่อ HashKnownHosts ในระบบ Windows ของฉัน การตั้งค่านี้ถูกตั้งค่าให้ทราบ ดังนั้นข้อมูลโฮสต์จึงถูกจัดเก็บไว้ในข้อความธรรมดา มิฉะนั้น คุณจะได้รับรายการแฮชในknown_hosts และไม่มีทางที่จะย้อนกลับกระบวนการได้ โปรดจำสิ่งนี้ไว้ในขณะที่ฉันดำเนินการต่อ
หลังจากการค้นคว้าเล็กน้อย ฉันได้เรียนรู้ว่ามีโครงสร้างของไฟล์known_hosts น่าเสียดายที่มันไม่ง่ายเหมือนไฟล์ที่คั่นด้วยช่องว่าง แม้ว่าคุณจะสามารถลองสิ่งนี้กับไฟล์ของคุณได้
$known = "~\.ssh\known_hosts"
Import-CSV $known -Delimiter " " -Header "Hostname","Keytype","Thumbprint"อย่างไรก็ตาม คุณอาจได้รับผลลัพธ์ดังนี้:
ในกรณีของฉัน ฉันมีรายการที่มีพอร์ต ชื่อโฮสต์ที่มีวงเล็บเหลี่ยม และบางรายการมีที่อยู่ IP เสริม แต่ไม่ว่า. หากฉันรู้โครงสร้าง ฉันสามารถแยกวิเคราะห์แต่ละรายการโดยใช้นิพจน์ทั่วไปและสร้างออบเจ็กต์ที่กำหนดเองได้ และนั่นคือสิ่งที่ฉันทำ
ตั้งชื่อการจับภาพนิพจน์ทั่วไป
เพื่อให้กระบวนการง่ายขึ้น (โดยสุจริต!) ฉันจึงสร้างรูปแบบนิพจน์ทั่วไปที่ใช้การจับภาพที่มีชื่อ
[regex]$rx = "^(?<host>^\S+?)((?=:))?((?<=:)(?<port>\d+))?(,(?<address>\S+))?\s(?<type>[\w-]+)\s(?<thumbprint>.*)"รูปแบบเริ่มต้นด้วยการกำหนดชื่อการดักจับที่เรียกว่า host ซึ่งตรงกับกลุ่มอักขระที่ไม่ใช่ช่องว่าง แต่ใช้การจับคู่ที่โลภน้อยกว่า
^(?<host>^\S+?)รูปแบบนี้ อาจ ตามด้วยเครื่องหมายทวิภาค นี่คือการมองไปข้างหน้าในเชิงบวก
((?=:)การจับครั้งที่สองเป็นการจับสำหรับพอร์ต ซึ่งอาจเป็นทางเลือก ฉันกำลังค้นหากลุ่มตัวเลขที่นำหน้าด้วยเครื่องหมายทวิภาค นี่คือการมองไปข้างหลัง และการจับภาพทั้งหมดนี้เป็นทางเลือก
((?<=:)(?<port>\d+))?ในทำนองเดียวกัน อาจมีที่อยู่ IP นำหน้าด้วยเครื่องหมายจุลภาค
(,(?<address>\S+))?จากนั้นฉันก็รู้ว่าควรมีช่องว่าง (\s) ตามด้วยประเภทนิ้วหัวแม่มือที่ฉันกำหนดเป็นตัวอักษรและขีดกลาง
(?<type>[\w-]+)
มีช่องอื่น (\s) และที่เหลือควรเป็นรอยนิ้วหัวแม่มือ
(?<thumbprint>.*)ในการประมวลผลไฟล์ข้อความ ฉันพบว่าการเปลี่ยนให้เป็นอาร์เรย์ของสตริงนั้นง่ายที่สุด
$content = (Get-Content -Path $known) -split "`n"ตอนนี้ฉันสามารถลองใช้รูปแบบ regex ในแต่ละรายการได้แล้ว
การจับภาพแต่ละครั้งที่มีชื่อเป็นกลุ่ม
ฉันสามารถรับแต่ละการจับภาพที่มีชื่อเช่นนี้ได้อย่างง่ายดาย:
$matched.groups["host"].value -replace ":$|\[|\]", ""สำหรับโฮสต์ ฉันจะแทนที่วงเล็บเหลี่ยมและโคลอนต่อท้ายโดยไม่มีอะไรเลย อาจมีวิธีการทำเช่นนี้กับการจับนิพจน์ทั่วไป แต่มันซับซ้อนเพียงพอและขั้นตอนเพิ่มเติมเล็กน้อยเช่นนี้ก็ไม่ใช่เรื่องใหญ่ ฉันสามารถใช้เทคนิคนี้เพื่อสร้างวัตถุแบบกำหนดเองได้
[pscustomobject]@{
PSTypeName = "sshKnownHost"
Hostname = $sshHost
Port = $matched.groups["port"].value
Address = $IP
Keytype = $matched.groups["type"].value
Thumbprint = $matched.groups["thumbprint"].value
}ฉันได้กำหนดชื่อประเภทสำหรับออบเจ็กต์แบบกำหนดเองที่เรียกว่า sshKnownHost ฉันทำสิ่งนี้เพื่อที่ว่าถ้าฉันต้องการ ฉันสามารถสร้างรูปแบบที่กำหนดเองหรือประเภทส่วนขยายได้
นี่คือฟังก์ชันที่สมบูรณ์ ซึ่งคุณสามารถดูได้เป็นส่วนสำคัญบน GitHub
#requires -version 5.1
Function Get-sshKnownHost {
<#
.Synopsis
Parse ssh known hosts
.Description
This command will parse the ssh known_hosts file and write custom objects to
the pipeline. If HashKnownHosts is set to yes in ssh_config, you may not get
meaningful results.
.Parameter Hostname
Specify a hostname. Wildcards are permitted. Leave blank to see all known
ssh hosts.
.Example
PS C:\> Get-sshKnownHost
Hostname : localhost
Port :
Address :
Keytype : ecdsa-sha2-nistp256
Thumbprint : AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGCbSDjrVN
B/oGITy7qKovJam+k2HKYCtJzyiYTjevW/mYIF5umy/1eOG7Nb2AtNgpI0p6ah
ZtChttdT/hcZfAU=
Hostname : 192.168.3.199
Port :
Address :
Keytype : ecdsa-sha2-nistp256
Thumbprint : AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOZuPhlOpx
sbkWNT2YKr4OgjSz05B0CYfHbmPlFeshOVo3r5GaXDZ+N5aRfbbPf6VYqMK/XP
gO7VQ2rNcHdAxOw=
...
.Example
PS C:\> Get-KnownHost -hostname srv*
Hostname : srv1
Port :
Address : 192.168.3.50
Keytype : ecdsa-sha2-nistp256
Thumbprint : AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBP6unwvoWX
AbQ2ymqO3/TB2zSBayXP1ke2J+YxxOe57WoJ9ZEWdDyNdXwjYPzO139eVa8gFz
gPSV4DgDm/hLfYM=
Hostname : srv2
Port :
Address : 192.168.3.51
Keytype : ecdsa-sha2-nistp256
Thumbprint : AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKcUMuWoDN
oM0RojPT+p/z8F2N7pPychlc49oTvCsGH5urCaTu6R4Fu9tctxLvuPKRIpl08+
DRTnXOGI3m/wDbw=
.Notes
Learn more about PowerShell: https://jdhitsolutions.com/blog/essential-powershell-resources/
.Link
ssh-keygen
.Inputs
none
.Outputs
sshKnownHost
#>
[cmdletbinding()]
[alias("gkh")]
[Outputtype("sshKnownHost")]
Param(
[Parameter(Position = 0, HelpMessage = "Specify a hostname. Wildcards are permitted. Leave blank to see all known hosts.")]
[string]$Hostname
)
Write-Verbose "Starting $($MyInvocation.MyCommand)"
#define a list to hold host data
$data = [System.Collections.Generic.list[object]]::New()
[regex]$rx = "^(?<host>^\S+?)((?=:))?((?<=:)(?<port>\d+))?(,(?<address>\S+))?\s(?<type>[\w-]+)\s(?<thumbprint>.*)"
$known = "~\.ssh\known_hosts"
Write-Verbose "Testing for $known"
if (Test-Path $known) {
$content = (Get-Content -Path $known) -split "`n"
Write-Verbose "Found $($content.count) entries"
#process all entries even if searching for a single hostname because there
#might be multiple entries
foreach ($entry in $content) {
$matched = $rx.Match($entry)
$sshHost = $matched.groups["host"].value -replace ":$|\[|\]", ""
$IP = $matched.groups["address"].value -replace "\[|\]", ""
Write-Verbose "Processing $sshHost"
#regex named captures are case-sensitive
#I haven't perfected the regex capture so I'll manually trim and trailing : in the hostname capture
$obj = [pscustomobject]@{
PSTypeName = "sshKnownHost"
Hostname = $sshHost
Port = $matched.groups["port"].value
Address = $IP
Keytype = $matched.groups["type"].value
Thumbprint = $matched.groups["thumbprint"].value
}
#add each new object to the list
$data.Add($obj)
} #foreach entry
}
else {
Write-Warning "Can't find $known. Is the ssh client installed?"
}
if ($Hostname -AND $data.count -gt 0) {
Write-Verbose "Searching for $hostname"
$data.where({$_.hostname -like $hostname})
}
elseif ($data.count -gt 0) {
$data
}
Write-Verbose "Ending $($MyInvocation.MyCommand)"
} #close function
สิ่งนี้ยังคงใช้ได้กับรายการที่ถูกแฮช
รายการสุดท้ายเป็นข้อความธรรมดาเพราะฉันเปลี่ยนไฟล์กำหนดค่า แต่ตอนนี้ฉันมีเครื่องมือ PowerShell ที่สามารถใช้งานได้จากคอนโซลได้อย่างง่ายดาย
สรุป
ฉันไม่รู้สึกว่านี่เป็นวิธีที่ดีที่สุดในการบรรลุเป้าหมายของฉัน ฉันรู้ด้วยว่ามีความเป็นไปได้ที่จะสร้างรูปแบบ regex สำรอง แต่ฉันรู้ว่าการทำงานนี้จะช่วยเสริมสร้างกล้ามเนื้อ regex ของฉัน และผลลัพธ์ที่ได้คือเครื่องมือ PowerShell ที่เติมเต็มความต้องการ PowerShell เป็นเรื่องเกี่ยวกับวัตถุในไปป์ไลน์ ตามหลักการแล้ว คุณคงไม่อยากแยกวิเคราะห์ข้อความ ยกเว้นเมื่อคุณต้องการแยกวิเคราะห์ข้อความเพื่อสร้างออบเจ็กต์ อย่างที่ผมได้ทำกับฟังก์ชันนี้แล้ว
หากคุณใช้ ssh ฉันหวังว่าคุณจะมอบฟังก์ชันนี้และลองแจ้งให้เราทราบว่ามันทำงานอย่างไรสำหรับคุณ ยินดีต้อนรับคำถามหรือความคิดเห็นอื่นๆ เกี่ยวกับสิ่งที่ฉันทำหรือเพราะเหตุใดตามปกติ





