当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 使用 PowerShell 和正则表达式解析 ssh 已知主机

作者:精品下载站 日期:2024-12-14 20:38:48 浏览:14 分类:玩电脑

使用 PowerShell 和正则表达式解析 ssh 已知主机


最近,我花时间学习更多有关 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"

但是,您可能会得到如下结果:

[玩转系统] 使用 PowerShell 和正则表达式解析 ssh 已知主机

就我而言,我有一些带有端口的条目、带有方括号的主机名,以及一些带有可选 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"

现在我可以在每个条目上尝试我的正则表达式模式。

[玩转系统] 使用 PowerShell 和正则表达式解析 ssh 已知主机

每个命名捕获都是一个组。

[玩转系统] 使用 PowerShell 和正则表达式解析 ssh 已知主机

我可以轻松地获取每个命名捕获,如下所示:

$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 和正则表达式解析 ssh 已知主机

这仍然适用于散列条目。

[玩转系统] 使用 PowerShell 和正则表达式解析 ssh 已知主机

最后一个条目是纯文本,因为我更改了配置文件。但现在我有了一个可以从控制台轻松使用的 PowerShell 工具。

[玩转系统] 使用 PowerShell 和正则表达式解析 ssh 已知主机

概括

我不抱任何幻想,这是实现我的目标的最佳途径。我还知道很可能构建一个替代的正则表达式模式。但我知道,致力于此工作增强了我的正则表达式能力,最终结果是一个满足需求的 PowerShell 工具。 PowerShell 是关于管道中的对象的。理想情况下,您不想解析文本,除非您需要解析文本以创建对象,就像我对此函数所做的那样。

如果您使用 ssh,我希望您能提供该功能并尝试让我知道它对您来说效果如何。像往常一样欢迎其他关于我做了什么或为什么做的问题或评论。

您需要 登录账户 后才能发表评论

取消回复欢迎 发表评论:

关灯