Skip to content

Commit 774816d

Browse files
committed
Merge branch 'agr-stsdk-ft-advay26' into dev-feature-sdkmigration
2 parents b3855a8 + 8213c64 commit 774816d

16 files changed

Lines changed: 258 additions & 83 deletions

File tree

src/NuGet.Jobs.Catalog2Registration/DependencyInjectionExtensions.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,8 @@ public static ContainerBuilder AddCatalog2Registration(this ContainerBuilder con
2828
RegisterCursorStorage(containerBuilder);
2929

3030
containerBuilder
31-
.Register<ICloudBlobClient>(c =>
32-
{
33-
var options = c.Resolve<IOptionsSnapshot<Catalog2RegistrationConfiguration>>();
34-
return new CloudBlobClientWrapper(
35-
options.Value.StorageConnectionString,
36-
requestTimeout: DefaultBlobRequestOptions.ServerTimeout);
37-
});
31+
.RegisterStorageAccount<Catalog2RegistrationConfiguration>(c => c.StorageConnectionString, requestTimeout: DefaultBlobRequestOptions.ServerTimeout)
32+
.As<ICloudBlobClient>();
3833

3934
containerBuilder.Register(c => new Catalog2RegistrationCommand(
4035
c.Resolve<ICollector>(),

src/NuGet.Jobs.Common/JsonConfigurationJob.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ protected virtual void ConfigureDefaultJobServices(IServiceCollection services,
167167
services.Configure<ValidationDbConfiguration>(configurationRoot.GetSection(ValidationDbConfigurationSectionName));
168168
services.Configure<ServiceBusConfiguration>(configurationRoot.GetSection(ServiceBusConfigurationSectionName));
169169
services.Configure<ValidationStorageConfiguration>(configurationRoot.GetSection(ValidationStorageConfigurationSectionName));
170+
services.ConfigureStorageMsi(configurationRoot);
170171

171172
services.AddSingleton(new TelemetryClient(ApplicationInsightsConfiguration.TelemetryConfiguration));
172173
services.AddTransient<ITelemetryClient, TelemetryClientWrapper>();
@@ -197,13 +198,7 @@ private void AddScopedSqlConnectionFactory<TDbConfiguration>(IServiceCollection
197198
public static void ConfigureFeatureFlagAutofacServices(ContainerBuilder containerBuilder)
198199
{
199200
containerBuilder
200-
.Register(c =>
201-
{
202-
var options = c.Resolve<IOptionsSnapshot<FeatureFlagConfiguration>>();
203-
return new CloudBlobClientWrapper(
204-
options.Value.ConnectionString,
205-
requestTimeout: TimeSpan.FromMinutes(2));
206-
})
201+
.RegisterStorageAccount<FeatureFlagConfiguration>(c => c.ConnectionString, requestTimeout: TimeSpan.FromMinutes(2))
207202
.Keyed<ICloudBlobClient>(FeatureFlagBindingKey);
208203

209204
containerBuilder
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Autofac;
6+
using Autofac.Builder;
7+
using Microsoft.Extensions.Configuration;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Extensions.Options;
10+
using NuGet.Services.Configuration;
11+
using NuGetGallery;
12+
13+
namespace NuGet.Jobs
14+
{
15+
public static class StorageAccountHelper
16+
{
17+
public static IServiceCollection ConfigureStorageMsi(
18+
this IServiceCollection serviceCollection,
19+
IConfiguration configuration,
20+
string storageUseManagedIdentityPropertyName = null,
21+
string storageManagedIdentityClientIdPropertyName = null)
22+
{
23+
if (serviceCollection == null)
24+
{
25+
throw new ArgumentNullException(nameof(serviceCollection));
26+
}
27+
if (configuration == null)
28+
{
29+
throw new ArgumentNullException(nameof(configuration));
30+
}
31+
32+
storageUseManagedIdentityPropertyName ??= Constants.StorageUseManagedIdentityPropertyName;
33+
storageManagedIdentityClientIdPropertyName ??= Constants.StorageManagedIdentityClientIdPropertyName;
34+
35+
string useManagedIdentityStr = configuration[storageUseManagedIdentityPropertyName];
36+
string managedIdentityClientId = string.IsNullOrEmpty(configuration[storageManagedIdentityClientIdPropertyName])
37+
? configuration[Constants.ManagedIdentityClientIdKey]
38+
: configuration[storageManagedIdentityClientIdPropertyName];
39+
bool useManagedIdentity = false;
40+
if (!string.IsNullOrWhiteSpace(useManagedIdentityStr))
41+
{
42+
useManagedIdentity = bool.Parse(useManagedIdentityStr);
43+
}
44+
return serviceCollection.Configure<StorageMsiConfiguration>(storageConfiguration =>
45+
{
46+
storageConfiguration.UseManagedIdentity = useManagedIdentity;
47+
storageConfiguration.ManagedIdentityClientId = managedIdentityClientId;
48+
});
49+
}
50+
51+
public static CloudBlobClientWrapper CreateCloudBlobClient(
52+
this IServiceProvider serviceProvider,
53+
string storageConnectionString,
54+
bool readAccessGeoRedundant = false,
55+
TimeSpan? requestTimeout = null)
56+
{
57+
if (serviceProvider == null)
58+
{
59+
throw new ArgumentNullException(nameof(serviceProvider));
60+
}
61+
if (string.IsNullOrWhiteSpace(storageConnectionString))
62+
{
63+
throw new ArgumentException($"{nameof(storageConnectionString)} cannot be null or empty.", nameof(storageConnectionString));
64+
}
65+
66+
var msiConfiguration = serviceProvider.GetRequiredService<IOptions<StorageMsiConfiguration>>().Value;
67+
return CreateCloudBlobClient(
68+
msiConfiguration,
69+
storageConnectionString,
70+
readAccessGeoRedundant,
71+
requestTimeout);
72+
}
73+
74+
public static IRegistrationBuilder<CloudBlobClientWrapper, SimpleActivatorData, SingleRegistrationStyle> RegisterStorageAccount<TConfiguration>(
75+
this ContainerBuilder builder,
76+
Func<TConfiguration, string> getConnectionString,
77+
Func<TConfiguration, bool> getReadAccessGeoRedundant = null,
78+
TimeSpan? requestTimeout = null)
79+
where TConfiguration : class, new()
80+
{
81+
if (builder == null)
82+
{
83+
throw new ArgumentNullException(nameof(builder));
84+
}
85+
if (getConnectionString == null)
86+
{
87+
throw new ArgumentNullException(nameof(getConnectionString));
88+
}
89+
90+
return builder.Register(c =>
91+
{
92+
var options = c.Resolve<IOptionsSnapshot<TConfiguration>>();
93+
string storageConnectionString = getConnectionString(options.Value);
94+
bool readAccessGeoRedundant = getReadAccessGeoRedundant?.Invoke(options.Value) ?? false;
95+
var msiConfiguration = c.Resolve<IOptions<StorageMsiConfiguration>>().Value;
96+
return CreateCloudBlobClient(
97+
msiConfiguration,
98+
storageConnectionString,
99+
readAccessGeoRedundant,
100+
requestTimeout);
101+
});
102+
}
103+
104+
private static CloudBlobClientWrapper CreateCloudBlobClient(
105+
StorageMsiConfiguration msiConfiguration,
106+
string storageConnectionString,
107+
bool readAccessGeoRedundant = false,
108+
TimeSpan? requestTimeout = null)
109+
{
110+
if (msiConfiguration.UseManagedIdentity)
111+
{
112+
if (string.IsNullOrWhiteSpace(msiConfiguration.ManagedIdentityClientId))
113+
{
114+
return CloudBlobClientWrapper.UsingDefaultAzureCredential(
115+
storageConnectionString,
116+
readAccessGeoRedundant: readAccessGeoRedundant,
117+
requestTimeout: requestTimeout);
118+
}
119+
else
120+
{
121+
return CloudBlobClientWrapper.UsingMsi(
122+
storageConnectionString,
123+
msiConfiguration.ManagedIdentityClientId,
124+
readAccessGeoRedundant,
125+
requestTimeout);
126+
}
127+
}
128+
129+
return new CloudBlobClientWrapper(
130+
storageConnectionString,
131+
readAccessGeoRedundant,
132+
requestTimeout);
133+
}
134+
}
135+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace NuGet.Jobs
5+
{
6+
public class StorageMsiConfiguration
7+
{
8+
public bool UseManagedIdentity { get; set; }
9+
public string ManagedIdentityClientId { get; set; }
10+
}
11+
}

src/NuGet.Jobs.GitHubIndexer/Job.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,15 @@ protected override void ConfigureJobServices(IServiceCollection services, IConfi
4242
services.AddTransient<IRepositoriesCache, DiskRepositoriesCache>();
4343
services.AddTransient<IConfigFileParser, ConfigFileParser>();
4444
services.AddTransient<IRepoFetcher, RepoFetcher>();
45-
services.AddTransient<ICloudBlobClient>(provider => {
46-
var config = provider.GetRequiredService<IOptionsSnapshot<GitHubIndexerConfiguration>>();
47-
return new CloudBlobClientWrapper(config.Value.StorageConnectionString, config.Value.StorageReadAccessGeoRedundant);
48-
});
4945

5046
services.Configure<GitHubIndexerConfiguration>(configurationRoot.GetSection(GitHubIndexerConfigurationSectionName));
5147
}
5248

5349
protected override void ConfigureAutofacServices(ContainerBuilder containerBuilder, IConfigurationRoot configurationRoot)
5450
{
51+
containerBuilder
52+
.RegisterStorageAccount<GitHubIndexerConfiguration>(c => c.StorageConnectionString, c => c.StorageReadAccessGeoRedundant)
53+
.As<ICloudBlobClient>();
5554
}
5655
}
5756
}

src/NuGet.Services.AzureSearch/DependencyInjectionExtensions.cs

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using Microsoft.Extensions.Logging;
2020
using Microsoft.Extensions.Options;
2121
using Microsoft.Rest;
22+
using NuGet.Jobs;
2223
using NuGet.Protocol;
2324
using NuGet.Services.AzureSearch.Auxiliary2AzureSearch;
2425
using NuGet.Services.AzureSearch.AuxiliaryFiles;
@@ -108,13 +109,7 @@ private static void RegisterIndexServices(ContainerBuilder containerBuilder, str
108109
private static void RegisterAzureSearchStorageServices(ContainerBuilder containerBuilder, string key)
109110
{
110111
containerBuilder
111-
.Register<ICloudBlobClient>(c =>
112-
{
113-
var options = c.Resolve<IOptionsSnapshot<AzureSearchConfiguration>>();
114-
return new CloudBlobClientWrapper(
115-
options.Value.StorageConnectionString,
116-
requestTimeout: DefaultBlobRequestOptions.ServerTimeout);
117-
})
112+
.RegisterStorageAccount<AzureSearchConfiguration>(c => c.StorageConnectionString, requestTimeout: DefaultBlobRequestOptions.ServerTimeout)
118113
.Keyed<ICloudBlobClient>(key);
119114

120115
containerBuilder
@@ -221,13 +216,9 @@ private static void RegisterAzureSearchStorageServices(ContainerBuilder containe
221216
private static void RegisterAuxiliaryDataStorageServices(ContainerBuilder containerBuilder, string key)
222217
{
223218
containerBuilder
224-
.Register<ICloudBlobClient>(c =>
225-
{
226-
var options = c.Resolve<IOptionsSnapshot<AuxiliaryDataStorageConfiguration>>();
227-
return new CloudBlobClientWrapper(
228-
options.Value.AuxiliaryDataStorageConnectionString,
229-
requestTimeout: DefaultBlobRequestOptions.ServerTimeout);
230-
})
219+
.RegisterStorageAccount<AuxiliaryDataStorageConfiguration>(
220+
c => c.AuxiliaryDataStorageConnectionString,
221+
requestTimeout: DefaultBlobRequestOptions.ServerTimeout)
231222
.Keyed<ICloudBlobClient>(key);
232223

233224
containerBuilder

src/NuGet.Services.Configuration/Constants.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
namespace NuGet.Services.Configuration
@@ -15,5 +15,7 @@ public static class Constants
1515
public static string KeyVaultStoreNameKey = "KeyVault_StoreName";
1616
public static string KeyVaultStoreLocationKey = "KeyVault_StoreLocation";
1717
public static string KeyVaultSendX5c = "KeyVault_SendX5c";
18+
public static string StorageUseManagedIdentityPropertyName = "Storage_UseManagedIdentity";
19+
public static string StorageManagedIdentityClientIdPropertyName = "Storage_ManagedIdentityClientId";
1820
}
1921
}

src/NuGet.Services.V3/NuGet.Services.V3.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10+
<PackageReference Include="WindowsAzure.Storage" />
1011
<ProjectReference Include="..\Catalog\NuGet.Services.Metadata.Catalog.csproj" />
1112
<ProjectReference Include="..\Validation.Common.Job\Validation.Common.Job.csproj" />
1213
</ItemGroup>

src/NuGet.Services.Validation.Orchestrator/Job.cs

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Threading.Tasks;
1111
using Autofac;
1212
using Autofac.Core;
13+
using Azure.Identity;
1314
using Azure.Storage.Blobs;
1415
using Microsoft.ApplicationInsights;
1516
using Microsoft.Extensions.Configuration;
@@ -180,13 +181,6 @@ protected override void ConfigureJobServices(IServiceCollection services, IConfi
180181
services.AddTransient<IBrokeredMessageSerializer<PackageValidationMessageData>, PackageValidationMessageDataSerializationAdapter>();
181182
services.AddTransient<ICriteriaEvaluator<Package>, PackageCriteriaEvaluator>();
182183
services.AddTransient<IProcessSignatureEnqueuer, ProcessSignatureEnqueuer>();
183-
services.AddTransient<ICloudBlobClient>(c =>
184-
{
185-
var configurationAccessor = c.GetRequiredService<IOptionsSnapshot<ValidationConfiguration>>();
186-
return new CloudBlobClientWrapper(
187-
configurationAccessor.Value.ValidationStorageConnectionString,
188-
readAccessGeoRedundant: false);
189-
});
190184
services.AddTransient<ICloudBlobContainerInformationProvider, GalleryCloudBlobContainerInformationProvider>();
191185
services.AddTransient<ICoreFileStorageService, CloudBlobCoreFileStorageService>();
192186
services.AddTransient<IFileDownloader, FileDownloader>();
@@ -236,6 +230,10 @@ protected override void ConfigureJobServices(IServiceCollection services, IConfi
236230

237231
protected override void ConfigureAutofacServices(ContainerBuilder containerBuilder, IConfigurationRoot configurationRoot)
238232
{
233+
containerBuilder
234+
.RegisterStorageAccount<ValidationConfiguration>(c => c.ValidationStorageConnectionString)
235+
.As<ICloudBlobClient>();
236+
239237
containerBuilder
240238
.Register(c =>
241239
{
@@ -387,11 +385,9 @@ private static void ConfigureLeaseService(ContainerBuilder builder)
387385
.Register(c =>
388386
{
389387
LeaseConfiguration config = c.Resolve<IOptionsSnapshot<LeaseConfiguration>>().Value;
388+
StorageMsiConfiguration storageMsiConfiguration = c.Resolve<IOptionsSnapshot<StorageMsiConfiguration>>().Value;
390389

391-
// workaround for https://github.com/Azure/azure-sdk-for-net/issues/44373
392-
var connectionString = config.ConnectionString.Replace("SharedAccessSignature=?", "SharedAccessSignature=");
393-
394-
BlobServiceClient blobServiceClient = new BlobServiceClient(connectionString);
390+
BlobServiceClient blobServiceClient = CreateBlobServiceClient(storageMsiConfiguration, config.ConnectionString);
395391
return new CloudBlobLeaseService(blobServiceClient, config.ContainerName, config.StoragePath);
396392
})
397393
.As<ILeaseService>();
@@ -456,13 +452,7 @@ private static void ConfigureSymbolScanValidator(ContainerBuilder builder)
456452
private static void ConfigureFlatContainer(ContainerBuilder builder)
457453
{
458454
builder
459-
.Register<CloudBlobClientWrapper>(c =>
460-
{
461-
var configurationAccessor = c.Resolve<IOptionsSnapshot<FlatContainerConfiguration>>();
462-
return new CloudBlobClientWrapper(
463-
configurationAccessor.Value.ConnectionString,
464-
readAccessGeoRedundant: false);
465-
})
455+
.RegisterStorageAccount<FlatContainerConfiguration>(c => c.ConnectionString)
466456
.Keyed<ICloudBlobClient>(FlatContainerBindingKey);
467457

468458
builder
@@ -614,5 +604,50 @@ private T GetRequiredService<T>()
614604
{
615605
return _serviceProvider.GetRequiredService<T>();
616606
}
607+
608+
private static BlobServiceClient CreateBlobServiceClient(
609+
StorageMsiConfiguration msiConfiguration,
610+
string storageConnectionString,
611+
TimeSpan? requestTimeout = null)
612+
{
613+
BlobClientOptions blobClientOptions = new BlobClientOptions();
614+
if (requestTimeout.HasValue)
615+
{
616+
blobClientOptions.Retry.NetworkTimeout = requestTimeout.Value;
617+
}
618+
619+
if (msiConfiguration.UseManagedIdentity)
620+
{
621+
if (string.IsNullOrWhiteSpace(msiConfiguration.ManagedIdentityClientId))
622+
{
623+
// 1. Using MSI with DefaultAzureCredential (local debugging)
624+
var defaultAzureCredentialOptions = new DefaultAzureCredentialOptions
625+
{
626+
ManagedIdentityClientId = null,
627+
};
628+
629+
return new BlobServiceClient(
630+
ConnectionStringExtensions.GetBlobEndpointFromConnectionString(storageConnectionString),
631+
new DefaultAzureCredential(defaultAzureCredentialOptions),
632+
blobClientOptions);
633+
}
634+
else
635+
{
636+
// 2. Using MSI with ClientId
637+
return new BlobServiceClient(
638+
ConnectionStringExtensions.GetBlobEndpointFromConnectionString(storageConnectionString),
639+
new ManagedIdentityCredential(msiConfiguration.ManagedIdentityClientId),
640+
blobClientOptions);
641+
}
642+
}
643+
else
644+
{
645+
// 3. Using SAS token
646+
// workaround for https://github.com/Azure/azure-sdk-for-net/issues/44373
647+
var connectionString = storageConnectionString.Replace("SharedAccessSignature=?", "SharedAccessSignature=");
648+
649+
return new BlobServiceClient(connectionString, blobClientOptions);
650+
}
651+
}
617652
}
618653
}

0 commit comments

Comments
 (0)