Windows SSH遠程登錄

在 Windows 上開啟 SSH 遠程訪問通常需要使用到 Windows 的 OpenSSH 功能。以下是詳細的步驟說明:

檢查並安裝 OpenSSH

  1. 檢查 OpenSSH 是否已安裝

    • 打開「設定」>「應用程式」>「應用程式與功能」>「管理可選功能」。
    • 在已安裝的列表中尋找「OpenSSH 伺服器」。如果存在,則表示它已經被安裝。
  2. 安裝 OpenSSH

    • 如果沒有找到 OpenSSH 伺服器,可以在「管理可選功能」頁面點擊「新增功能」,然後在列表中找到「OpenSSH 伺服器」,點擊「安裝」。

啟動並設定 OpenSSH 服務

  1. 啟動 OpenSSH 服務

    • 安裝完成後,打開命令提示字元(以管理員身份執行)。
    • 輸入 net start sshd 來啟動 OpenSSH 服務。如果想要每次開機時自動啟動該服務,可以輸入 sc config sshd start= auto
  2. 配置防火牆

    • 確保 Windows 防火牆允許 SSH 連接。可以通過「控制台」>「系統與安全」>「Windows Defender 防火牆」>「進階設定」,然後新增入站規則,允許 TCP 埠 22 的連接。

取得 IP 位址並進行連接測試

  1. 取得 IP 位址

    • 要從另一台機器連接到這台開啟了 SSH 服務的 Windows 電腦,你需要知道它的 IP 位址。可以在命令提示字元下使用 ipconfig 命令來查看本機的 IP 位址。
  2. 連接測試

    • 在另一台電腦或行動裝置上使用 SSH 用戶端(例如:PuTTY、Termius 等)嘗試連接到你的 Windows PC,使用格式 ssh username@your_ip_address。其中 username 是你要登入的 Windows 帳戶名,your_ip_address 是你之前查到的 IP 位址。

修改配置

注意避免使用密碼登入,這是絕對的雷區。務必使用公鑰進行登入,我們需要修改設定,停用密碼登入,允許公鑰登入。

該配置文件不便修改,需要特殊權限才能修改,同時還需要保證其目錄和文件的權限為特定值,這裡推薦使用腳本進行修改。

# 檢查管理員權限
$elevated = [bool]([System.Security.Principal.WindowsPrincipal]::new(
    [System.Security.Principal.WindowsIdentity]::GetCurrent()
).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator))

if (-not $elevated) {
    Write-Error "請以管理員身份運行此腳本"
    exit 1
}

# 1. 檢查並安裝 OpenSSH 伺服器
Write-Host "正在檢查 OpenSSH 伺服器安裝狀態..."
$capability = Get-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0

if ($capability.State -ne 'Installed') {
    Write-Host "正在安裝 OpenSSH 伺服器..."
    Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 | Out-Null
}

# 2. 啟動並設定開機自啟 SSH 服務
Write-Host "正在配置 SSH 服務..."
$service = Get-Service sshd -ErrorAction SilentlyContinue
if (-not $service) {
    Write-Error "OpenSSH 服務安裝失敗"
    exit 1
}

if ($service.Status -ne 'Running') {
    Start-Service sshd
}
Set-Service sshd -StartupType Automatic

# 3. 修改配置文件
$configPath = "C:\ProgramData\ssh\sshd_config"
if (Test-Path $configPath) {
    Write-Host "正在備份原始配置文件..."
    Copy-Item $configPath "$configPath.bak" -Force
} else {
    Write-Error "找不到配置文件: $configPath"
    exit 1
}

Write-Host "正在修改 SSH 配置..."
$config = Get-Content -Path $configPath -Raw

# 啟用公鑰認證並停用密碼登入
$config = $config -replace '^#?PubkeyAuthentication .*$','PubkeyAuthentication yes' `
                  -replace '^#?PasswordAuthentication .*$','PasswordAuthentication no'

# 確保包含必要配置
if ($config -notmatch 'PubkeyAuthentication') {
    $config += "`nPubkeyAuthentication yes"
}
if ($config -notmatch 'PasswordAuthentication') {
    $config += "`nPasswordAuthentication no"
}

