Skip to content

Commit 4fc8893

Browse files
committed
implements nuget api key retrieval - Trusted Publishing
Implements retrieval of the NuGet API key using GitHub OIDC- Trusted Publishing
1 parent 4e4eed2 commit 4fc8893

File tree

1 file changed

+90
-2
lines changed

1 file changed

+90
-2
lines changed

build/publish/Tasks/PublishNuget.cs

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Net.Http.Headers;
2+
using System.Text.Json;
13
using Cake.Common.Tools.DotNet.NuGet.Push;
24
using Common.Utilities;
35

@@ -10,7 +12,7 @@ public class PublishNuget : FrostingTask<BuildContext>;
1012

1113
[TaskName(nameof(PublishNugetInternal))]
1214
[TaskDescription("Publish nuget packages")]
13-
public class PublishNugetInternal : FrostingTask<BuildContext>
15+
public class PublishNugetInternal : AsyncFrostingTask<BuildContext>
1416
{
1517
public override bool ShouldRun(BuildContext context)
1618
{
@@ -21,7 +23,7 @@ public override bool ShouldRun(BuildContext context)
2123
return shouldRun;
2224
}
2325

24-
public override void Run(BuildContext context)
26+
public override async Task RunAsync(BuildContext context)
2527
{
2628
// publish to github packages for commits on main and on original repo
2729
if (context.IsInternalPreRelease)
@@ -35,6 +37,16 @@ public override void Run(BuildContext context)
3537
PublishToNugetRepo(context, apiKey, Constants.GithubPackagesUrl);
3638
context.EndGroup();
3739
}
40+
41+
var nugetApiKey = await GetNugetApiKey(context);
42+
if (string.IsNullOrEmpty(nugetApiKey))
43+
{
44+
context.Warning("Could not retrieve NuGet API key.");
45+
}
46+
else
47+
{
48+
context.Information("Successfully retrieved NuGet API key via OIDC.");
49+
}
3850
// publish to nuget.org for tagged releases
3951
if (context.IsStableRelease || context.IsTaggedPreRelease)
4052
{
@@ -46,6 +58,8 @@ public override void Run(BuildContext context)
4658
}
4759
PublishToNugetRepo(context, apiKey, Constants.NugetOrgUrl);
4860
context.EndGroup();
61+
var url = new Uri(
62+
"https://run-actions-2-azure-eastus.actions.githubusercontent.com/68//idtoken/71084348-96ba-41e6-b690-47fc84f192c3/30514149-640b-5df8-9fad-53dc9469f7f8?api-version=2.0&audience=https%3A%2F%2Fwww.nuget.org");
4963
}
5064
}
5165
private static void PublishToNugetRepo(BuildContext context, string apiKey, string apiUrl)
@@ -63,4 +77,78 @@ private static void PublishToNugetRepo(BuildContext context, string apiKey, stri
6377
});
6478
}
6579
}
80+
81+
private static async Task<string?> GetNugetApiKey(BuildContext context)
82+
{
83+
try
84+
{
85+
const string nugetUsername = "gittoolsbot";
86+
const string nugetTokenServiceUrl = "https://www.nuget.org/api/v2/token";
87+
const string nugetAudience = "https://www.nuget.org";
88+
89+
var oidcRequestToken = context.Environment.GetEnvironmentVariable("ACTIONS_ID_TOKEN_REQUEST_TOKEN");
90+
var oidcRequestUrl = context.Environment.GetEnvironmentVariable("ACTIONS_ID_TOKEN_REQUEST_URL");
91+
92+
if (string.IsNullOrEmpty(oidcRequestToken) || string.IsNullOrEmpty(oidcRequestUrl))
93+
throw new InvalidOperationException("Missing GitHub OIDC request environment variables.");
94+
95+
var tokenUrl = $"{oidcRequestUrl}&audience={Uri.EscapeDataString(nugetAudience)}";
96+
context.Information($"Requesting GitHub OIDC token from: {tokenUrl}");
97+
98+
using var http = new HttpClient();
99+
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", oidcRequestToken);
100+
var tokenResp = await http.GetAsync(tokenUrl);
101+
var tokenBody = await tokenResp.Content.ReadAsStringAsync();
102+
103+
if (!tokenResp.IsSuccessStatusCode)
104+
throw new Exception("Failed to retrieve OIDC token from GitHub.");
105+
106+
using var tokenDoc = JsonDocument.Parse(tokenBody);
107+
if (!tokenDoc.RootElement.TryGetProperty("value", out var valueElem) || valueElem.ValueKind != JsonValueKind.String)
108+
throw new Exception("Failed to retrieve OIDC token from GitHub.");
109+
110+
var oidcToken = valueElem.GetString();
111+
112+
var requestBody = JsonSerializer.Serialize(new { username = nugetUsername, tokenType = "ApiKey" });
113+
114+
using var tokenServiceHttp = new HttpClient();
115+
tokenServiceHttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", oidcToken);
116+
tokenServiceHttp.DefaultRequestHeaders.UserAgent.ParseAdd("nuget/login-action");
117+
var content = new StringContent(requestBody, Encoding.UTF8, "application/json");
118+
var exchangeResp = await tokenServiceHttp.PostAsync(nugetTokenServiceUrl, content);
119+
var exchangeBody = await exchangeResp.Content.ReadAsStringAsync();
120+
121+
if (!exchangeResp.IsSuccessStatusCode)
122+
{
123+
var errorMessage = $"Token exchange failed ({(int)exchangeResp.StatusCode})";
124+
try
125+
{
126+
using var errDoc = JsonDocument.Parse(exchangeBody);
127+
errorMessage +=
128+
errDoc.RootElement.TryGetProperty("error", out var errProp) &&
129+
errProp.ValueKind == JsonValueKind.String
130+
? $": {errProp.GetString()}"
131+
: $": {exchangeBody}";
132+
}
133+
catch
134+
{
135+
errorMessage += $": {exchangeBody}";
136+
}
137+
throw new Exception(errorMessage);
138+
}
139+
140+
using var respDoc = JsonDocument.Parse(exchangeBody);
141+
if (!respDoc.RootElement.TryGetProperty("apiKey", out var apiKeyProp) || apiKeyProp.ValueKind != JsonValueKind.String)
142+
throw new Exception("Response did not contain \"apiKey\".");
143+
144+
var apiKey = apiKeyProp.GetString();
145+
context.Information($"Successfully exchanged OIDC token for NuGet API key.");
146+
return apiKey;
147+
}
148+
catch (Exception ex)
149+
{
150+
context.Error($"Failed to retrieve NuGet API key: {ex.Message}");
151+
return null;
152+
}
153+
}
66154
}

0 commit comments

Comments
 (0)