azure-sdk-for-cpp/eng/common/scripts/LoginToGH.ps1
2025-10-14 22:31:12 +00:00

157 lines
5.3 KiB
PowerShell

<#
.SYNOPSIS
Mints a GitHub App installation access token using Azure Key Vault 'sign' (non-exportable key),
and logs in the GitHub CLI by setting GH_TOKEN (and GITHUB_TOKEN).
.PARAMETER KeyVaultName
Name of the Azure Key Vault containing the non-exportable RSA key.
.PARAMETER KeyName
Name of the RSA key in Key Vault (imported as a *key*, not a secret).
.PARAMETER GitHubAppId
Numeric App ID (not client ID) of your GitHub App.
.PARAMETER PipelineVariableName
Name of the ADO variable to set when -SetPipelineVariable is used (default: GH_TOKEN).
.OUTPUTS
Writes minimal info to stdout. Token is placed in $env:GH_TOKEN and $env:GITHUB_TOKEN.
.NOTES
Requires: Azure CLI (az), git (optional), gh (optional if ShowGhAuthStatus is used).
#>
[CmdletBinding()]
param(
[string] $KeyVaultName = "azuresdkengkeyvault",
[string] $KeyName = "azure-sdk-automation",
[string] $GitHubAppId = '1086291',
[string] $InstallationTokenOwner = "Azure",
[string] $PipelineVariableName = "GH_TOKEN"
)
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
$GitHubApiBaseUrl = "https://api.github.com"
$GitHubApiVersion = "2022-11-28"
function Get-Headers {
param(
[Parameter(Mandatory)][string] $Jwt,
[Parameter(Mandatory)][string] $ApiVersion
)
return @{
'Authorization' = "Bearer $Jwt"
'Accept' = 'application/vnd.github+json'
'X-GitHub-Api-Version' = $ApiVersion
'User-Agent' = 'ado-pwsh-ghapp'
}
}
function New-GitHubAppJwt {
param(
[Parameter(Mandatory)] [string] $VaultName,
[Parameter(Mandatory)] [string] $KeyName,
[Parameter(Mandatory)] [string] $AppId
)
function Base64UrlEncode($json) {
$bytes = [System.Text.Encoding]::UTF8.GetBytes($json)
$base64 = [Convert]::ToBase64String($bytes)
return $base64.TrimEnd('=') -replace '\+', '-' -replace '/', '_'
}
# === STEP 1: Create JWT Header and Payload ===
$Header = @{
alg = "RS256"
typ = "JWT"
}
$Now = [int][double]::Parse((Get-Date -UFormat %s))
$Payload = @{
iat = $Now
exp = $Now + 600 # 10 minutes
iss = $AppId
}
$EncodedHeader = Base64UrlEncode (ConvertTo-Json $Header -Compress)
$EncodedPayload = Base64UrlEncode (ConvertTo-Json $Payload -Compress)
$UnsignedToken = "$EncodedHeader.$EncodedPayload"
# === STEP 2: Sign the token using Azure CLI ===
$UnsignedTokenBytes = [System.Security.Cryptography.SHA256]::Create().ComputeHash([Text.Encoding]::ASCII.GetBytes($UnsignedToken))
$Base64Value = [Convert]::ToBase64String($UnsignedTokenBytes)
$SignResultJson = az keyvault key sign `
--vault-name $VaultName `
--name $KeyName `
--algorithm RS256 `
--digest $Base64Value | ConvertFrom-Json
$Signature = $SignResultJson.signature
return "$UnsignedToken.$Signature"
}
function Get-GitHubInstallationId {
param(
[Parameter(Mandatory)][string] $Jwt,
[Parameter(Mandatory)][string] $ApiBase,
[Parameter(Mandatory)][string] $ApiVersion,
[Parameter(Mandatory)][string] $InstallationTokenOwner
)
$headers = Get-Headers -Jwt $Jwt -ApiVersion $ApiVersion
$uri = "$ApiBase/app/installations"
$resp = Invoke-RestMethod -Method Get -Headers $headers -Uri $uri -TimeoutSec 30
$resp | Foreach-Object { Write-Host " $($_.id): $($_.account.login) [$($_.target_type)]" }
$resp = $resp | Where-Object { $_.account.login -ieq $InstallationTokenOwner }
if (!$resp.id) { throw "No installations found for this App." }
return $resp.id
}
function New-GitHubInstallationToken {
param(
[Parameter(Mandatory)] [string] $Jwt,
[Parameter(Mandatory)] [string] $InstallationId,
[Parameter(Mandatory)] [string] $ApiBase,
[Parameter(Mandatory)] [string] $ApiVersion
)
$headers = Get-Headers -Jwt $Jwt -ApiVersion $ApiVersion
$uri = "$ApiBase/app/installations/$InstallationId/access_tokens"
$resp = Invoke-RestMethod -Method Post -Headers $headers -Uri $uri -TimeoutSec 30
if (!$resp.token) { throw "Failed to obtain installation access token for installation $InstallationId." }
return $resp.token
}
Write-Host "Generating GitHub App JWT by signing via Azure Key Vault (no key export)..."
$jwt = New-GitHubAppJwt -VaultName $KeyVaultName -KeyName $KeyName -AppId $GitHubAppId
Write-Host "Fetching installation ID for $InstallationTokenOwner ..."
$installationId = Get-GitHubInstallationId -Jwt $jwt -ApiBase $GitHubApiBaseUrl -ApiVersion $GitHubApiVersion -InstallationTokenOwner $InstallationTokenOwner
Write-Host "Installation ID resolved: $installationId"
Write-Host "Exchanging JWT for installation access token..."
$installationToken = New-GitHubInstallationToken -Jwt $jwt -InstallationId $installationId -ApiBase $GitHubApiBaseUrl -ApiVersion $GitHubApiVersion
# Export for gh CLI & git
$env:GH_TOKEN = $installationToken
Write-Host "GH_TOKEN has been set in the current process."
# Optionally set an Azure DevOps secret variable (so later tasks can reuse it)
if ($PipelineVariableName) {
Write-Host "##vso[task.setvariable variable=$PipelineVariableName;issecret=true]$installationToken"
Write-Host "Azure DevOps variable '$PipelineVariableName' has been set (secret)."
}
try {
Write-Host "`n--- gh auth status ---"
& gh auth status
}
catch {
Write-Warning "gh CLI not found or auth status failed: $($_.Exception.Message)"
}