Skip to content

Commit 5033758

Browse files
authored
MSI auth support for NuGetGallery (#10059)
* MSI auth. * DefaultAzureCredential support for debug builds for local debugging. * Removed last reference to WindowsAzure.Storage * Extracting blob properties from `BlobDownloadDetails`.
1 parent 5c5ba51 commit 5033758

12 files changed

Lines changed: 91 additions & 59 deletions

File tree

src/AccountDeleter/Configuration/GalleryConfiguration.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public string SiteRoot
3535
public string AzureStorage_Uploads_ConnectionString { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
3636
public string AzureStorage_Revalidation_ConnectionString { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
3737
public bool AzureStorageReadAccessGeoRedundant { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
38+
public bool AzureStorageUseMsi { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
39+
public string AzureStorageMsiClientId { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
3840
public TimeSpan FeatureFlagsRefreshInterval { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
3941
public bool AdminPanelEnabled { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
4042
public bool AdminPanelDatabaseAccessEnabled { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }

src/NuGetGallery.Core/NuGetGallery.Core.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@
4141
</ItemGroup>
4242

4343
<ItemGroup>
44-
<PackageReference Include="WindowsAzure.Storage" Version="9.3.3" />
45-
4644
<PackageReference Include="Azure.Core" Version="1.39.0" />
4745
<PackageReference Include="Azure.Identity" Version="1.11.3" />
4846
<PackageReference Include="Azure.Storage.Blobs" Version="12.20.0" />

src/NuGetGallery.Core/Services/CloudBlobClientWrapper.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,20 @@ public static CloudBlobClientWrapper UsingServicePrincipal(
7272
return new CloudBlobClientWrapper(storageConnectionString, tokenCredential, readAccessGeoRedundant, requestTimeout);
7373
}
7474

75+
public static CloudBlobClientWrapper UsingDefaultAzureCredential(
76+
string storageConnectionString,
77+
string clientId = null,
78+
bool readAccessGeoRedundant = false,
79+
TimeSpan? requestTimeout = null)
80+
{
81+
var options = new DefaultAzureCredentialOptions
82+
{
83+
ManagedIdentityClientId = clientId,
84+
};
85+
var tokenCredential = new DefaultAzureCredential(options);
86+
return new CloudBlobClientWrapper(storageConnectionString, tokenCredential, readAccessGeoRedundant, requestTimeout);
87+
}
88+
7589
public ISimpleCloudBlob GetBlobFromUri(Uri uri)
7690
{
7791
// For Azure blobs, the query string is assumed to be the SAS token.

src/NuGetGallery.Core/Services/CloudBlobReadOnlyProperties.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,14 @@ public CloudBlobReadOnlyProperties(BlobItem blobItem)
3131
CopyStatus = null;
3232
CopyStatusDescription = null;
3333
}
34+
35+
public CloudBlobReadOnlyProperties(BlobDownloadDetails details)
36+
{
37+
LastModifiedUtc = details.LastModified.UtcDateTime;
38+
Length = details.ContentLength;
39+
IsSnapshot = false;
40+
CopyStatus = details.CopyStatus;
41+
CopyStatusDescription = details.CopyStatusDescription;
42+
}
3443
}
3544
}

src/NuGetGallery.Core/Services/CloudBlobWrapper.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,7 @@ private void UpdateEtag(BlobDownloadDetails details)
522522
if (details != null)
523523
{
524524
_lastSeenEtag = EtagToString(details.ETag);
525+
BlobProperties = new CloudBlobReadOnlyProperties(details);
525526
ReplaceHttpHeaders(details);
526527
ReplaceMetadata(details.Metadata);
527528
}

src/NuGetGallery.Services/Configuration/AppConfiguration.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ public class AppConfiguration : IAppConfiguration
7171
/// </summary>
7272
public bool AzureStorageReadAccessGeoRedundant { get; set; }
7373

74+
public bool AzureStorageUseMsi { get; set; } = false;
75+
76+
public string AzureStorageMsiClientId { get; set; } = null;
77+
7478
public TimeSpan FeatureFlagsRefreshInterval { get; set; }
7579

7680
[DefaultValue(true)]

src/NuGetGallery.Services/Configuration/IAppConfiguration.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,20 @@ public interface IAppConfiguration : IMessageServiceConfiguration
9090
/// </summary>
9191
bool AzureStorageReadAccessGeoRedundant { get; set; }
9292

93+
/// <summary>
94+
/// Indicates whether Managed Service Identity should be used to access Azure Storage.
95+
/// If false, the presumption is that connection strings contain the necessary credentials.
96+
/// If true, single MSI is going to be used for all storage connections.
97+
/// </summary>
98+
bool AzureStorageUseMsi { get; set; }
99+
100+
/// <summary>
101+
/// Client ID of the MSI to use for Azure storage access.
102+
/// If empty or not specified, the default MSI will be used in Release builds
103+
/// and <see cref="Azure.Identity.DefaultAzureCredential"/> in Debug builds.
104+
/// </summary>
105+
string AzureStorageMsiClientId { get; set; }
106+
93107
/// <summary>
94108
/// How frequently the feature flags should be refreshed.
95109
/// </summary>

src/NuGetGallery/App_Start/DefaultDependenciesModule.cs

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,9 @@ protected override void Load(ContainerBuilder builder)
237237
.InstancePerLifetimeScope();
238238

239239
builder.RegisterType<EntityRepository<AccountDelete>>()
240-
.AsSelf()
241-
.As<IEntityRepository<AccountDelete>>()
242-
.InstancePerLifetimeScope();
240+
.AsSelf()
241+
.As<IEntityRepository<AccountDelete>>()
242+
.InstancePerLifetimeScope();
243243

244244
builder.RegisterType<EntityRepository<Credential>>()
245245
.AsSelf()
@@ -1432,18 +1432,45 @@ private static void ConfigureForAzureStorage(ContainerBuilder builder, IGalleryC
14321432
{
14331433
if (completedBindingKeys.Add(dependent.BindingKey))
14341434
{
1435-
builder.RegisterInstance(new CloudBlobClientWrapper(dependent.AzureStorageConnectionString, configuration.Current.AzureStorageReadAccessGeoRedundant))
1436-
.AsSelf()
1437-
.As<ICloudBlobClient>()
1438-
.SingleInstance()
1439-
.Keyed<ICloudBlobClient>(dependent.BindingKey);
1435+
CloudBlobClientWrapper blobClient;
1436+
if (!configuration.Current.AzureStorageUseMsi)
1437+
{
1438+
blobClient = new CloudBlobClientWrapper(dependent.AzureStorageConnectionString, configuration.Current.AzureStorageReadAccessGeoRedundant);
1439+
}
1440+
else
1441+
{
1442+
if (string.IsNullOrWhiteSpace(configuration.Current.AzureStorageMsiClientId))
1443+
{
1444+
#if DEBUG
1445+
blobClient = CloudBlobClientWrapper.UsingDefaultAzureCredential(
1446+
dependent.AzureStorageConnectionString,
1447+
readAccessGeoRedundant: configuration.Current.AzureStorageReadAccessGeoRedundant);
1448+
#else
1449+
blobClient = CloudBlobClientWrapper.UsingMsi(
1450+
dependent.AzureStorageConnectionString,
1451+
readAccessGeoRedundant: configuration.Current.AzureStorageReadAccessGeoRedundant);
1452+
#endif
1453+
}
1454+
else
1455+
{
1456+
blobClient = CloudBlobClientWrapper.UsingMsi(
1457+
dependent.AzureStorageConnectionString,
1458+
configuration.Current.AzureStorageMsiClientId,
1459+
configuration.Current.AzureStorageReadAccessGeoRedundant);
1460+
}
1461+
}
1462+
builder.RegisterInstance(blobClient)
1463+
.AsSelf()
1464+
.As<ICloudBlobClient>()
1465+
.SingleInstance()
1466+
.Keyed<ICloudBlobClient>(dependent.BindingKey);
14401467

14411468
// Do not register the service as ICloudStorageStatusDependency because
14421469
// the CloudAuditingService registers it and the gallery uses the same storage account for all the containers.
14431470
builder.RegisterType<CloudBlobFileStorageService>()
14441471
.WithParameter(new ResolvedParameter(
1445-
(pi, ctx) => pi.ParameterType == typeof(ICloudBlobClient),
1446-
(pi, ctx) => ctx.ResolveKeyed<ICloudBlobClient>(dependent.BindingKey)))
1472+
(pi, ctx) => pi.ParameterType == typeof(ICloudBlobClient),
1473+
(pi, ctx) => ctx.ResolveKeyed<ICloudBlobClient>(dependent.BindingKey)))
14471474
.AsSelf()
14481475
.As<IFileStorageService>()
14491476
.As<ICoreFileStorageService>()

tests/NuGetGallery.Core.Facts/NuGetGallery.Core.Facts.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@
108108
<Compile Include="Services\CoreReadmeFileServiceFacts.cs" />
109109
<Compile Include="Services\CoreLicenseFileServiceFacts.cs" />
110110
<Compile Include="Services\CoreSymbolPackageServiceFacts.cs" />
111-
<Compile Include="Services\FileUriPermissionsFacts.cs" />
112111
<Compile Include="Services\FolderNamesDataAttribute.cs" />
113112
<Compile Include="Services\FolderNamesDataAttributeFacts.cs" />
114113
<Compile Include="Services\GalleryCloudBlobContainerInformationProviderFacts.cs" />

tests/NuGetGallery.Core.Facts/Services/FileUriPermissionsFacts.cs

Lines changed: 0 additions & 33 deletions
This file was deleted.

0 commit comments

Comments
 (0)