Skip to content

Commit b99c08f

Browse files
authored
Reset cached secrets on SyncTriggers (#7698) (#7931)
1 parent 4e6462e commit b99c08f

File tree

9 files changed

+259
-71
lines changed

9 files changed

+259
-71
lines changed

src/WebJobs.Script.WebHost/Management/FunctionsSyncManager.cs

Lines changed: 63 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ public async Task<SyncTriggersResult> TrySyncTriggersAsync(bool isBackgroundSync
106106
{
107107
await _syncSemaphore.WaitAsync();
108108

109+
PrepareSyncTriggers();
110+
109111
var hashBlobClient = await GetHashBlobAsync();
110112
if (isBackgroundSync && hashBlobClient == null && !_environment.IsKubernetesManagedHosting())
111113
{
@@ -160,6 +162,31 @@ public async Task<SyncTriggersResult> TrySyncTriggersAsync(bool isBackgroundSync
160162
return result;
161163
}
162164

165+
/// <summary>
166+
/// SyncTriggers is performed whenever deployments or other changes are made to the application.
167+
/// There are some operations we want to perform whenever this happens.
168+
/// </summary>
169+
private void PrepareSyncTriggers()
170+
{
171+
// We clear cache to ensure that secrets are reloaded. This is important because secrets are part
172+
// of the StartupContext payload (see StartupContextProvider) and that payload comes from the
173+
// SyncTriggers operation. So there's a chicken and egg situation here. Consider the following scenario:
174+
// - app is using blob storage for keys
175+
// - a SyncTriggers operation has happened previously and the StartupContext has key info
176+
// - app instances initialize keys from StartupContext (keys aren't loaded from storage)
177+
// - user updates the app to use a new storage account
178+
// - a SyncTriggers operation is performed
179+
// - the app initializes from StartupContext, and **previous old key info is loaded**
180+
// - the SyncTriggers operation uses this old key info, so trigger cache is never updated with new key info
181+
// - Portal/ARM APIs will continue to show old key info.
182+
// By clearing cache, we ensure that this host instance reloads keys when they're requested, and the SyncTriggers
183+
// operation will contain current keys.
184+
if (_secretManagerProvider.SecretsEnabled)
185+
{
186+
_secretManagerProvider.Current.ClearCache();
187+
}
188+
}
189+
163190
internal static bool IsSyncTriggersEnvironment(IScriptWebHostEnvironment webHostEnvironment, IEnvironment environment)
164191
{
165192
if (environment.IsCoreTools())
@@ -316,45 +343,48 @@ public async Task<SyncTriggersPayload> GetSyncTriggersPayload()
316343
}
317344
}
318345

319-
// Add functions secrets to the payload
320-
// Only secret types we own/control can we cache directly
321-
// Encryption is handled by Antares before storage
322-
var secretsStorageType = _environment.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebJobsSecretStorageType);
323-
if (string.IsNullOrEmpty(secretsStorageType) ||
324-
string.Compare(secretsStorageType, "files", StringComparison.OrdinalIgnoreCase) == 0 ||
325-
string.Compare(secretsStorageType, "blob", StringComparison.OrdinalIgnoreCase) == 0)
346+
if (_secretManagerProvider.SecretsEnabled)
326347
{
327-
var functionAppSecrets = new FunctionAppSecrets();
328-
329-
// add host secrets
330-
var hostSecretsInfo = await _secretManagerProvider.Current.GetHostSecretsAsync();
331-
functionAppSecrets.Host = new FunctionAppSecrets.HostSecrets
348+
// Add functions secrets to the payload
349+
// Only secret types we own/control can we cache directly
350+
// Encryption is handled by Antares before storage
351+
var secretsStorageType = _environment.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebJobsSecretStorageType);
352+
if (string.IsNullOrEmpty(secretsStorageType) ||
353+
string.Compare(secretsStorageType, "files", StringComparison.OrdinalIgnoreCase) == 0 ||
354+
string.Compare(secretsStorageType, "blob", StringComparison.OrdinalIgnoreCase) == 0)
332355
{
333-
Master = hostSecretsInfo.MasterKey,
334-
Function = hostSecretsInfo.FunctionKeys,
335-
System = hostSecretsInfo.SystemKeys
336-
};
356+
var functionAppSecrets = new FunctionAppSecrets();
337357

338-
// add function secrets
339-
var httpFunctions = functionsMetadata.Where(p => p.InputBindings.Any(q => q.IsTrigger && string.Compare(q.Type, "httptrigger", StringComparison.OrdinalIgnoreCase) == 0)).Select(p => p.Name).ToArray();
340-
functionAppSecrets.Function = new FunctionAppSecrets.FunctionSecrets[httpFunctions.Length];
341-
for (int i = 0; i < httpFunctions.Length; i++)
342-
{
343-
var currFunctionName = httpFunctions[i];
344-
var currSecrets = await _secretManagerProvider.Current.GetFunctionSecretsAsync(currFunctionName);
345-
functionAppSecrets.Function[i] = new FunctionAppSecrets.FunctionSecrets
358+
// add host secrets
359+
var hostSecretsInfo = await _secretManagerProvider.Current.GetHostSecretsAsync();
360+
functionAppSecrets.Host = new FunctionAppSecrets.HostSecrets
346361
{
347-
Name = currFunctionName,
348-
Secrets = currSecrets
362+
Master = hostSecretsInfo.MasterKey,
363+
Function = hostSecretsInfo.FunctionKeys,
364+
System = hostSecretsInfo.SystemKeys
349365
};
350-
}
351366

352-
result.Add("secrets", JObject.FromObject(functionAppSecrets));
353-
}
354-
else
355-
{
356-
// TODO: handle other external key storage types
357-
// like KeyVault when the feature comes online
367+
// add function secrets
368+
var httpFunctions = functionsMetadata.Where(p => p.InputBindings.Any(q => q.IsTrigger && string.Compare(q.Type, "httptrigger", StringComparison.OrdinalIgnoreCase) == 0)).Select(p => p.Name).ToArray();
369+
functionAppSecrets.Function = new FunctionAppSecrets.FunctionSecrets[httpFunctions.Length];
370+
for (int i = 0; i < httpFunctions.Length; i++)
371+
{
372+
var currFunctionName = httpFunctions[i];
373+
var currSecrets = await _secretManagerProvider.Current.GetFunctionSecretsAsync(currFunctionName);
374+
functionAppSecrets.Function[i] = new FunctionAppSecrets.FunctionSecrets
375+
{
376+
Name = currFunctionName,
377+
Secrets = currSecrets
378+
};
379+
}
380+
381+
result.Add("secrets", JObject.FromObject(functionAppSecrets));
382+
}
383+
else
384+
{
385+
// TODO: handle other external key storage types
386+
// like KeyVault when the feature comes online
387+
}
358388
}
359389

360390
string json = JsonConvert.SerializeObject(result);

0 commit comments

Comments
 (0)