Weboldal keresés

Ismert ssh gazdagépek elemzése PowerShell és reguláris kifejezésekkel


Az utóbbi időben azzal töltöttem az időt, hogy többet tanuljak az ssh-ről. Sajnos ritkán volt szükségem az ssh megtanulására és használatára. De természetesen a PowerShell 7 és az ssh-alapú távvezérlés révén ideje feljavítani a játékomat. Elkezdtem telepíteni az ssh-kiszolgáló összetevőt a Windows tesztkiszolgálóira (erről egy másik napon írok), és megvizsgálom, hogyan használhatom az ssh-t az automatizálási és szkriptelési munkám során.

A legutóbbi tesztelés során megpróbáltam csatlakozni egy távoli gazdagéphez, ahol újratelepítettem az ssh-t. Ez biztonsági figyelmeztetést eredményezett, amely szerint az ujjlenyomatok nem egyeznek az aktuális ismert gazdagép-bejegyzéssel. Így végre eljutottam ahhoz, hogy megtanuljam, hol tárolják a távoli gazdagép konfigurációját. (Teljesen elismerem, hogy többet nem tudok az ssh-ről, mint amit csinálok.) Az ssh kliens a távoli gazdagép információit egy szöveges fájlban tárolja az .ssh könyvtárban, amely a felhasználó saját mappájának gyökerében található. Erre a fájlra ~\.ssh\known_hosts néven hivatkozhat.

Ha megnyitja a fájlt egy szövegszerkesztőben, valami ilyesmit fog látni:

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=

Ez a tartalom a Windows 10 asztalon lévő fájlból. Egyes ügyfeleknél a tartalom a következő formában jelenhet meg:

|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=

Az ssh_config fájlban található egy HashKnownHosts nevű beállítás. Az én Windows rendszeremen ez úgy van beállítva, hogy tudja, így a gazdagép információi egyszerű szövegben kerülnek tárolásra. Ellenkező esetben kivonatolt bejegyzést kap az ismert_hosts mezőben, és nincs mód a folyamat visszafordítására. Ezt tartsa szem előtt, miközben folytatom.

Egy kis kutatás után megtudtam, hogy van egy szerkezete az ismert_hosts fájlnak. Sajnos ez nem olyan egyszerű, mint egy szóközzel tagolt fájl. Bár ezt kipróbálhatnád a fájlodban.

$known = "~\.ssh\known_hosts"
Import-CSV $known -Delimiter " " -Header "Hostname","Keytype","Thumbprint"

Előfordulhat azonban, hogy a következő eredményeket kapja:

Az én esetemben voltak bejegyzéseim portokkal, gazdagépnevek szögletes zárójelekkel, és néhány bejegyzésem opcionális IP-címmel. De nem számít. Ha ismerem a struktúrát, minden bejegyzést reguláris kifejezésekkel elemelhetek, és létrehozhatok egy egyéni objektumot. És ezt tettem.

Reguláris kifejezések rögzítése

A folyamat egyszerűsítése érdekében (őszintén szólva!) kidolgoztam egy reguláris kifejezés-mintát, amely elnevezett rögzítéseket használ.

[regex]$rx = "^(?<host>^\S+?)((?=:))?((?<=:)(?<port>\d+))?(,(?<address>\S+))?\s(?<type>[\w-]+)\s(?<thumbprint>.*)"

A minta a host nevű elnevezésű rögzítés meghatározásával kezdődik, amely megfelel a nem szóköz karakterek csoportjának, de egy kevésbé mohó egyezést használ.

^(?<host>^\S+?)

Ezt a mintát követheti egy kettőspont. Ez egy pozitív előretekintés.

((?=:)

A második rögzítés a portra vonatkozik, amely opcionális lehet. Olyan számcsoportot keresek, amely előtt kettőspont áll. Ez egy betekintés a mögé. És ez az egész rögzítés opciókból áll.

((?<=:)(?<port>\d+))?

Hasonlóképpen előfordulhat, hogy egy IP-cím előtt vessző szerepel.

(,(?<address>\S+))?

Akkor tudom, hogy egy szóköznek (\s) kell lennie, majd az ujjlenyomat típusának, amelyet tetszőleges szókarakterként és kötőjelként definiálok.

(?<type>[\w-]+)

Van még egy szóköz (\s), a többi pedig az ujjlenyomat.

(?<thumbprint>.*)

A szövegfájl feldolgozásához úgy találtam, hogy a legegyszerűbb karakterláncok tömbjévé alakítani.

$content = (Get-Content -Path $known) -split "`n"

Most minden bejegyzésnél kipróbálhatom a regex-mintát.

Minden elnevezett rögzítés egy csoport.

Könnyen megszerezhetem az egyes elnevezett felvételeket, így:

$matched.groups["host"].value -replace ":$|\[|\]", ""

A házigazda esetében a szögletes zárójeleket és az esetleges kettőspontokat lecserélem, semmivel. Valószínűleg van mód erre a reguláris kifejezés rögzítésével, de ez elég bonyolult, és egy ilyen kis plusz lépés nem nagy baj. Ezzel a technikával egyéni objektumot készíthetek.

[pscustomobject]@{
 PSTypeName = "sshKnownHost"
 Hostname   = $sshHost
 Port       = $matched.groups["port"].value
 Address    = $IP
 Keytype    = $matched.groups["type"].value
 Thumbprint = $matched.groups["thumbprint"].value
 }

Meghatároztam egy típusnevet az sshKnownHost nevű egyéni objektumhoz. Ezt azért tettem, hogy ha akarok, létrehozhassak egyéni formátumot vagy gépelhessek bővítményeket.

Itt van a teljes funkció, amelyet a GitHubon lényegében találhat meg.

#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

Ez továbbra is működik kivonatolt bejegyzésekkel.

Az utolsó bejegyzés egyszerű szöveges, mert megváltoztattam a konfigurációs fájlt. De most van egy PowerShell eszközöm, amelyet egyszerűen használhatok a konzolról.

Összegzés

Nincsenek illúzióim, hogy ez a legjobb út a célomhoz. Azt is tudom, hogy valószínűleg lehetséges egy alternatív regex minta létrehozása. De tudom, hogy ezen dolgozva felépítették a regex izmaim, és a végeredmény egy PowerShell eszköz, amely kielégíti az igényeket. A PowerShell a folyamatban lévő objektumokról szól. Ideális esetben nem szeretne szöveget elemezni, kivéve, ha objektumok létrehozásához szöveget kell elemeznie, ahogyan ezt ezzel a funkcióval tettem.

Ha ssh-t használ, remélem megadja a funkciót, és megpróbálja elmondani, hogyan működik az Ön számára. Egyéb kérdéseket vagy megjegyzéseket azzal kapcsolatban, hogy mit csináltam vagy miért, a szokásos módon várjuk.

Kapcsolódó cikkek