# 寫回配置文件
$config | Set-Content -Path $configPath -Encoding UTF8

authorized_keys 文件權限確認

# normal user
$authKeys = "$env:USERPROFILE\.ssh\authorized_keys"
icacls $authKeys /inheritance:r /grant "$($env:USERNAME):F" /grant "SYSTEM:F"
icacls "$env:USERPROFILE\.ssh" /inheritance:r /grant "$($env:USERNAME):F" /grant "SYSTEM:F"

# administrator
$adminAuth = "C:\ProgramData\ssh\administrators_authorized_keys"
icacls $adminAuth /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"

設定防火牆規則

# 允許 SSH 埠
New-NetFirewallRule -DisplayName "OpenSSH Server (sshd)" -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22

增加公鑰

普通用戶

# normal user
$userProfile = $env:USERPROFILE
$sshDir = Join-Path $userProfile ".ssh"
$authorizedKeysPath = Join-Path $sshDir "authorized_keys"
$PublicKeyPath = "D:\public_keys\id_rsa.pub"

# 建立 .ssh 目錄
if (-not (Test-Path $sshDir)) {
    New-Item -ItemType Directory -Path $sshDir | Out-Null
}

# 設定 .ssh 目錄權限
$currentUser = "$env:USERDOMAIN\$env:USERNAME"
$acl = Get-Acl $sshDir
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
    $currentUser, "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow"
)
$acl.AddAccessRule($rule)
Set-Acl $sshDir $acl

# 新增公鑰
if (Test-Path $PublicKeyPath) {
    $pubKey = Get-Content -Path $PublicKeyPath -Raw
    if ($pubKey) {
        # 確保公鑰末尾有換行符
        if (-not $pubKey.EndsWith("`n")) {
            $pubKey += "`n"
        }

        # 追加公鑰
        Add-Content -Path $authorizedKeysPath -Value $pubKey -Encoding UTF8

        # 設定文件權限
        $acl = Get-Acl $authorizedKeysPath
        $acl.SetSecurityDescriptorRule(
            (New-Object System.Security.AccessControl.FileSystemAccessRule(
                $currentUser, "FullControl", "None", "None", "Allow"
            ))
        )
        Set-Acl $authorizedKeysPath $acl
    }
} else {
    Write-Error "公鑰文件不存在: $PublicKeyPath"
    exit 1
}

# 重啟 SSH 服務
Write-Host "正在重啟 SSH 服務..."
Restart-Service sshd

管理員用戶

# administrator
$adminSshDir = "C:\ProgramData\ssh"
$adminAuthKeysPath = Join-Path $adminSshDir "administrators_authorized_keys"
$adminPublicKeyPath = "D:\public_keys\id_rsa.pub"

# 建立管理員 SSH 目錄
if (-not (Test-Path $adminSshDir)) {
    New-Item -ItemType Directory -Path $adminSshDir | Out-Null
}

# 設定管理員 SSH 目錄權限
$adminAcl = Get-Acl $adminSshDir
$adminRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
    "Administrators", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow"
)
$adminAcl.AddAccessRule($adminRule)
Set-Acl $adminSshDir $adminAcl

# 新增管理員公鑰
if (Test-Path $adminPublicKeyPath) {
    $adminPubKey = Get-Content -Path $adminPublicKeyPath -Raw
    if ($adminPubKey) {
        # 確保公鑰末尾有換行符
        if (-not $adminPubKey.EndsWith("`n")) {
            $adminPubKey += "`n"
        }

        # 追加公鑰
        Add-Content -Path $adminAuthKeysPath -Value $adminPubKey -Encoding UTF8

        # 設定文件權限
        $adminAcl = Get-Acl $adminAuthKeysPath
        $adminAcl.SetSecurityDescriptorRule(
            (New-Object System.Security.AccessControl.FileSystemAccessRule(
                "Administrators", "FullControl", "None", "None", "Allow"
            ))
        )
        Set-Acl $adminAuthKeysPath $adminAcl
    }
} else {
    Write-Error "管理員公鑰文件不存在: $adminPublicKeyPath"
    exit 1
}

# 重啟 SSH 服務
Write-Host "正在重啟 SSH 服務..."
Restart-Service sshd