Skip to content

Commit 40084eb

Browse files
VpOfEngineeringfabiocavTonewallTony Choijzaballos
authored
3.21.1 hotfix (#9794)
* 3.21.1 --------- Co-authored-by: Tony Choi <[email protected]> * Bump minor version to 1 * Adding additional WebJobs assemblies to deps validation exclusion (#9795) (#9796) --------- Co-authored-by: Fabio Cavalcante <[email protected]> Co-authored-by: Tony Choi <[email protected]> Co-authored-by: Tony Choi <[email protected]> Co-authored-by: jzaballos <[email protected]>
1 parent 2084757 commit 40084eb

File tree

7 files changed

+183
-19
lines changed

7 files changed

+183
-19
lines changed

build/common.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<LangVersion>latest</LangVersion>
66
<MajorVersion>3</MajorVersion>
77
<MinorVersion>21</MinorVersion>
8-
<PatchVersion>0</PatchVersion>
8+
<PatchVersion>1</PatchVersion>
99
<VersionPrefix>$(MajorVersion).$(MinorVersion).$(PatchVersion)</VersionPrefix>
1010
<Version Condition=" '$(BuildNumber)' != '' ">$(VersionPrefix)-$(BuildNumber)</Version>
1111
<Version Condition=" '$(Version)' == '' ">$(VersionPrefix)</Version>

src/WebJobs.Script.WebHost/Security/Authentication/Jwt/ScriptJwtBearerExtensions.cs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,25 @@ public static AuthenticationBuilder AddScriptJwtBearer(this AuthenticationBuilde
8080
}
8181
});
8282

83-
private static TokenValidationParameters CreateTokenValidationParameters()
83+
private static string[] GetValidAudiences()
84+
{
85+
if (SystemEnvironment.Instance.IsPlaceholderModeEnabled()
86+
&& SystemEnvironment.Instance.IsLinuxConsumptionOnAtlas())
87+
{
88+
return new string[]
89+
{
90+
ScriptSettingsManager.Instance.GetSetting(ContainerName)
91+
};
92+
}
93+
94+
return new string[]
95+
{
96+
string.Format(SiteAzureFunctionsUriFormat, ScriptSettingsManager.Instance.GetSetting(AzureWebsiteName)),
97+
string.Format(SiteUriFormat, ScriptSettingsManager.Instance.GetSetting(AzureWebsiteName))
98+
};
99+
}
100+
101+
public static TokenValidationParameters CreateTokenValidationParameters()
84102
{
85103
var signingKeys = SecretsUtility.GetTokenIssuerSigningKeys();
86104
var result = new TokenValidationParameters();
@@ -89,11 +107,7 @@ private static TokenValidationParameters CreateTokenValidationParameters()
89107
result.IssuerSigningKeys = signingKeys;
90108
result.AudienceValidator = AudienceValidator;
91109
result.IssuerValidator = IssuerValidator;
92-
result.ValidAudiences = new string[]
93-
{
94-
string.Format(SiteAzureFunctionsUriFormat, ScriptSettingsManager.Instance.GetSetting(AzureWebsiteName)),
95-
string.Format(SiteUriFormat, ScriptSettingsManager.Instance.GetSetting(AzureWebsiteName))
96-
};
110+
result.ValidAudiences = GetValidAudiences();
97111
result.ValidIssuers = new string[]
98112
{
99113
AppServiceCoreUri,

src/WebJobs.Script.WebHost/Security/SimpleWebTokenHelper.cs

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ public static class SimpleWebTokenHelper
1515
/// <summary>
1616
/// A SWT or a Simple Web Token is a token that's made of key=value pairs separated
1717
/// by &. We only specify expiration in ticks from now (exp={ticks})
18-
/// The SWT is then returned as an encrypted string
18+
/// The SWT is then returned as an encrypted string.
1919
/// </summary>
20-
/// <param name="validUntil">Datetime for when the token should expire</param>
21-
/// <param name="key">Optional key to encrypt the token with</param>
22-
/// <returns>a SWT signed by this app</returns>
20+
/// <param name="validUntil">Datetime for when the token should expire.</param>
21+
/// <param name="key">Optional key to encrypt the token with.</param>
22+
/// <returns>a SWT signed by this app.</returns>
2323
public static string CreateToken(DateTime validUntil, byte[] key = null) => Encrypt($"exp={validUntil.Ticks}", key);
2424

25-
internal static string Encrypt(string value, byte[] key = null, IEnvironment environment = null)
25+
internal static string Encrypt(string value, byte[] key = null, IEnvironment environment = null, bool includesSignature = false)
2626
{
2727
key = key ?? SecretsUtility.GetEncryptionKey(environment);
2828

@@ -43,23 +43,31 @@ internal static string Encrypt(string value, byte[] key = null, IEnvironment env
4343
cryptoStream.FlushFinalBlock();
4444
}
4545

46-
// return {iv}.{swt}.{sha236(key)}
47-
return string.Format("{0}.{1}.{2}", iv, Convert.ToBase64String(cipherStream.ToArray()), GetSHA256Base64String(aes.Key));
46+
if (includesSignature)
47+
{
48+
return $"{Convert.ToBase64String(aes.IV)}.{Convert.ToBase64String(cipherStream.ToArray())}.{GetSHA256Base64String(aes.Key)}.{Convert.ToBase64String(ComputeHMACSHA256(aes.Key, input))}";
49+
}
50+
else
51+
{
52+
// return {iv}.{swt}.{sha236(key)}
53+
return string.Format("{0}.{1}.{2}", iv, Convert.ToBase64String(cipherStream.ToArray()), GetSHA256Base64String(aes.Key));
54+
}
4855
}
4956
}
5057
}
5158

