The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

在高度網路管制的企業內部如何設定 Git 連接 Azure Repos 上的儲存庫

我每年都會有好幾場 Azure DevOps 的企業內訓,最近的一場是在一家金融業的企業,他們的網路管制非常嚴格,但是有特別開放 Azure DevOps Services 的雲端服務,所以基本上連線是正常的,唯獨 Git 連接 Azure Repos 上的儲存庫時,就會遇到連線問題。今天這篇文章我就來說說解決方案。

Secure Git connection in a highly restricted corporate network

問題描述

這個問題主要出在金融業對於內部上網的管制,他們的網路安全設定非常嚴格,所以連接網站時的 TLS/SSL 憑證,並不是 Azure DevOps Services 官方網站的憑證,而是公司內部自簽的憑證。為了解決 TLS 連線信任問題,一般的解法通常都是透過 AD 的群組原則(Group Policy)自動安裝公司核准的根憑證,因此使用者不會感覺到憑證被換過。

不過,這招對 Git for Windows 是行不通的,因為 Git for Windows 擁有獨立的根憑證儲存區,並沒有共用 Windows 的憑證儲存區,所以即使你在 Windows 上安裝了公司的根憑證,Git for Windows 也無法正確的連接 Azure Repos 上的儲存庫。

解決方案

解決方案其實很簡單,就是讓 Git for Windows 信任公司自簽的憑證,這樣就可以正確的連接 Azure Repos 上的儲存庫了。

為了要能信任公司自簽的憑證,Git 可以透過設定 http.sslBackend 組態設定來選擇不同的受信任根憑證來源:

  1. 使用獨立的根憑證檔案來驗證 SSL/TLS 連線 (預設值)

    git config --global http.sslBackend openssl
    

    這個部分還需要搭配 http.sslCAInfo 組態設定,指定根憑證檔案的路徑,例如:

    git config --global http.sslCAInfo "$env:USERPROFILE\ca-bundle.crt"
    
  2. 使用 Windows 的內建網絡層 Secure Channel (SChannel) 來處理 HTTPS 連接

    如果企業有透過 AD 的群組原則(Group Policy)自動安裝公司核准的根憑證,這個選項就非常適合,因為 Git for Windows 會自動使用 Windows 的憑證儲存區來驗證 SSL/TLS 連線。

    git config --global http.sslBackend schannel
    

    這個設定應該是最簡單的方法,但前提就是你已經有安裝好受信任的根憑證到 Windows 的憑證儲存區。

若你是全新安裝 Git for Windows 的話,安裝過程會看到 Choosing HTTPS transport backend 這個步驟,你也可以在這邊選擇 Use the native Windows Secure Channel library 自動完成上述設定,不用安裝好之後才打指令變更設定!

Choosing HTTPS transport backend

若要使用獨立的根憑證檔案來驗證 SSL/TLS 連線,以下是完整的設定步驟:

  1. 先取得 Azure DevOps Services 的網站憑證

    我以 https://dev.azure.com/orgName/ 為例,你必須取得 dev.azure.com 的憑證,取得的方法有很多種,詳細作法請見下一個段落。

    不過,以企業內部網路環境來說,最正確的作法,應該是取得公司核發的根憑證,這樣才能保證所有公司發出的憑證你都能夠正確的連接。

  2. 複製 Git for Windows 的根憑證定義檔到你的家目錄 (Home Directory)

    Git 的根憑證定義檔可以透過以下命令查到,通常檔名為 ca-bundle.crt

    git config --system http.sslCAInfo
    

    先將該檔案複製到你的家目錄,通常是 C:\Users\YourName\,以下是我常用的 PowerShell 命令:

    $CABundlePath=$(git config --system http.sslCAInfo)
    Copy-Item -Path $CABundlePath -Destination "$env:USERPROFILE\ca-bundle.crt"
    
  3. 將公司自簽憑證加入到 ca-bundle.crt 檔案中

    該檔案是一個文字檔,且斷行符號為 LF ( \n ) 喔,寫入時需要特別注意一下!

    你可以使用以下 PowerShell 指令將公司自簽憑證加入到 ca-bundle.crt 檔案中:

    Get-Content C:\YourCert.pem | Out-File -Append -FilePath "$env:USERPROFILE\ca-bundle.crt" -Encoding ascii
    

    請將 C:\YourCert.pem 改成你的公司自簽憑證的檔案路徑,通常是 .pem.crt 副檔名,且為「文字格式」。

  4. 設定 Git 的 http.sslCAInfo 設定,指向家目錄的 ca-bundle.crt 檔案

    若使用 PowerShell 的命令如下:

    git config --global http.sslCAInfo "$env:USERPROFILE\ca-bundle.crt"
    

