Skip to content
This repository was archived by the owner on Jul 30, 2024. It is now read-only.

Commit 384546b

Browse files
authored
[Package Signing] Add client APIs to Package Signature Validation Job (#264)
Calls `NuGet.Client`'s APIs to determine whether a package is signed. **NOTE**: This doesn't have any integration tests yet as that would require a signed package to be checked-in to source control.
1 parent 19663a1 commit 384546b

6 files changed

Lines changed: 118 additions & 15 deletions

File tree

src/NuGet.Jobs.Common/JobRunner.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using System.Diagnostics;
88
using System.Linq;
99
using System.Net;
10-
using System.Threading;
1110
using System.Threading.Tasks;
1211
using Microsoft.Extensions.Logging;
1312
using NuGet.Services.Logging;
@@ -163,7 +162,7 @@ private static async Task JobLoop(JobBase job, bool runContinuously, int sleepDu
163162
// Wait for <sleepDuration> milliSeconds and run the job again
164163
_logger.LogInformation("Will sleep for {SleepDuration} before the next Job run", PrettyPrintTime(sleepDuration));
165164

166-
Thread.Sleep(sleepDuration);
165+
await Task.Delay(sleepDuration);
167166
}
168167
}
169168
}

src/Validation.PackageSigning.Core/Error.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,7 @@ public static class Error
99
{
1010
public static EventId ValidatorStateServiceFailedToAddStatus = new EventId(1000, "Failed to add validator's status");
1111
public static EventId ValidatorStateServiceFailedToUpdateStatus = new EventId(1001, "Failed to update validator's status");
12+
13+
public static EventId ValidateSignatureFailedToDownloadPackageStatus = new EventId(1100, "Failed to download package");
1214
}
1315
}

src/Validation.PackageSigning.ExtractAndValidateSignature/Job.cs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using System.Collections.Generic;
66
using System.Diagnostics;
77
using System.IO;
8+
using System.Net;
9+
using System.Net.Http;
810
using System.Threading.Tasks;
911
using Autofac;
1012
using Autofac.Extensions.DependencyInjection;
@@ -26,9 +28,9 @@ namespace NuGet.Jobs.Validation.PackageSigning.ExtractAndValidateSignature
2628
public class Job : JobBase
2729
{
2830
/// <summary>
29-
/// The configured service provider, used to instiate the services this job depends on.
31+
/// The maximum number of concurrent connections that can be established to a single server.
3032
/// </summary>
31-
private IServiceProvider _serviceProvider;
33+
private const int MaximumConnectionsPerServer = 64;
3234

3335
/// <summary>
3436
/// The argument this job uses to determine the configuration file's path.
@@ -37,6 +39,7 @@ public class Job : JobBase
3739

3840
private const string ValidationDbConfigurationSectionName = "ValidationDb";
3941
private const string ServiceBusConfigurationSectionName = "ServiceBus";
42+
private const string PackageDownloadTimeoutName = "PackageDownloadTimeout";
4043

4144
/// <summary>
4245
/// The maximum time that a KeyVault secret will be cached for.
@@ -54,11 +57,18 @@ public class Job : JobBase
5457
/// </summary>
5558
private static readonly TimeSpan MaxShutdownTime = TimeSpan.FromMinutes(1);
5659

60+
/// <summary>
61+
/// The configured service provider, used to instiate the services this job depends on.
62+
/// </summary>
63+
private IServiceProvider _serviceProvider;
64+
5765
public override void Init(IDictionary<string, string> jobArgsDictionary)
5866
{
5967
var configurationFilename = JobConfigurationManager.GetArgument(jobArgsDictionary, ConfigurationArgument);
6068

6169
_serviceProvider = GetServiceProvider(GetConfigurationRoot(configurationFilename));
70+
71+
ServicePointManager.DefaultConnectionLimit = MaximumConnectionsPerServer;
6272
}
6373

6474
public override async Task Run()
@@ -144,6 +154,22 @@ private void ConfigureJobServices(IServiceCollection services, IConfigurationRoo
144154
services.AddTransient<IBrokeredMessageSerializer<SignatureValidationMessage>, SignatureValidationMessageSerializer>();
145155
services.AddTransient<IMessageHandler<SignatureValidationMessage>, SignatureValidationMessageHandler>();
146156
services.AddTransient<IPackageSigningStateService, PackageSigningStateService>();
157+
158+
services.AddSingleton(p =>
159+
{
160+
var assemblyName = typeof(SignatureValidationMessage).Assembly.GetName();
161+
162+
var client = new HttpClient(new WebRequestHandler
163+
{
164+
AllowPipelining = true,
165+
AutomaticDecompression = (DecompressionMethods.GZip | DecompressionMethods.Deflate),
166+
});
167+
168+
client.Timeout = configurationRoot.GetValue<TimeSpan>(PackageDownloadTimeoutName);
169+
client.DefaultRequestHeaders.Add("user-agent", $"{assemblyName.Name}/{assemblyName.Version}");
170+
171+
return client;
172+
});
147173
}
148174

149175
private IServiceProvider GetServiceProvider(IConfigurationRoot configurationRoot)

src/Validation.PackageSigning.ExtractAndValidateSignature/Settings/dev.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
"TopicPath": "validate-signature",
88
"SubscriptionName": "extract-and-validate-signature"
99
},
10+
11+
"PackageDownloadTimeout": "10:00",
12+
1013
"KeyVault_VaultName": "#{Deployment.Azure.KeyVault.VaultName}",
1114
"KeyVault_ClientId": "#{Deployment.Azure.KeyVault.ClientId}",
1215
"KeyVault_CertificateThumbprint": "#{Deployment.Azure.KeyVault.CertificateThumbprint}",