5259
public static string Decrypt(byte[] encryptionKey, string value)
5360
{
5461
var parts = value.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
55-
if (parts.Length != 2 && parts.Length != 3)
62+
if (parts.Length != 2 && parts.Length != 3 && parts.Length != 4)
5663
{
5764
throw new InvalidOperationException("Malformed token.");
5865
}
5966

6067
var iv = Convert.FromBase64String(parts[0]);
6168
var data = Convert.FromBase64String(parts[1]);
6269
var base64KeyHash = parts.Length == 3 ? parts[2] : null;
70+
var signature = parts.Length == 4 ? Convert.FromBase64String(parts[3]) : null;
6371

6472
if (!string.IsNullOrEmpty(base64KeyHash) && !string.Equals(GetSHA256Base64String(encryptionKey), base64KeyHash))
6573
{
@@ -76,11 +84,25 @@ public static string Decrypt(byte[] encryptionKey, string value)
7684
binaryWriter.Write(data, 0, data.Length);
7785
}
7886

79-
return Encoding.UTF8.GetString(ms.ToArray());
87+
var input = ms.ToArray();
88+
if (signature != null && !signature.SequenceEqual(ComputeHMACSHA256(encryptionKey, input)))
89+
{
90+
throw new InvalidOperationException("Signature mismatches!");
91+
}
92+
93+
return Encoding.UTF8.GetString(input);
8094
}
8195
}
8296
}
8397

98+
private static byte[] ComputeHMACSHA256(byte[] key, byte[] input)
99+
{
100+
using (var hmacSha256 = new HMACSHA256(key))
101+
{
102+
return hmacSha256.ComputeHash(input);
103+
}
104+
}
105+
84106
public static string Decrypt(string value, IEnvironment environment = null)
85107
{
86108
byte[] key = SecretsUtility.GetEncryptionKey(environment);

src/WebJobs.Script/Extensions/HttpRequestExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ public static bool IsAppServiceInternalRequest(this HttpRequest request, IEnviro
8686
}
8787

8888
// this header will *always* be present on requests originating externally (i.e. going
89-
// through the Anatares front end). For requests originating internally it will NOT be
89+
// through the Antares front end). For requests originating internally it will NOT be
9090
// present.
91-
return !request.Headers.Keys.Contains(ScriptConstants.AntaresLogIdHeaderName);
91+
return !request.Headers.ContainsKey(ScriptConstants.AntaresLogIdHeaderName);
9292
}
9393

