Skip to content

Commit c2fbcee

Browse files
authored
Use protected configuration provider instead of reflection (WITHOUT DI) (#8132)
Address #8121 Related to https://github.com/NuGet/Engineering/issues/3206
1 parent 94e4009 commit c2fbcee

7 files changed

Lines changed: 111 additions & 59 deletions

File tree

src/NuGetGallery.Services/Configuration/ConfigurationService.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Microsoft.WindowsAzure.ServiceRuntime;
1515
using NuGet.Services.Configuration;
1616
using NuGet.Services.KeyVault;
17+
using NuGetGallery.Configuration.SecretReader;
1718

1819
namespace NuGetGallery.Configuration
1920
{
@@ -40,6 +41,22 @@ public class ConfigurationService : IGalleryConfigurationService, IConfiguration
4041

4142
public ISecretInjector SecretInjector { get; set; }
4243

44+
/// <summary>
45+
/// Initializes the configuration service and associates a secret injector based on the configured KeyVault
46+
/// settings.
47+
/// </summary>
48+
public static ConfigurationService Initialize()
49+
{
50+
var configuration = new ConfigurationService();
51+
var secretReaderFactory = new SecretReaderFactory(configuration);
52+
var secretReader = secretReaderFactory.CreateSecretReader();
53+
var secretInjector = secretReaderFactory.CreateSecretInjector(secretReader);
54+
55+
configuration.SecretInjector = secretInjector;
56+
57+
return configuration;
58+
}
59+
4360
public ConfigurationService()
4461
{
4562
_httpSiteRootThunk = new Lazy<string>(GetHttpSiteRoot);

src/NuGetGallery/App_Start/DefaultDependenciesModule.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
using System.Net.Http;
1313
using System.Net.Mail;
1414
using System.Security.Principal;
15-
using System.Threading;
1615
using System.Threading.Tasks;
1716
using System.Web;
1817
using System.Web.Hosting;
@@ -58,7 +57,6 @@
5857
using NuGetGallery.Security;
5958
using NuGetGallery.Services;
6059
using Role = NuGet.Services.Entities.Role;
61-
using SecretReaderFactory = NuGetGallery.Configuration.SecretReader.SecretReaderFactory;
6260

6361
namespace NuGetGallery
6462
{
@@ -88,18 +86,14 @@ protected override void Load(ContainerBuilder builder)
8886
{
8987
var services = new ServiceCollection();
9088

91-
var configuration = new ConfigurationService();
92-
var secretReaderFactory = new SecretReaderFactory(configuration);
93-
var secretReader = secretReaderFactory.CreateSecretReader();
94-
var secretInjector = secretReaderFactory.CreateSecretInjector(secretReader);
89+
var configuration = ConfigurationService.Initialize();
90+
var secretInjector = configuration.SecretInjector;
9591

9692
builder.RegisterInstance(secretInjector)
9793
.AsSelf()
9894
.As<ISecretInjector>()
9995
.SingleInstance();
10096

101-
configuration.SecretInjector = secretInjector;
102-
10397
// Register the ILoggerFactory and configure AppInsights.
10498
var applicationInsightsConfiguration = ConfigureApplicationInsights(
10599
configuration.Current,
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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 System.Configuration;
6+
using System.Diagnostics;
7+
using System.Xml;
8+
using NuGetGallery.Configuration;
9+
10+
namespace NuGetGallery
11+
{
12+
public class GalleryMachineKeyConfigurationProvider : ProtectedConfigurationProvider
13+
{
14+
public static IGalleryConfigurationService Configuration { get; set; }
15+
16+
public override XmlNode Decrypt(XmlNode encryptedNode)
17+
{
18+
Trace.TraceInformation($"[{nameof(GalleryMachineKeyConfigurationProvider)}] Initializing machine key configuration.");
19+
20+
var xmlDoc = new XmlDocument();
21+
xmlDoc.XmlResolver = null;
22+
xmlDoc.AppendChild(xmlDoc.CreateElement(string.Empty, "machineKey", string.Empty));
23+
24+
// Get the configuration used for fetching the machine key settings. These will be cached for the lifetime
25+
// of the process. This is acceptable because this function's outupt is also cached for the duration of the
26+
// process by the .NET Framework configuration system.
27+
var config = Configuration;
28+
if (Configuration == null)
29+
{
30+
Trace.TraceWarning($"[{nameof(GalleryMachineKeyConfigurationProvider)}] Initializing dedicated configuration service.");
31+
config = ConfigurationService.Initialize();
32+
Trace.TraceWarning($"[{nameof(GalleryMachineKeyConfigurationProvider)}] Initialized dedicated configuration service.");
33+
Configuration = config;
34+
}
35+
36+
// The machine keys are used for encrypting/decrypting cookies used by ASP.NET, these are usually set by IIS in 'Auto' mode.
37+
// During a deployment to Azure cloud service the same machine key values are set on all the instances of a given cloud service,
38+
// thereby providing session persistence across different instances in the same deployment slot. However, across different slots(staging vs production)
39+
// these session keys are different. Thereby causing the loss of session upon a slot swap. Manually setting these values on role start ensures same
40+
// keys are used by all the instances across all the slots of a Azure cloud service. See more analysis here: https://github.com/NuGet/Engineering/issues/1329
41+
Trace.TraceInformation($"[{nameof(GalleryMachineKeyConfigurationProvider)}] Checking current gallery configuration.");
42+
if (config.Current.EnableMachineKeyConfiguration
43+
&& !string.IsNullOrWhiteSpace(config.Current.MachineKeyDecryption)
44+
&& !string.IsNullOrWhiteSpace(config.Current.MachineKeyDecryptionKey)
45+
&& !string.IsNullOrWhiteSpace(config.Current.MachineKeyValidationAlgorithm)
46+
&& !string.IsNullOrWhiteSpace(config.Current.MachineKeyValidationKey))
47+
{
48+
Trace.TraceInformation($"[{nameof(GalleryMachineKeyConfigurationProvider)}] Using machine key settings from gallery configuration.");
49+
xmlDoc.DocumentElement.SetAttribute("decryptionKey", config.Current.MachineKeyDecryptionKey);
50+
xmlDoc.DocumentElement.SetAttribute("decryption", config.Current.MachineKeyDecryption);
51+
xmlDoc.DocumentElement.SetAttribute("validationKey", config.Current.MachineKeyValidationKey);
52+
xmlDoc.DocumentElement.SetAttribute("validation", config.Current.MachineKeyValidationAlgorithm);
53+
}
54+
else
55+
{
56+
Trace.TraceInformation($"[{nameof(GalleryMachineKeyConfigurationProvider)}] Gallery configuration does not have custom machine key settings.");
57+
}
58+
59+
Trace.TraceInformation($"[{nameof(GalleryMachineKeyConfigurationProvider)}] Machine key configuration is complete.");
60+
61+
return xmlDoc.DocumentElement;
62+
}
63+
64+
public override XmlNode Encrypt(XmlNode node)
65+
{
66+
throw new NotImplementedException();
67+
}
68+
}
69+
}

src/NuGetGallery/App_Start/OwinStartup.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Configuration;
67
using System.Linq;
78
using System.Net;
89
using System.Threading;
@@ -12,7 +13,6 @@
1213
using System.Web.Http;
1314
using System.Web.Mvc;
1415
using Elmah;
15-
using Microsoft.Extensions.Logging;
1616
using Microsoft.Owin;
1717
using Microsoft.Owin.Logging;
1818
using Microsoft.Owin.Security;
@@ -26,8 +26,6 @@
2626
using NuGetGallery.Infrastructure;
2727
using Owin;
2828

29-
using ILoggerFactory = Microsoft.Extensions.Logging.ILoggerFactory;
30-
3129
[assembly: OwinStartup(typeof(NuGetGallery.OwinStartup))]
3230

3331
namespace NuGetGallery
@@ -64,8 +62,19 @@ public static void Configuration(IAppBuilder app)
6462
var config = dependencyResolver.GetService<IGalleryConfigurationService>();
6563
var auth = dependencyResolver.GetService<AuthenticationService>();
6664

67-
// Configure machine key for session persistence across slots
68-
SessionPersistence.Setup(config);
65+
// Ensure the machine key provider has the shared configuration instance and force the machine key
66+
// configuration section to be initialized. This is normally done only when the first request needs the
67+
// machine key but we choose to aggressively execute the initialization here outside of the request context
68+
// since it is internally awaiting an asynchronous API in a synchronous method. This cannot be done in a
69+
// request context because it will cause a deadlock.
70+
//
71+
// Note that is is technically possible for some code before this to initialize the machine key (e.g. by
72+
// calling an API that uses the machine key configuration). If this happens, the machine key will be
73+
// fetched from KeyVault seperately. This will be slightly slower (two KeyVault secret resolutions instead
74+
// of one) but will not be harmful.
75+
GalleryMachineKeyConfigurationProvider.Configuration = config;
76+
ConfigurationManager.GetSection("system.web/machineKey");
77+
6978
// Refresh the content for the ContentObjectService to guarantee it has loaded the latest configuration on startup.
7079
var contentObjectService = dependencyResolver.GetService<IContentObjectService>();
7180
HostingEnvironment.QueueBackgroundWorkItem(async token =>

src/NuGetGallery/App_Start/SessionPersistence.cs

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

src/NuGetGallery/NuGetGallery.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,12 @@
125125
</ItemGroup>
126126
<ItemGroup>
127127
<Compile Include="ActionName.cs" />
128+
<Compile Include="App_Start\GalleryMachineKeyConfigurationProvider.cs" />
128129
<Compile Include="App_Start\LatestVersionRouteConstraint.cs" />
129130
<Compile Include="App_Start\NuGetODataV2FeedConfig.cs" />
130131
<Compile Include="App_Start\NuGetODataV1FeedConfig.cs" />
131132
<Compile Include="App_Start\NuGetODataConfig.cs" />
132133
<Compile Include="App_Start\StorageDependent.cs" />
133-
<Compile Include="App_Start\SessionPersistence.cs" />
134134
<Compile Include="App_Start\WebApiConfig.cs" />
135135
<Compile Include="App_Start\AutofacConfig.cs" />
136136
<Compile Include="Areas\Admin\Controllers\ApiKeysController.cs" />

src/NuGetGallery/Web.config

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
<section name="dataCacheClients" type="Microsoft.ApplicationServer.Caching.DataCacheClientsSection, Microsoft.ApplicationServer.Caching.Core" allowLocation="true" allowDefinition="Everywhere"/>
1919
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
2020
</configSections>
21+
<configProtectedData>
22+
<providers>
23+
<add name="GalleryMachineKeyConfigurationProvider" type="NuGetGallery.GalleryMachineKeyConfigurationProvider, NuGetGallery"/>
24+
</providers>
25+
</configProtectedData>
2126
<appSettings>
2227
<!-- If you're running in Azure, we suggest you set these in your .cscfg file. -->
2328
<!-- ******************* -->
@@ -354,6 +359,9 @@
354359
<error statusCode="500" redirect="~/App_500.aspx"/>
355360
</customErrors>
356361
<sessionState mode="Off"/>
362+
<machineKey configProtectionProvider="GalleryMachineKeyConfigurationProvider">
363+
<EncryptedData/>
364+
</machineKey>
357365
</system.web>
358366
<system.webServer>
359367
<tracing>

0 commit comments

Comments
 (0)