Skip to content

Commit e104f5f

Browse files
authored
Including admin JWT on outbound platform calls (#9199)
1 parent e458c41 commit e104f5f

File tree

10 files changed

+58
-23
lines changed

10 files changed

+58
-23
lines changed

src/WebJobs.Script.WebHost/Filters/AuthorizationLevelAttribute.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ namespace Microsoft.Azure.WebJobs.Script.WebHost.Filters
2020
public class AuthorizationLevelAttribute : AuthorizationFilterAttribute
2121
{
2222
public const string FunctionsKeyHeaderName = "x-functions-key";
23-
public const string ArmTokenHeaderName = "x-ms-site-restricted-token";
2423

2524
public AuthorizationLevelAttribute(AuthorizationLevel level)
2625
{
@@ -94,7 +93,7 @@ public async override Task OnAuthorizationAsync(HttpActionContext actionContext,
9493

9594
internal static string GetArmTokenHeader(HttpActionContext actionContext)
9695
{
97-
if (actionContext.Request.Headers.TryGetValues(ArmTokenHeaderName, out IEnumerable<string> values))
96+
if (actionContext.Request.Headers.TryGetValues(ScriptConstants.SiteRestrictedTokenHeaderName, out IEnumerable<string> values))
9897
{
9998
return values.First();
10099
}

src/WebJobs.Script.WebHost/Filters/JwtAuthenticationAttribute.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,14 @@ public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationTok
5757
ValidateIssuer = true,
5858
ValidAudiences = new string[]
5959
{
60-
string.Format(AdminJwtSiteFunctionsValidAudienceFormat, ScriptSettingsManager.Instance.GetSetting(AzureWebsiteName)),
61-
string.Format(AdminJwtSiteValidAudienceFormat, ScriptSettingsManager.Instance.GetSetting(AzureWebsiteName))
60+
string.Format(SiteAzureFunctionsUriFormat, ScriptSettingsManager.Instance.GetSetting(AzureWebsiteName)),
61+
string.Format(SiteUriFormat, ScriptSettingsManager.Instance.GetSetting(AzureWebsiteName))
6262
},
6363
ValidIssuers = new string[]
6464
{
65-
AdminJwtAppServiceIssuer,
66-
string.Format(AdminJwtScmValidIssuerFormat, ScriptSettingsManager.Instance.GetSetting(AzureWebsiteName)),
67-
string.Format(AdminJwtSiteValidIssuerFormat, ScriptSettingsManager.Instance.GetSetting(AzureWebsiteName))
65+
AppServiceCoreUri,
66+
string.Format(ScmSiteUriFormat, ScriptSettingsManager.Instance.GetSetting(AzureWebsiteName)),
67+
string.Format(SiteUriFormat, ScriptSettingsManager.Instance.GetSetting(AzureWebsiteName))
6868
}
6969
};
7070

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,8 @@ internal static HttpRequestMessage BuildSetTriggersRequest()
420420
// of triggers. It'll verify app ownership using a SWT token valid for 5 minutes. It should be plenty.
421421
private async Task<(bool, string)> SetTriggersAsync(string content)
422422
{
423-
var token = SimpleWebTokenHelper.CreateToken(DateTime.UtcNow.AddMinutes(5));
423+
string swtToken = SimpleWebTokenHelper.CreateToken(DateTime.UtcNow.AddMinutes(5));
424+
string jwtToken = JwtTokenHelper.CreateToken(DateTime.UtcNow.AddMinutes(5));
424425

425426
string sanitizedContentString = content;
426427
if (ArmCacheEnabled)
@@ -439,7 +440,8 @@ internal static HttpRequestMessage BuildSetTriggersRequest()
439440
var requestId = Guid.NewGuid().ToString();
440441
request.Headers.Add(ScriptConstants.AntaresLogIdHeaderName, requestId);
441442
request.Headers.Add("User-Agent", ScriptConstants.FunctionsUserAgent);
442-
request.Headers.Add("x-ms-site-restricted-token", token);
443+
request.Headers.Add(ScriptConstants.SiteRestrictedTokenHeaderName, swtToken);
444+
request.Headers.Add(ScriptConstants.SiteTokenHeaderName, jwtToken);
443445
request.Content = new StringContent(content, Encoding.UTF8, "application/json");
444446

445447
string message = $"Making SyncTriggers request (RequestId={requestId}, Uri={request.RequestUri.ToString()}, Content={sanitizedContentString}).";
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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.IdentityModel.Tokens.Jwt;
6+
using Microsoft.Azure.WebJobs.Script.Config;
7+
using Microsoft.IdentityModel.Tokens;
8+
9+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Security
10+
{
11+
internal static class JwtTokenHelper
12+
{
13+
public static string CreateToken(DateTime validUntil, string audience = null, string issuer = null, byte[] key = null)
14+
{
15+
var tokenHandler = new JwtSecurityTokenHandler();
16+
key = key ?? SecretsUtility.GetEncryptionKey();
17+
var tokenDescriptor = new SecurityTokenDescriptor
18+
{
19+
Audience = audience ?? ScriptConstants.AppServiceCoreUri,
20+
Issuer = issuer ?? string.Format(ScriptConstants.SiteUriFormat, ScriptSettingsManager.Instance.GetSetting(EnvironmentSettingNames.AzureWebsiteName)),
21+
Expires = validUntil,
22+
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
23+
};
24+
var token = tokenHandler.CreateToken(tokenDescriptor);
25+
string tokenValue = tokenHandler.WriteToken(token);
26+
27+
return tokenValue;
28+
}
29+
}
30+
}

src/WebJobs.Script.WebHost/WebJobs.Script.WebHost.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,7 @@
519519
<Compile Include="Management\WebFunctionsManager.cs" />
520520
<Compile Include="Models\FunctionMetadataResponse.cs" />
521521
<Compile Include="ResourceContainsSecretsAttribute.cs" />
522+
<Compile Include="Security\JwtTokenHelper.cs" />
522523
<Compile Include="Security\SecretsUtility.cs" />
523524
<Compile Include="Security\SimpleWebTokenHelper.cs" />
524525
<Compile Include="StandbyManager.cs" />

src/WebJobs.Script/GlobalSuppressions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,4 @@
250250
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ARM", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.ScriptConstants.#AntaresARMRequestTrackingIdHeader")]
251251
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ARM", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.ScriptConstants.#AntaresARMExtensionsRouteHeader")]
252252
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Scm", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.ScriptConstants.#AdminJwtScmValidIssuerFormat")]
253+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Scm", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.ScriptConstants.#ScmSiteUriFormat")]

