Ssh Bilinen Ana Bilgisayarları PowerShell ve Normal İfadelerle Ayrıştırma
Son zamanlarda ssh hakkında daha fazla şey öğrenmek için zaman harcıyorum. Ne yazık ki ssh'yi öğrenmeye ve kullanmaya nadiren ihtiyaç duydum. Ancak elbette PowerShell 7 ve ssh tabanlı uzaktan iletişim sayesinde oyunumu geliştirmenin zamanı geldi. Ssh sunucu bileşenini Windows test sunucularıma dağıtmaya başladım (bunun hakkında başka bir gün yazacağım) ve otomasyon ve komut dosyası oluşturma çalışmalarımda ssh'yi nasıl kullanacağımı keşfetmeye başladım.
Son testler sırasında ssh'yi yeniden yüklediğim uzak bir ana bilgisayara bağlanmayı denedim. Bu, parmak izlerinin mevcut bilinen ana bilgisayar girişiyle eşleşmediğine dair bir güvenlik uyarısıyla sonuçlandı. Böylece nihayet uzak ana bilgisayar yapılandırmasının nerede saklandığını öğrenmeyi başardım. (Ssh hakkında bildiğimden daha fazlasını bilmediğimi özgürce kabul ediyorum.) Ssh istemcisi uzak ana bilgisayar bilgilerini kullanıcının ana klasörünün kökünde bulunan .ssh dizinindeki bir metin dosyasında saklar. Bu dosyaya ~\.ssh\known_hosts olarak başvurabilirsiniz.
Dosyayı bir metin düzenleyicide açarsanız şunun gibi bir şey görürsünüz:
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=Bu, Windows 10 masaüstümdeki dosyanın içeriğidir. Bazı istemcilerde içeriği şu biçimde görebilirsiniz:
|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 dosyanızda HashKnownHosts adında bir ayar vardır. Windows sistemimde bu, ana bilgisayar bilgilerinin düz metin olarak saklanmasını sağlayacak şekilde ayarlanmıştır. Aksi takdirde, bilinen_anasistemlerde hash edilmiş bir giriş elde edersiniz ve süreci tersine çevirmenin bir yolu yoktur. Devam ederken bunu aklımda tut.
Biraz araştırınca bilinen_hosts dosyasının bir yapısı olduğunu öğrendim. Ne yazık ki, boşlukla ayrılmış bir dosya kadar basit değil. Yine de bunu dosyanızda deneyebilirsiniz.
$known = "~\.ssh\known_hosts"
Import-CSV $known -Delimiter " " -Header "Hostname","Keytype","Thumbprint"Ancak şöyle sonuçlar alabilirsiniz:
Benim durumumda, bağlantı noktalarına sahip girişler, köşeli parantezli ana bilgisayar adları ve isteğe bağlı bir IP adresine sahip bazı girişler vardı. Ama önemi yok. Yapıyı bilirsem her girişi normal ifadeler kullanarak ayrıştırabilir ve özel bir nesne oluşturabilirim. Ben de öyle yaptım.
Adlandırılmış Normal İfade Yakalamaları
Süreci basitleştirmek için (dürüst olmak gerekirse!), adlandırılmış yakalamaları kullanan bir düzenli ifade modeli geliştirdim.
[regex]$rx = "^(?<host>^\S+?)((?=:))?((?<=:)(?<port>\d+))?(,(?<address>\S+))?\s(?<type>[\w-]+)\s(?<thumbprint>.*)"Model, boşluk olmayan bir grup karakterle eşleşen, ancak daha az açgözlü bir eşleşme kullanan, ana bilgisayar adı verilen adlandırılmış bir yakalamanın tanımlanmasıyla başlar.
^(?<host>^\S+?)Bu kalıbın ardından iki nokta üst üste gelebilir. Bu ileriye dönük olumlu bir bakış açısıdır.
((?=:)İkinci yakalama, isteğe bağlı olabilecek bağlantı noktası için bir yakalamadır. Başında iki nokta üst üste bulunan bir sayı grubunu arıyorum. Bu bir geriye bakış. Ve tüm bu yakalama seçeneklerdir.
((?<=:)(?<port>\d+))?Benzer şekilde, önünde virgül bulunan bir IP adresi olabilir.
(,(?<address>\S+))?Daha sonra bir boşluk (\s) ve ardından herhangi bir kelime karakteri olarak tanımladığım parmak izi türünün ve tire işaretinin gelmesi gerektiğini biliyorum.
(?<type>[\w-]+)
Başka bir boşluk (\s) var ve geri kalanı parmak izi olmalı.
(?<thumbprint>.*)Metin dosyasını işlemek için onu bir dizi diziye dönüştürmenin en kolay olduğunu buldum.
$content = (Get-Content -Path $known) -split "`n"Artık her girişte normal ifade düzenimi deneyebilirim.
Her adlandırılmış yakalama bir gruptur.
Her adlandırılmış yakalamayı şu şekilde kolayca alabilirim:
$matched.groups["host"].value -replace ":$|\[|\]", ""Ana bilgisayar için köşeli parantezleri ve sondaki iki nokta üst üste noktalarını hiçbir şeyle değiştiriyorum. Muhtemelen bunu normal ifade yakalamayla yapmanın bir yolu vardır, ancak bu yeterince karmaşıktır ve bunun gibi ekstra bir adım çok da önemli değildir. Bu tekniği özel bir nesne oluşturmak için kullanabilirim.
[pscustomobject]@{
PSTypeName = "sshKnownHost"
Hostname = $sshHost
Port = $matched.groups["port"].value
Address = $IP
Keytype = $matched.groups["type"].value
Thumbprint = $matched.groups["thumbprint"].value
}Özel nesne için sshKnownHost adlı bir tür adı tanımladım. Bunu, istersem özel format oluşturabilmem veya uzantılar yazabilmem için yaptım.
İşte GitHub'da özet olarak bulabileceğiniz tam işlev.
#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
Bu hala karma girişlerle çalışır.
Yapılandırma dosyasını değiştirdiğim için son giriş düz metindir. Ancak artık konsoldan rahatlıkla kullanabileceğim bir PowerShell aracım var.
Özet
Hedefime giden en iyi yolun bu olduğuna dair hiçbir yanılsama içinde değilim. Ayrıca alternatif bir normal ifade modeli oluşturmanın muhtemelen mümkün olduğunu da biliyorum. Ancak bunun üzerinde çalışmanın normal ifade kaslarımı geliştirdiğini biliyorum ve sonuç, bir ihtiyacı karşılayan bir PowerShell aracıdır. PowerShell tamamen işlem hattındaki nesnelerle ilgilidir. İdeal olarak, benim bu fonksiyonda yaptığım gibi, nesneler oluşturmak için metni ayrıştırmanız gerektiği durumlar dışında, metni ayrıştırmak istemezsiniz.
Eğer ssh kullanıyorsanız, umarım fonksiyonu verirsiniz ve sizin için nasıl çalıştığını bana bildirmeye çalışırsınız. Ne yaptığım ya da neden yaptığımla ilgili diğer soru ve yorumlarınızı her zamanki gibi memnuniyetle karşılarım.