9494
public static bool IsPlatformInternalRequest(this HttpRequest request, IEnvironment environment = null)

test/WebJobs.Script.Tests/DependencyTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ public class DependencyTests
2020
{
2121
"Microsoft.Azure.WebJobs.Script.WebHost.dll",
2222
"Microsoft.Azure.WebJobs.dll",
23+
"Microsoft.Azure.WebJobs.Script.dll",
24+
"Microsoft.Azure.WebJobs.Script.Grpc.dll",
2325
"Microsoft.Azure.WebJobs.Host.dll",
2426
"Microsoft.Azure.WebJobs.Logging.ApplicationInsights.dll",
2527
"Microsoft.Azure.WebJobs.Script.Abstractions.dll",
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using Microsoft.Azure.WebJobs.Script.Config;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Xunit;
10+
using static Microsoft.Azure.WebJobs.Script.EnvironmentSettingNames;
11+
12+
namespace Microsoft.Azure.WebJobs.Script.Tests.Extensions
13+
{
14+
public class ScriptJwtBearerExtensionsTests
15+
{
16+
[Theory]
17+
[InlineData(true, true)]
18+
[InlineData(true, false)]
19+
[InlineData(false, true)]
20+
[InlineData(false, false)]
21+
public void CreateTokenValidationParameters_HasExpectedAudience(bool isPlaceholderModeEnabled, bool isLinuxConsumptionOnAtlas)
22+
{
23+
var containerName = "RandomContainerName";
24+
var siteName = "RandomSiteName";
25+
ScriptSettingsManager.Instance.SetSetting(AzureWebsiteName, siteName);
26+
27+
var expectedWithSiteName = new string[]
28+
{
29+
string.Format(ScriptConstants.SiteAzureFunctionsUriFormat, ScriptSettingsManager.Instance.GetSetting(AzureWebsiteName)),
30+
string.Format(ScriptConstants.SiteUriFormat, ScriptSettingsManager.Instance.GetSetting(AzureWebsiteName))
31+
};
32+
var expectedWithContainerName = new string[]
33+
{
34+
containerName
35+
};
36+
37+
var testData = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
38+
39+
if (isPlaceholderModeEnabled)
40+
{
41+
testData[AzureWebsitePlaceholderMode] = "1";
42+
}
43+
44+
if (isLinuxConsumptionOnAtlas)
45+
{
46+
testData[AzureWebsiteInstanceId] = string.Empty;
47+
testData[ContainerName] = containerName;
48+
}
49+
50+
testData[ContainerEncryptionKey] = Convert.ToBase64String(TestHelpers.GenerateKeyBytes());
51+
using (new TestScopedEnvironmentVariable(testData))
52+
{
53+
var tokenValidationParameters = ScriptJwtBearerExtensions.CreateTokenValidationParameters();
54+
var audiences = tokenValidationParameters.ValidAudiences.ToList();
55+
56+
if (isPlaceholderModeEnabled &&
57+
isLinuxConsumptionOnAtlas)
58+
{
59+
Assert.Equal(audiences.Count, expectedWithContainerName.Length);
60+
Assert.Equal(audiences[0], expectedWithContainerName[0]);
61+
}
62+
else
63+
{
64+
Assert.Equal(audiences.Count, expectedWithSiteName.Length);
65+
Assert.True(audiences.Contains(expectedWithSiteName[0]));
66+
Assert.True(audiences.Contains(expectedWithSiteName[1]));
67+
}
68+
}
69+
}
70+
}
71+
}

test/WebJobs.Script.Tests/Helpers/SimpleWebTokenTests.cs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Security.Cryptography;
5+
using System.Collections.Generic;
66
using Microsoft.AspNetCore.Authentication;
7+
using Microsoft.Azure.WebJobs.Script.WebHost;
8+
using Microsoft.Azure.WebJobs.Script.WebHost.Models;
79
using Microsoft.Azure.WebJobs.Script.WebHost.Security;
10+
using Newtonsoft.Json;
811
using Xunit;
912

1013
namespace Microsoft.Azure.WebJobs.Script.Tests.Helpers
@@ -86,11 +89,63 @@ public void Validate_Token_Uses_Website_Encryption_Key_If_Container_Encryption_K
8689
Assert.True(SimpleWebTokenHelper.TryValidateToken(token, new SystemClock()));
8790
}
8891

