From 71193d656c58795f20e61989e3cfbf743ad3a4af Mon Sep 17 00:00:00 2001 From: Mathew Charles Date: Fri, 9 Jun 2023 11:10:32 -0700 Subject: [PATCH] Fixing key defaulting regression (#9307) --- .../Jwt/ScriptJwtBearerExtensions.cs | 11 ++---- .../Security/SecretsUtility.cs | 27 +++++++++++++ .../WebHostEndToEnd/SWAEndToEndTests.cs | 39 ++++++++++++++----- 3 files changed, 60 insertions(+), 17 deletions(-) diff --git a/src/WebJobs.Script.WebHost/Security/Authentication/Jwt/ScriptJwtBearerExtensions.cs b/src/WebJobs.Script.WebHost/Security/Authentication/Jwt/ScriptJwtBearerExtensions.cs index 5ff6927cc2..6dde506446 100644 --- a/src/WebJobs.Script.WebHost/Security/Authentication/Jwt/ScriptJwtBearerExtensions.cs +++ b/src/WebJobs.Script.WebHost/Security/Authentication/Jwt/ScriptJwtBearerExtensions.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Security.Claims; -using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; @@ -73,15 +72,11 @@ public static AuthenticationBuilder AddScriptJwtBearer(this AuthenticationBuilde private static TokenValidationParameters CreateTokenValidationParameters() { + var signingKeys = SecretsUtility.GetTokenIssuerSigningKeys(); var result = new TokenValidationParameters(); - if (SecretsUtility.TryGetEncryptionKey(out string key)) + if (signingKeys.Length > 0) { - // TODO: Once ScriptSettingsManager is gone, Audience and Issuer should be pulled from configuration. - result.IssuerSigningKeys = new SecurityKey[] - { - new SymmetricSecurityKey(key.ToKeyBytes()), - new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)) - }; + result.IssuerSigningKeys = signingKeys; result.ValidateAudience = true; result.ValidateIssuer = true; result.ValidAudiences = new string[] diff --git a/src/WebJobs.Script.WebHost/Security/SecretsUtility.cs b/src/WebJobs.Script.WebHost/Security/SecretsUtility.cs index 95c9857616..8eb59f5f48 100644 --- a/src/WebJobs.Script.WebHost/Security/SecretsUtility.cs +++ b/src/WebJobs.Script.WebHost/Security/SecretsUtility.cs @@ -2,8 +2,11 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; +using System.Text; using Microsoft.Azure.Web.DataProtection; +using Microsoft.IdentityModel.Tokens; namespace Microsoft.Azure.WebJobs.Script.WebHost { @@ -68,6 +71,30 @@ public static byte[] GetEncryptionKey(IEnvironment environment = null) return key.ToKeyBytes(); } + public static SymmetricSecurityKey[] GetTokenIssuerSigningKeys() + { + List signingKeys = new List(); + + // first we want to use the DataProtection APIs to get the default key, + // which will return any user specified AzureWebEncryptionKey with precedence + // over the platform default key + string defaultKey = Util.GetDefaultKeyValue(); + if (defaultKey != null) + { + signingKeys.Add(new SymmetricSecurityKey(defaultKey.ToKeyBytes())); + signingKeys.Add(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(defaultKey))); + } + + // next we want to ensure a key is also added for the platform default key + // if it wasn't already added above + if (SecretsUtility.TryGetEncryptionKey(out string key) && !string.Equals(key, defaultKey)) + { + signingKeys.Add(new SymmetricSecurityKey(key.ToKeyBytes())); + } + + return signingKeys.ToArray(); + } + public static byte[] ToKeyBytes(this string hexOrBase64) { // only support 32 bytes (256 bits) key length diff --git a/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/SWAEndToEndTests.cs b/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/SWAEndToEndTests.cs index 3ac174da05..c033ae528b 100644 --- a/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/SWAEndToEndTests.cs +++ b/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/SWAEndToEndTests.cs @@ -1,10 +1,6 @@ -using Microsoft.Azure.WebJobs.Script.WebHost; -using Microsoft.Azure.WebJobs.Script.WebHost.Management; -using Microsoft.Azure.WebJobs.Script.WebHost.Security; -using Microsoft.Azure.WebJobs.Script.Workers.Rpc; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Newtonsoft.Json; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using System; using System.Collections.Generic; using System.Linq; @@ -12,6 +8,12 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; +using Microsoft.Azure.WebJobs.Script.WebHost; +using Microsoft.Azure.WebJobs.Script.WebHost.Management; +using Microsoft.Azure.WebJobs.Script.Workers.Rpc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Newtonsoft.Json; using Xunit; namespace Microsoft.Azure.WebJobs.Script.Tests.Integration.WebHostEndToEnd @@ -68,7 +70,7 @@ public async Task InvokeFunction_FunctionLevel_ValidToken_Succeeds(string header { // if an admin token is passed, the function invocation succeeds HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "api/HttpTrigger-FunctionAuth?code=test"); - string token = _fixture.Host.GenerateAdminJwtToken(); + string token = GetSWAAdminJwtToken(); if (string.Compare(nameof(HttpRequestHeader.Authorization), headerName) == 0) { @@ -83,6 +85,16 @@ public async Task InvokeFunction_FunctionLevel_ValidToken_Succeeds(string header response.EnsureSuccessStatusCode(); } + private string GetSWAAdminJwtToken() + { + // Ensure we use AzureWebEncryptionKey to generate tokens, as that's what SWA does + string keyValue = _fixture.SWAEncryptionKey; + byte[] keyBytes = keyValue.ToKeyBytes(); + string token = _fixture.Host.GenerateAdminJwtToken(key: keyBytes); + + return token; + } + [Fact] public async Task SyncTriggers_Succeeds() { @@ -109,19 +121,28 @@ public class TestFixture : EndToEndTestFixture public TestFixture() : base(@"TestScripts\CSharp", "csharp", RpcWorkerConstants.DotNetLanguageWorkerName, addTestSettings: false) { + // SWA generates their own key and sets via AzureWebEncryptionKey + // This should take precedence over the default key var testKeyBytes = TestHelpers.GenerateKeyBytes(); var testKey = TestHelpers.GenerateKeyHexString(testKeyBytes); + SWAEncryptionKey = testKey; + + // Default key provisioned by Antares and available via WEBSITE_AUTH_ENCRYPTION_KEY + var defaultTestKeyBytes = TestHelpers.GenerateKeyBytes(); + var defaultTestKey = TestHelpers.GenerateKeyHexString(defaultTestKeyBytes); var settings = new Dictionary() { { "AzureWebEncryptionKey", testKey }, - { EnvironmentSettingNames.WebSiteAuthEncryptionKey, testKey }, + { EnvironmentSettingNames.WebSiteAuthEncryptionKey, defaultTestKey }, { "AzureWebJobsStorage", null }, { EnvironmentSettingNames.AzureWebsiteName, "testsite" } }; _scopedEnvironment = new TestScopedEnvironmentVariable(settings); } + public string SWAEncryptionKey { get; } + public override void ConfigureScriptHost(IWebJobsBuilder webJobsBuilder) { base.ConfigureScriptHost(webJobsBuilder);