只要上面 4 個步驟,就可以設定好了!

無論是透過 Git 命令列工具,或是透過 Visual Studio 2022 的 Git 工具,都可以正確的連接 Azure Repos 上的儲存庫了!

取得 Azure DevOps Services 的網站憑證

以下是我提供兩個簡單的方法取得 Azure DevOps Services 的網站憑證:

  1. 透過 LinuxWSL 可以執行以下命令

    echo | openssl s_client -showcerts -servername dev.azure.com -connect dev.azure.com:443 2>/dev/null | openssl x509 -outform PEM > dev.azure.com.pem
    
  2. 透過 PowerShell 可以執行以下命令

    $domain = "dev.azure.com"
    $cert = [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
    $tcpClient = New-Object System.Net.Sockets.TcpClient($domain, 443)
    $sslStream = New-Object System.Net.Security.SslStream($tcpClient.GetStream(), $false, { $true }, $null)
    $sslStream.AuthenticateAsClient($domain)
    $cert = $sslStream.RemoteCertificate
    $certBytes = $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert)
    $pem = "-----BEGIN CERTIFICATE-----`n" + [System.Convert]::ToBase64String($certBytes, "InsertLineBreaks") + "`n-----END CERTIFICATE-----"
    $certPath = "dev.azure.com.pem"
    Set-Content -Path $certPath -Value $pem
    $sslStream.Close()
    $tcpClient.Close()
    

上述兩個命令都會產生一個 dev.azure.com.pem 檔案,內容稍有不同,但其實是同一張憑證。

我有發現 PowerShell 使用到 [System.Convert]::ToBase64String 方法,這裡設定的 InsertLineBreaks 選項,會產生每行最寬 76 字元的 Base64 編碼文字。然而 openssl x509 -outform PEM 則會產生每行最寬 64 字元的 Base64 編碼文字。只有這點差別而已,兩個命令產生的憑證檔其實是一樣的。

最後我整理最無腦的 PowerShell 設定腳本

理論上只要修改第一行的變數,你就可以在第一次設定的時候自動設定到好:

$domain = "dev.azure.com"

$cert = [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
$tcpClient = New-Object System.Net.Sockets.TcpClient($domain, 443)
$sslStream = New-Object System.Net.Security.SslStream($tcpClient.GetStream(), $false, { $true }, $null)
$sslStream.AuthenticateAsClient($domain)
$cert = $sslStream.RemoteCertificate
$certBytes = $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert)
$pem = "-----BEGIN CERTIFICATE-----`n" + [System.Convert]::ToBase64String($certBytes, "InsertLineBreaks") + "`n-----END CERTIFICATE-----"
$certPath = "server.pem"
Set-Content -Path $certPath -Value $pem
$sslStream.Close()
$tcpClient.Close()

$CABundlePath = $(git config --system http.sslCAInfo)
Copy-Item -Path $CABundlePath -Destination "$env:USERPROFILE\ca-bundle.crt"

Get-Content $certPath | Out-File -Append -FilePath "$env:USERPROFILE\ca-bundle.crt" -Encoding ascii
Remove-Item -LiteralPath $certPath

git config --global http.sslCAInfo "$env:USERPROFILE\ca-bundle.crt"

清除上述設定的 PowerShell 命令如下:

git config --global --unset http.sslCAInfo
Remove-Item -LiteralPath "$env:USERPROFILE\ca-bundle.crt"

相關連結

留言評論