src/WebJobs.Script/ScriptConstants.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public static class ScriptConstants
6565
public const string ColdStartEventName = "ColdStart";
6666
public const string ShutdownRecoveryEventName = "ShutdownRecovery";
6767

68+
public const string SiteRestrictedTokenHeaderName = "x-ms-site-restricted-token";
6869
public const string SiteTokenHeaderName = "x-ms-site-token";
6970
public const string AntaresARMRequestTrackingIdHeader = "x-ms-arm-request-tracking-id";
7071
public const string AntaresARMExtensionsRouteHeader = "X-MS-VIA-EXTENSIONS-ROUTE";
@@ -83,11 +84,10 @@ public static class ScriptConstants
8384
public const string FeatureFlagDisableShadowCopy = "DisableShadowCopy";
8485
public const string FeatureFlagsEnableDynamicExtensionLoading = "EnableDynamicExtensionLoading";
8586

86-
public const string AdminJwtSiteFunctionsValidAudienceFormat = "https://{0}.azurewebsites.net/azurefunctions";
87-
public const string AdminJwtSiteValidAudienceFormat = "https://{0}.azurewebsites.net";
88-
public const string AdminJwtScmValidIssuerFormat = "https://{0}.scm.azurewebsites.net";
89-
public const string AdminJwtSiteValidIssuerFormat = "https://{0}.azurewebsites.net";
90-
public const string AdminJwtAppServiceIssuer = "https://appservice.core.azurewebsites.net";
87+
public const string SiteAzureFunctionsUriFormat = "https://{0}.azurewebsites.net/azurefunctions";
88+
public const string ScmSiteUriFormat = "https://{0}.scm.azurewebsites.net";
89+
public const string SiteUriFormat = "https://{0}.azurewebsites.net";
90+
public const string AppServiceCoreUri = "https://appservice.core.azurewebsites.net";
9191

9292
public const string SwaggerFileName = "swagger.json";
9393
public const string AzureFunctionsSystemDirectoryName = ".azurefunctions";

test/WebJobs.Script.Tests/Filters/AuthorizationLevelAttributeTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ public async Task OnAuthorization_Arm_Success_SetsAdminAuthLevel()
406406
var attribute = new AuthorizationLevelAttribute(AuthorizationLevel.Admin);
407407

408408
HttpRequestMessage request = new HttpRequestMessage();
409-
request.Headers.Add(AuthorizationLevelAttribute.ArmTokenHeaderName, token);
409+
request.Headers.Add(ScriptConstants.SiteRestrictedTokenHeaderName, token);
410410
var actionContext = CreateActionContext(typeof(TestController).GetMethod(nameof(TestController.Get)), HttpConfig);
411411
actionContext.ControllerContext.Request = request;
412412

@@ -429,7 +429,7 @@ public async Task OnAuthorization_Arm_Expired_ReturnsUnauthorized()
429429
var attribute = new AuthorizationLevelAttribute(AuthorizationLevel.Admin);
430430

431431
HttpRequestMessage request = new HttpRequestMessage();
432-
request.Headers.Add(AuthorizationLevelAttribute.ArmTokenHeaderName, token);
432+
request.Headers.Add(ScriptConstants.SiteRestrictedTokenHeaderName, token);
433433
var actionContext = CreateActionContext(typeof(TestController).GetMethod(nameof(TestController.Get)), HttpConfig);
434434
actionContext.ControllerContext.Request = request;
435435

