@@ -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