92+
[Fact]
93+
public void Validate_Token_Checks_Signature_If_Signature_Is_Available()
94+
{
95+
var websiteAuthEncryptionKey = TestHelpers.GenerateKeyBytes();
96+
var websiteAuthEncryptionStringKey = TestHelpers.GenerateKeyHexString(websiteAuthEncryptionKey);
97+
98+
var timeStamp = DateTime.UtcNow.AddHours(1);
99+
100+
Environment.SetEnvironmentVariable(EnvironmentSettingNames.ContainerEncryptionKey, string.Empty);
101+
Environment.SetEnvironmentVariable(EnvironmentSettingNames.WebSiteAuthEncryptionKey, websiteAuthEncryptionStringKey);
102+
103+
var token = SimpleWebTokenHelper.Encrypt($"exp={timeStamp.Ticks}", websiteAuthEncryptionKey, includesSignature: true);
104+
105+
Assert.True(SimpleWebTokenHelper.TryValidateToken(token, new SystemClock()));
106+
}
107+
108+
[Fact]
109+
public void Encrypt_And_Decrypt_Context_With_Signature()
110+
{
111+
var websiteAuthEncryptionKey = TestHelpers.GenerateKeyBytes();
112+
var websiteAuthEncryptionStringKey = TestHelpers.GenerateKeyHexString(websiteAuthEncryptionKey);
113+
var hostContext = GetHostAssignmentContext();
114+
var hostContextJson = JsonConvert.SerializeObject(hostContext);
115+
116+
Environment.SetEnvironmentVariable(EnvironmentSettingNames.ContainerEncryptionKey, string.Empty);
117+
Environment.SetEnvironmentVariable(EnvironmentSettingNames.WebSiteAuthEncryptionKey, websiteAuthEncryptionStringKey);
118+
119+
var encryptedHostContextWithSignature = SimpleWebTokenHelper.Encrypt(hostContextJson, websiteAuthEncryptionKey, includesSignature: true);
120+
121+
var decryptedHostContextJson = SimpleWebTokenHelper.Decrypt(websiteAuthEncryptionKey, encryptedHostContextWithSignature);
122+
123+
Assert.Equal(hostContextJson, decryptedHostContextJson);
124+
}
125+
89126
public void Dispose()
90127
{
91128
// Clean up
92129
Environment.SetEnvironmentVariable(EnvironmentSettingNames.WebSiteAuthEncryptionKey, string.Empty);
93130
Environment.SetEnvironmentVariable(EnvironmentSettingNames.ContainerEncryptionKey, string.Empty);
94131
}
132+
133+
private static HostAssignmentContext GetHostAssignmentContext()
134+
{
135+
var hostAssignmentContext = new HostAssignmentContext();
136+
hostAssignmentContext.SiteId = 1;
137+
hostAssignmentContext.SiteName = "sitename";
138+
hostAssignmentContext.LastModifiedTime = DateTime.UtcNow.Add(TimeSpan.FromMinutes(new Random().Next()));
139+
hostAssignmentContext.Environment = new Dictionary<string, string>();
140+
hostAssignmentContext.MSIContext = new MSIContext();
141+
hostAssignmentContext.EncryptedTokenServiceSpecializationPayload = "payload";
142+
hostAssignmentContext.TokenServiceApiEndpoint = "endpoints";
143+
hostAssignmentContext.CorsSettings = new CorsSettings();
144+
hostAssignmentContext.EasyAuthSettings = new EasyAuthSettings();
145+
hostAssignmentContext.Secrets = new FunctionAppSecrets();
146+
hostAssignmentContext.IsWarmupRequest = false;
147+
148+
return hostAssignmentContext;
149+
}
95150
}
96151
}

0 commit comments

Comments
 (0)