ค้นหาเว็บไซต์

การแยกวิเคราะห์โฮสต์ที่รู้จัก 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 ฉันหวังว่าคุณจะมอบฟังก์ชันนี้และลองแจ้งให้เราทราบว่ามันทำงานอย่างไรสำหรับคุณ ยินดีต้อนรับคำถามหรือความคิดเห็นอื่นๆ เกี่ยวกับสิ่งที่ฉันทำหรือเพราะเหตุใดตามปกติ

บทความที่เกี่ยวข้อง