src/Validation.PackageSigning.ExtractAndValidateSignature/SignatureValidationMessageHandler.cs

Lines changed: 81 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33

44
using System;
55
using System.Data.Entity.Infrastructure;
6+
using System.Diagnostics;
7+
using System.IO;
8+
using System.Net;
69
using System.Net.Http;
10+
using System.Threading;
711
using System.Threading.Tasks;
812
using Microsoft.Extensions.Logging;
913
using NuGet.Jobs.Validation.PackageSigning.Messages;
1014
using NuGet.Jobs.Validation.PackageSigning.Storage;
15+
using NuGet.Packaging;
1116
using NuGet.Services.ServiceBus;
1217
using NuGet.Services.Validation;
13-
using NuGet.Versioning;
1418

1519
namespace NuGet.Jobs.Validation.PackageSigning.ExtractAndValidateSignature
1620
{
@@ -23,22 +27,27 @@ namespace NuGet.Jobs.Validation.PackageSigning.ExtractAndValidateSignature
2327
public class SignatureValidationMessageHandler
2428
: IMessageHandler<SignatureValidationMessage>
2529
{
30+
private const int BufferSize = 8192;
31+
32+
private readonly HttpClient _httpClient;
2633
private readonly IValidatorStateService _validatorStateService;
2734
private readonly IPackageSigningStateService _packageSigningStateService;
2835
private readonly ILogger<SignatureValidationMessageHandler> _logger;
2936

3037
/// <summary>
3138
/// Instantiate's a new package signatures validator.
3239
/// </summary>
33-
/// <param name="validationContext">The persisted validation context.</param>
34-
/// <param name="certificateStore">The persisted certificate store.</param>
40+
/// <param name="httpClient">The HTTP client used to download packages.</param>
3541
/// <param name="validatorStateService">The service used to retrieve and persist this validator's state.</param>
3642
/// <param name="packageSigningStateService">The service used to retrieve and persist package signing state.</param>
43+
/// <param name="logger">The logger that should be used.</param>
3744
public SignatureValidationMessageHandler(
45+
HttpClient httpClient,
3846
IValidatorStateService validatorStateService,
3947
IPackageSigningStateService packageSigningStateService,
4048
ILogger<SignatureValidationMessageHandler> logger)
4149
{
50+
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
4251
_validatorStateService = validatorStateService ?? throw new ArgumentNullException(nameof(validatorStateService));
4352
_packageSigningStateService = packageSigningStateService ?? throw new ArgumentNullException(nameof(packageSigningStateService));
4453
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
@@ -83,8 +92,7 @@ public async Task<bool> HandleAsync(SignatureValidationMessage message)
8392
}
8493

8594
// Validate package
86-
// TODO: consume actual client nupkg's containing missing signing APIs
87-
if (!IsSigned(message.PackageVersion))
95+
if ( ! await IsSigned(message.NupkgUri, CancellationToken.None))
8896
{
8997
return await HandleUnsignedPackageAsync(validation, message);
9098
}
@@ -95,10 +103,13 @@ public async Task<bool> HandleAsync(SignatureValidationMessage message)
95103
}
96104
}
97105

