Skip to content

Commit dbb335a

Browse files
authored
Update valid audience for legion applications during specialization (#9325)
Update valid audience for legion applications during specialization.
1 parent 85be1e2 commit dbb335a

File tree

6 files changed

+153
-54
lines changed

6 files changed

+153
-54
lines changed

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

Lines changed: 57 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -26,52 +26,70 @@ public static class ScriptJwtBearerExtensions
2626

2727
public static AuthenticationBuilder AddScriptJwtBearer(this AuthenticationBuilder builder)
2828
=> builder.AddJwtBearer(o =>
29+
{
30+
o.Events = new JwtBearerEvents()
31+
{
32+
OnMessageReceived = c =>
33+
{
34+
// By default, tokens are passed via the standard Authorization Bearer header. However we also support
35+
// passing tokens via the x-ms-site-token header.
36+
if (c.Request.Headers.TryGetValue(ScriptConstants.SiteTokenHeaderName, out StringValues values))
37+
{
38+
// the token we set here will be the one used - Authorization header won't be checked.
39+
c.Token = values.FirstOrDefault();
40+
}
41+
42+
// Temporary: Tactical fix to address specialization issues. This should likely be moved to a token validator
43+
// TODO: DI (FACAVAL) This will be fixed once the permanent fix is in place
44+
if (_specialized == 0 && !SystemEnvironment.Instance.IsPlaceholderModeEnabled() && Interlocked.CompareExchange(ref _specialized, 1, 0) == 0)
45+
{
46+
o.TokenValidationParameters = CreateTokenValidationParameters();
47+
}
48+
49+
return Task.CompletedTask;
50+
},
51+
OnTokenValidated = c =>
52+
{
53+
c.Principal.AddIdentity(new ClaimsIdentity(new Claim[]
2954
{
30-
o.Events = new JwtBearerEvents()
31-
{
32-
OnMessageReceived = c =>
33-
{
34-
// By default, tokens are passed via the standard Authorization Bearer header. However we also support
35-
// passing tokens via the x-ms-site-token header.
36-
if (c.Request.Headers.TryGetValue(ScriptConstants.SiteTokenHeaderName, out StringValues values))
37-
{
38-
// the token we set here will be the one used - Authorization header won't be checked.
39-
c.Token = values.FirstOrDefault();
40-
}
55+
new Claim(SecurityConstants.AuthLevelClaimType, AuthorizationLevel.Admin.ToString())
56+
}));
4157

42-
// Temporary: Tactical fix to address specialization issues. This should likely be moved to a token validator
43-
// TODO: DI (FACAVAL) This will be fixed once the permanent fix is in place
44-
if (_specialized == 0 && !SystemEnvironment.Instance.IsPlaceholderModeEnabled() && Interlocked.CompareExchange(ref _specialized, 1, 0) == 0)
45-
{
46-
o.TokenValidationParameters = CreateTokenValidationParameters();
47-
}
58+
c.Success();
4859

49-
return Task.CompletedTask;
50-
},
51-
OnTokenValidated = c =>
52-
{
53-
c.Principal.AddIdentity(new ClaimsIdentity(new Claim[]
54-
{
55-
new Claim(SecurityConstants.AuthLevelClaimType, AuthorizationLevel.Admin.ToString())
56-
}));
60+
return Task.CompletedTask;
61+
}
62+
};
5763

58-
c.Success();
64+
o.TokenValidationParameters = CreateTokenValidationParameters();
5965

60-
return Task.CompletedTask;
61-
}
62-
};
66+
// TODO: DI (FACAVAL) Remove this once the work above is completed.
67+
if (!SystemEnvironment.Instance.IsPlaceholderModeEnabled())
68+
{
69+
// We're not in standby mode, so flag as specialized
70+
_specialized = 1;
71+
}
72+
});
6373

64-
o.TokenValidationParameters = CreateTokenValidationParameters();
74+
private static string[] GetValidAudiences()
75+
{
76+
if (SystemEnvironment.Instance.IsPlaceholderModeEnabled() &&
77+
SystemEnvironment.Instance.IsLinuxConsumptionOnLegion())
78+
{
79+
return new string[]
80+
{
81+
ScriptSettingsManager.Instance.GetSetting(WebsitePodName)
82+
};
83+
}
6584

66-
// TODO: DI (FACAVAL) Remove this once the work above is completed.
67-
if (!SystemEnvironment.Instance.IsPlaceholderModeEnabled())
68-
{
69-
// We're not in standby mode, so flag as specialized
70-
_specialized = 1;
71-
}
72-
});
85+
return new string[]
86+
{
87+
string.Format(SiteAzureFunctionsUriFormat, ScriptSettingsManager.Instance.GetSetting(AzureWebsiteName)),
88+
string.Format(SiteUriFormat, ScriptSettingsManager.Instance.GetSetting(AzureWebsiteName))
89+
};
90+
}
7391

