microsoft/typespec

Public

mirrored from https://github.com/microsoft/typespecAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/fix-html-program-viewer-crash

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

eng/common/scripts/login-to-github.ps1

198lines · modecode

1<#
2.SYNOPSIS
3 Mints a GitHub App installation access token using Azure Key Vault 'sign' (non-exportable key),
4 and logs in the GitHub CLI by setting GH_TOKEN.
5
6 Works in both Azure DevOps pipelines and GitHub Actions workflows.
7 Requires Azure CLI to be pre-authenticated (via AzureCLI@2 in ADO, or azure/login in GH Actions).
8
9.PARAMETER KeyVaultName
10 Name of the Azure Key Vault containing the non-exportable RSA key.
11
12.PARAMETER KeyName
13 Name of the RSA key in Key Vault (imported as a *key*, not a secret).
14
15.PARAMETER GitHubAppId
16 Numeric App ID (not client ID) of your GitHub App.
17
18.PARAMETER InstallationTokenOwners
19 List of GitHub organizations or users for which to obtain installation tokens.
20
21.PARAMETER VariableNamePrefix
22 Prefix for the exported variable name (default: GH_TOKEN).
23 With a single owner, exports as GH_TOKEN. With multiple owners, exports as GH_TOKEN_<Owner>.
24
25.OUTPUTS
26 Sets environment variables in the current process and exports them to the CI system:
27 - Azure DevOps: sets secret pipeline variables via ##vso logging commands
28 - GitHub Actions: writes to GITHUB_ENV and masks the token
29#>
30
31[CmdletBinding()]
32param(
33 [string] $KeyVaultName = "azuresdkengkeyvault",
34 [string] $KeyName = "azure-sdk-automation",
35 [string] $GitHubAppId = '1086291', # Azure SDK Automation App ID
36 [string[]] $InstallationTokenOwners = @("Azure"),
37 [string] $VariableNamePrefix = "GH_TOKEN"
38)
39
40$ErrorActionPreference = 'Stop'
41Set-StrictMode -Version Latest
42
43$GitHubApiBaseUrl = "https://api.github.com"
44$GitHubApiVersion = "2022-11-28"
45
46function Get-Headers {
47 param(
48 [Parameter(Mandatory)][string] $Jwt,
49 [Parameter(Mandatory)][string] $ApiVersion
50 )
51 return @{
52 'Authorization' = "Bearer $Jwt"
53 'Accept' = 'application/vnd.github+json'
54 'X-GitHub-Api-Version' = $ApiVersion
55 'User-Agent' = 'ado-pwsh-ghapp'
56 }
57}
58
59function New-GitHubAppJwt {
60 param(
61 [Parameter(Mandatory)] [string] $VaultName,
62 [Parameter(Mandatory)] [string] $KeyName,
63 [Parameter(Mandatory)] [string] $AppId
64 )
65
66 function Base64UrlEncode {
67 param(
68 [string]$Data,
69 [switch]$IsBase64String
70 )
71 if ($IsBase64String) {
72 $base64 = $Data
73 } else {
74 $bytes = [System.Text.Encoding]::UTF8.GetBytes($Data)
75 $base64 = [Convert]::ToBase64String($bytes)
76 }
77 return $base64.TrimEnd('=') -replace '\+', '-' -replace '/', '_'
78 }
79
80 # === STEP 1: Create JWT Header and Payload ===
81 $Header = @{
82 alg = "RS256"
83 typ = "JWT"
84 }
85 $Now = [int][double]::Parse((Get-Date -UFormat %s))
86 $Payload = @{
87 iat = $Now - 10 # 10 seconds clock skew
88 exp = $Now + 600 # 10 minutes
89 iss = $AppId
90 }
91
92 $EncodedHeader = Base64UrlEncode (ConvertTo-Json $Header -Compress)
93 $EncodedPayload = Base64UrlEncode (ConvertTo-Json $Payload -Compress)
94 $UnsignedToken = "$EncodedHeader.$EncodedPayload"
95
96 # === STEP 2: Sign the token using Azure CLI ===
97 $UnsignedTokenBytes = [System.Security.Cryptography.SHA256]::Create().ComputeHash([Text.Encoding]::ASCII.GetBytes($UnsignedToken))
98 $Base64Value = [Convert]::ToBase64String($UnsignedTokenBytes)
99
100 $SignResultJson = az keyvault key sign `
101 --vault-name $VaultName `
102 --name $KeyName `
103 --algorithm RS256 `
104 --digest $Base64Value | ConvertFrom-Json
105
106 if ($LASTEXITCODE -ne 0) {
107 throw "Failed to sign JWT with Azure Key Vault. Error: $($SignResultJson | ConvertTo-Json -Compress)"
108 }
109
110 if (!$SignResultJson.signature) {
111 throw "Azure Key Vault response does not contain a signature. Response: $($SignResultJson | ConvertTo-Json -Compress)"
112 }
113
114 $Signature = Base64UrlEncode -Data $SignResultJson.signature -IsBase64String
115 return "$UnsignedToken.$Signature"
116}
117
118function Get-GitHubInstallationId {
119 param(
120 [Parameter(Mandatory)][string] $Jwt,
121 [Parameter(Mandatory)][string] $ApiBase,
122 [Parameter(Mandatory)][string] $ApiVersion,
123 [Parameter(Mandatory)][string] $InstallationTokenOwner
124 )
125
126 $headers = Get-Headers -Jwt $Jwt -ApiVersion $ApiVersion
127
128 $uri = "$ApiBase/app/installations"
129 $resp = Invoke-RestMethod -Method Get -Headers $headers -Uri $uri -TimeoutSec 30 -MaximumRetryCount 3
130
131 $resp | Foreach-Object { Write-Host " $($_.id): $($_.account.login) [$($_.target_type)]" }
132
133 $resp = $resp | Where-Object { $_.account.login -ieq $InstallationTokenOwner }
134 if (!$resp.id) { throw "No installations found for this App." }
135 return $resp.id
136}
137
138function New-GitHubInstallationToken {
139 param(
140 [Parameter(Mandatory)] [string] $Jwt,
141 [Parameter(Mandatory)] [string] $InstallationId,
142 [Parameter(Mandatory)] [string] $ApiBase,
143 [Parameter(Mandatory)] [string] $ApiVersion
144 )
145 $headers = Get-Headers -Jwt $Jwt -ApiVersion $ApiVersion
146 $uri = "$ApiBase/app/installations/$InstallationId/access_tokens"
147 $resp = Invoke-RestMethod -Method Post -Headers $headers -Uri $uri -TimeoutSec 30 -MaximumRetryCount 3
148 if (!$resp.token) { throw "Failed to obtain installation access token for installation $InstallationId." }
149 return $resp.token
150}
151
152Write-Host "Generating GitHub App JWT by signing via Azure Key Vault (no key export)..."
153$jwt = New-GitHubAppJwt -VaultName $KeyVaultName -KeyName $KeyName -AppId $GitHubAppId
154
155foreach ($InstallationTokenOwner in $InstallationTokenOwners)
156{
157 Write-Host "Fetching installation ID for $InstallationTokenOwner ..."
158 $installationId = Get-GitHubInstallationId -Jwt $jwt -ApiBase $GitHubApiBaseUrl -ApiVersion $GitHubApiVersion -InstallationTokenOwner $InstallationTokenOwner
159
160 Write-Host "Installation ID resolved: $installationId"
161
162 Write-Host "Exchanging JWT for installation access token..."
163 $installationToken = New-GitHubInstallationToken -Jwt $jwt -InstallationId $installationId -ApiBase $GitHubApiBaseUrl -ApiVersion $GitHubApiVersion
164
165 $variableName = $VariableNamePrefix
166 if ($InstallationTokenOwners.Count -gt 1)
167 {
168 $variableName = $VariableNamePrefix + "_" + $InstallationTokenOwner
169 }
170
171 Set-Item -Path Env:$variableName -Value $installationToken
172
173 # Export for gh CLI & git
174 Write-Host "$variableName has been set in the current process."
175
176 # Azure DevOps: set secret pipeline variable (so later tasks can reuse it)
177 if ($null -ne $env:SYSTEM_TEAMPROJECTID) {
178 Write-Host "##vso[task.setvariable variable=$variableName;issecret=true]$installationToken"
179 Write-Host "Azure DevOps variable '$variableName' has been set (secret)."
180 }
181
182 # GitHub Actions: mask the token and export to GITHUB_ENV
183 if ($env:GITHUB_ACTIONS -eq 'true') {
184 Write-Host "::add-mask::$installationToken"
185 Add-Content -Path $env:GITHUB_ENV -Value "$variableName=$installationToken"
186 Write-Host "GitHub Actions env variable '$variableName' has been exported."
187 }
188
189 try {
190 Write-Host "`n--- gh auth status ---"
191 $gh_token_value_before = $env:GH_TOKEN
192 $env:GH_TOKEN = $installationToken
193 & gh auth status
194 }
195 finally{
196 $env:GH_TOKEN = $gh_token_value_before
197 }
198}
199