98-
private bool IsSigned(string packageVersion)
106+
private async Task<bool> IsSigned(Uri packageUri, CancellationToken cancellationToken)
99107
{
100-
var nugetVersion = NuGetVersion.Parse(packageVersion);
101-
return nugetVersion.IsPrerelease && string.Equals(nugetVersion.Release, "signed", StringComparison.OrdinalIgnoreCase);
108+
using (var packageStream = await DownloadPackageAsync(packageUri, cancellationToken))
109+
using (var package = new PackageArchiveReader(packageStream, leaveStreamOpen: false))
110+
{
111+
return await package.IsSignedAsync(cancellationToken);
112+
}
102113
}
103114

104115
private async Task<bool> HandleUnsignedPackageAsync(ValidatorStatus validation, SignatureValidationMessage message)
@@ -171,5 +182,67 @@ private async Task<bool> BlockSignedPackageAsync(ValidatorStatus validation, Sig
171182
// Consume the message if successfully saved state.
172183
return saveStateResult == SaveStatusResult.Success;
173184
}
185+
186+
private async Task<Stream> DownloadPackageAsync(Uri packageUri, CancellationToken cancellationToken)
187+
{
188+
_logger.LogInformation("Attempting to download package from {PackageUri}...", packageUri);
189+
190+
Stream packageStream = null;
191+
var stopwatch = Stopwatch.StartNew();
192+
193+
try
194+
{
195+
// Download the package from the network to a temporary file.
196+
using (var response = await _httpClient.GetAsync(packageUri, HttpCompletionOption.ResponseHeadersRead))
197+
{
198+
_logger.LogInformation(
199+
"Received response {StatusCode}: {ReasonPhrase} of type {ContentType} for request {PackageUri}",
200+
response.StatusCode,
201+
response.ReasonPhrase,
202+
response.Content.Headers.ContentType,
203+
packageUri);
204+
205+
if (response.StatusCode != HttpStatusCode.OK)
206+
{
207+
throw new InvalidOperationException($"Expected status code {HttpStatusCode.OK} for package download, actual: {response.StatusCode}");
208+
}
209+
210+
using (var networkStream = await response.Content.ReadAsStreamAsync())
211+
{
212+
packageStream = new FileStream(
213+
Path.GetTempFileName(),
214+
FileMode.Create,
215+
FileAccess.ReadWrite,
216+
FileShare.None,
217+
BufferSize,
218+
FileOptions.DeleteOnClose | FileOptions.Asynchronous);
219+
220+
await networkStream.CopyToAsync(packageStream, BufferSize, cancellationToken);
221+
}
222+
}
223+
224+
packageStream.Position = 0;
225+
226+
_logger.LogInformation(
227+
"Downloaded {PackageSizeInBytes} bytes in {DownloadElapsedTime} seconds for request {PackageUri}",
228+
packageStream.Length,
229+
stopwatch.Elapsed.TotalSeconds,
230+
packageUri);
231+
232+
return packageStream;
233+
}
234+
catch (Exception e)
235+
{
236+
_logger.LogError(
237+
Error.ValidateSignatureFailedToDownloadPackageStatus,
238+
e,
239+
"Exception thrown when trying to download package from {PackageUri}",
240+
packageUri);
241+
242+
packageStream?.Dispose();
243+
244+
throw;
245+
}
246+
}
174247
}
175248
}

src/Validation.PackageSigning.ExtractAndValidateSignature/Validation.PackageSigning.ExtractAndValidateSignature.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@
9696
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions">
9797
<Version>1.1.2</Version>
9898
</PackageReference>
99+
<PackageReference Include="NuGet.Packaging">
100+
<Version>4.6.0-preview1-4690</Version>
101+
</PackageReference>
99102
<PackageReference Include="NuGet.Services.Configuration">
100103
<Version>2.4.3</Version>
101104
</PackageReference>
@@ -114,9 +117,6 @@
114117
<PackageReference Include="NuGet.Services.Validation">
115118
<Version>2.4.3</Version>
116119
</PackageReference>
117-
<PackageReference Include="NuGet.Versioning">
118-
<Version>4.4.0</Version>
119-
</PackageReference>
120120
<PackageReference Include="System.Net.Http">
121121
<Version>4.3.3</Version>
122122
</PackageReference>

0 commit comments

Comments
 (0)