74-
private static TokenValidationParameters CreateTokenValidationParameters()
92+
public static TokenValidationParameters CreateTokenValidationParameters()
7593
{
7694
var signingKeys = SecretsUtility.GetTokenIssuerSigningKeys();
7795
var result = new TokenValidationParameters();
@@ -80,11 +98,7 @@ private static TokenValidationParameters CreateTokenValidationParameters()
8098
result.IssuerSigningKeys = signingKeys;
8199
result.ValidateAudience = true;
82100
result.ValidateIssuer = true;
83-
result.ValidAudiences = new string[]
84-
{
85-
string.Format(SiteAzureFunctionsUriFormat, ScriptSettingsManager.Instance.GetSetting(AzureWebsiteName)),
86-
string.Format(SiteUriFormat, ScriptSettingsManager.Instance.GetSetting(AzureWebsiteName))
87-
};
101+
result.ValidAudiences = GetValidAudiences();
88102
result.ValidIssuers = new string[]
89103
{
90104
AppServiceCoreUri,

src/WebJobs.Script/Environment/EnvironmentExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,8 @@ public static bool IsLinuxConsumptionOnAtlas(this IEnvironment environment)
312312
public static bool IsLinuxConsumptionOnLegion(this IEnvironment environment)
313313
{
314314
return !environment.IsAppService() &&
315-
!string.IsNullOrEmpty(environment.GetEnvironmentVariable(ContainerName)) &&
315+
(!string.IsNullOrEmpty(environment.GetEnvironmentVariable(ContainerName)) ||
316+
!string.IsNullOrEmpty(environment.GetEnvironmentVariable(WebsitePodName))) &&
316317
!string.IsNullOrEmpty(environment.GetEnvironmentVariable(LegionServiceHost));
317318
}
318319

src/WebJobs.Script/Environment/EnvironmentSettingNames.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public static class EnvironmentSettingNames
3131
public const string FunctionsExtensionVersion = "FUNCTIONS_EXTENSION_VERSION";
3232
public const string FunctionWorkerRuntime = "FUNCTIONS_WORKER_RUNTIME";
3333
public const string ContainerName = "CONTAINER_NAME";
34+
public const string WebsitePodName = "WEBSITE_POD_NAME";
3435
public const string LegionServiceHost = "LEGION_SERVICE_HOST";
3536
public const string WebSiteHomeStampName = "WEBSITE_HOME_STAMPNAME";
3637
public const string WebSiteStampDeploymentId = "WEBSITE_STAMP_DEPLOYMENT_ID";

test/WebJobs.Script.Tests/Extensions/EnvironmentExtensionsTests.cs

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -195,28 +195,37 @@ public void IsFlexConsumptionSku_ReturnsExpectedResult(string sku, bool expected
195195
}
196196

197197
[Theory]
198-
[InlineData(true, false, false, true)]
199-
[InlineData(false, true, false, true)]
200-
[InlineData(true, true, false, true)]
201-
[InlineData(false, false, false, false)]
202-
[InlineData(false, false, true, false)]
203-
public void IsAnyLinuxConsumption_ReturnsExpectedResult(bool isLinuxConsumptionOnAtlas, bool isLinuxConsumptionOnLegion, bool isManagedAppEnvironment, bool expectedValue)
198+
[InlineData(true, false, false, true, false)]
199+
[InlineData(false, true, false, true, false)]
200+
[InlineData(false, true, false, true, true)]
201+
[InlineData(true, true, false, true, false)]
202+
[InlineData(true, true, false, true, true)]
203+
[InlineData(false, false, false, false, false)]
204+
[InlineData(false, false, true, false, false)]
205+
public void IsAnyLinuxConsumption_ReturnsExpectedResult(bool isLinuxConsumptionOnAtlas, bool isLinuxConsumptionOnLegion, bool isManagedAppEnvironment, bool expectedValue, bool setPodName)
204206
{
205207
IEnvironment env = new TestEnvironment();
206208
if (isLinuxConsumptionOnAtlas)
207209
{
208-
env.SetEnvironmentVariable(EnvironmentSettingNames.ContainerName, "RandomContainerName");
210+
env.SetEnvironmentVariable(ContainerName, "RandomContainerName");
209211
}
210212

211213
if (isLinuxConsumptionOnLegion)
212214
{
213-
env.SetEnvironmentVariable(EnvironmentSettingNames.ContainerName, "RandomContainerName");
214-
env.SetEnvironmentVariable(EnvironmentSettingNames.LegionServiceHost, "RandomLegionServiceHostName");
215+
if (setPodName)
216+
{
217+
env.SetEnvironmentVariable(WebsitePodName, "RandomPodName");
218+
}
219+
else
220+
{
221+
env.SetEnvironmentVariable(ContainerName, "RandomContainerName");
222+
}
223+
env.SetEnvironmentVariable(LegionServiceHost, "RandomLegionServiceHostName");
215224
}
216225

217226
if (isManagedAppEnvironment)
218227
{
219-
env.SetEnvironmentVariable(EnvironmentSettingNames.ManagedEnvironment, "true");
228+
env.SetEnvironmentVariable(ManagedEnvironment, "true");
220229
}
221230

222231
Assert.Equal(expectedValue, env.IsAnyLinuxConsumption());
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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 isLinuxConsumptionOnLegion)
22+
{
23+
var podName = "RandomPodName";
24+
var siteName = "RandomSiteName";
25+
ScriptSettingsManager.Instance.SetSetting(AzureWebsiteName, siteName);
26+
ScriptSettingsManager.Instance.SetSetting(WebsitePodName, podName);
27+
28+
var expectedWithSiteName = new string[]
29+
{
30+
string.Format(ScriptConstants.SiteAzureFunctionsUriFormat, ScriptSettingsManager.Instance.GetSetting(AzureWebsiteName)),
31+
string.Format(ScriptConstants.SiteUriFormat, ScriptSettingsManager.Instance.GetSetting(AzureWebsiteName))
32+
};
33+
var expectedWithPodName = new string[]
34+
{
35+
ScriptSettingsManager.Instance.GetSetting(WebsitePodName)
36+
};
37+
38+
var testData = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
39+
40+
if (isPlaceholderModeEnabled)
41+
{
42+
testData[AzureWebsitePlaceholderMode] = "1";
43+
}
44+
45+
if (isLinuxConsumptionOnLegion)
46+
{
47+
testData[AzureWebsiteInstanceId] = string.Empty;
48+
testData[WebsitePodName] = podName;
49+
testData[LegionServiceHost] = "1";
50+
}
51+
52+
testData[ContainerEncryptionKey] = Convert.ToBase64String(TestHelpers.GenerateKeyBytes());
53+
using (new TestScopedEnvironmentVariable(testData))
54+
{
55+
var tokenValidationParameters = ScriptJwtBearerExtensions.CreateTokenValidationParameters();
56+
var audiences = tokenValidationParameters.ValidAudiences.ToList();
57+
58+
if (isPlaceholderModeEnabled &&
59+
isLinuxConsumptionOnLegion)
60+
{
61+
Assert.Equal(audiences.Count, expectedWithPodName.Length);
62+
Assert.Equal(audiences[0], expectedWithPodName[0]);
63+
}
64+
else
65+
{
66+
Assert.Equal(audiences.Count, expectedWithSiteName.Length);
67+
Assert.True(audiences.Contains(expectedWithSiteName[0]));
68+
Assert.True(audiences.Contains(expectedWithSiteName[1]));
69+
}
70+
}
71+
}
72+
}
73+
}

test/WebJobs.Script.Tests/FunctionsSyncServiceTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ public void IsSyncTriggersEnvironment_StandbyMode_ReturnsExpectedResult(bool isA
137137
_mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.LegionServiceHost)).Returns(isConsumptionLinuxOnLegion ? "1" : null);
138138
_mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteContainerReady)).Returns(containerReady ? "1" : null);
139139
_mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.ManagedEnvironment)).Returns(isManagedAppEnvironment ? "1" : null);
140+
_mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.WebsitePodName)).Returns(isConsumptionLinuxOnLegion ? "RandomPodName" : null);
140141

141142
var result = FunctionsSyncManager.IsSyncTriggersEnvironment(_mockWebHostEnvironment.Object, _mockEnvironment.Object);
142143
Assert.Equal(expected, result);

0 commit comments

Comments
 (0)