@@ -453,7 +453,7 @@ public async Task OnAuthorization_Arm_Invalid_ReturnsUnauthorized()
453453
var attribute = new AuthorizationLevelAttribute(AuthorizationLevel.Admin);
454454

455455
HttpRequestMessage request = new HttpRequestMessage();
456-
request.Headers.Add(AuthorizationLevelAttribute.ArmTokenHeaderName, token);
456+
request.Headers.Add(ScriptConstants.SiteRestrictedTokenHeaderName, token);
457457
var actionContext = CreateActionContext(typeof(TestController).GetMethod(nameof(TestController.Get)), HttpConfig);
458458
actionContext.ControllerContext.Request = request;
459459

test/WebJobs.Script.Tests/Filters/JwtAuthenticationAttributeTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ public JwtAuthenticationAttributeTests()
5454
[InlineData(ScriptConstants.SiteTokenHeaderName, "https://testsite.azurewebsites.net", "https://testsite.azurewebsites.net")]
5555
public async Task AuthenticateAsync_WithValidToken_SetsAdminAuthorizationLevel(string headerName, string issuer = null, string audience = null)
5656
{
57-
issuer = issuer ?? string.Format(AdminJwtScmValidIssuerFormat, Instance.GetSetting(AzureWebsiteName));
58-
audience = audience ?? string.Format(AdminJwtSiteFunctionsValidAudienceFormat, Instance.GetSetting(AzureWebsiteName));
57+
issuer = issuer ?? string.Format(ScmSiteUriFormat, Instance.GetSetting(AzureWebsiteName));
58+
audience = audience ?? string.Format(SiteAzureFunctionsUriFormat, Instance.GetSetting(AzureWebsiteName));
5959

6060
string token = JwtGenerator.GenerateToken(issuer, audience, expires: DateTime.UtcNow.AddMinutes(10));
6161

@@ -68,8 +68,8 @@ public async Task AuthenticateAsync_WithValidToken_SetsAdminAuthorizationLevel(s
6868
[InlineData(10, null, "invalid")]
6969
public async Task AuthenticateAsync_InvalidToken_DoesNotSetAuthorizationLevel(int expiry, string issuer = null, string audience = null)
7070
{
71-
issuer = issuer ?? string.Format(AdminJwtScmValidIssuerFormat, Instance.GetSetting(AzureWebsiteName));
72-
audience = audience ?? string.Format(AdminJwtSiteFunctionsValidAudienceFormat, Instance.GetSetting(AzureWebsiteName));
71+
issuer = issuer ?? string.Format(ScmSiteUriFormat, Instance.GetSetting(AzureWebsiteName));
72+
audience = audience ?? string.Format(SiteAzureFunctionsUriFormat, Instance.GetSetting(AzureWebsiteName));
7373

7474
string token = JwtGenerator.GenerateToken(issuer, audience, expires: DateTime.UtcNow.AddMinutes(expiry), notBefore: DateTime.UtcNow.AddHours(-1));
7575

@@ -79,8 +79,8 @@ public async Task AuthenticateAsync_InvalidToken_DoesNotSetAuthorizationLevel(in
7979
[Fact]
8080
public async Task AuthenticateAsync_WithValidToken_Base64Encoding_SetsAdminAuthorizationLevel()
8181
{
82-
string issuer = string.Format(AdminJwtScmValidIssuerFormat, Instance.GetSetting(AzureWebsiteName));
83-
string audience = string.Format(AdminJwtSiteFunctionsValidAudienceFormat, Instance.GetSetting(AzureWebsiteName));
82+
string issuer = string.Format(ScmSiteUriFormat, Instance.GetSetting(AzureWebsiteName));
83+
string audience = string.Format(SiteUriFormat, Instance.GetSetting(AzureWebsiteName));
8484

8585
string key = SecretsUtility.GetEncryptionKeyValue();
8686
var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(key.ToKeyBytes()), "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256");

test/WebJobs.Script.Tests/Management/FunctionsSyncManagerTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@ public async Task TrySyncTriggers_PostsExpectedContent(bool cacheEnabled)
238238
// verify expected headers
239239
Assert.Equal(ScriptConstants.FunctionsUserAgent, _mockHttpHandler.LastRequest.Headers.UserAgent.ToString());
240240
Assert.True(_mockHttpHandler.LastRequest.Headers.Contains(ScriptConstants.AntaresLogIdHeaderName));
241+
Assert.NotEmpty(_mockHttpHandler.LastRequest.Headers.GetValues(ScriptConstants.SiteRestrictedTokenHeaderName));
242+
Assert.NotEmpty(_mockHttpHandler.LastRequest.Headers.GetValues(ScriptConstants.SiteTokenHeaderName));
241243

242244
if (cacheEnabled)
243245
{

0 commit comments

Comments
 (0)