Skip to content

Commit 48e363b

Browse files
authored
Adding HostingConfig for SWT token handling (#9704)
1 parent fa440fd commit 48e363b

File tree

14 files changed

+348
-113
lines changed

14 files changed

+348
-113
lines changed

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

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@
1313
using System.Threading.Tasks;
1414
using Azure.Storage.Blobs;
1515
using Microsoft.Azure.WebJobs.Host.Executors;
16+
using Microsoft.Azure.WebJobs.Script.Config;
1617
using Microsoft.Azure.WebJobs.Script.Description;
1718
using Microsoft.Azure.WebJobs.Script.Models;
1819
using Microsoft.Azure.WebJobs.Script.WebHost.Extensions;
1920
using Microsoft.Azure.WebJobs.Script.WebHost.Models;
2021
using Microsoft.Azure.WebJobs.Script.WebHost.Security;
21-
using Microsoft.Extensions.Configuration;
2222
using Microsoft.Extensions.Logging;
2323
using Microsoft.Extensions.Options;
2424
using Newtonsoft.Json;
@@ -54,30 +54,30 @@ public class FunctionsSyncManager : IFunctionsSyncManager, IDisposable
5454
private readonly ILogger _logger;
5555
private readonly HttpClient _httpClient;
5656
private readonly ISecretManagerProvider _secretManagerProvider;
57-
private readonly IConfiguration _configuration;
5857
private readonly IHostIdProvider _hostIdProvider;
5958
private readonly IScriptWebHostEnvironment _webHostEnvironment;
6059
private readonly IEnvironment _environment;
6160
private readonly HostNameProvider _hostNameProvider;
6261
private readonly IFunctionMetadataManager _functionMetadataManager;
6362
private readonly SemaphoreSlim _syncSemaphore = new SemaphoreSlim(1, 1);
6463
private readonly IAzureStorageProvider _azureStorageProvider;
64+
private readonly IOptions<FunctionsHostingConfigOptions> _hostingConfigOptions;
6565

6666
private BlobClient _hashBlobClient;
6767

68-
public FunctionsSyncManager(IConfiguration configuration, IHostIdProvider hostIdProvider, IOptionsMonitor<ScriptApplicationHostOptions> applicationHostOptions, ILogger<FunctionsSyncManager> logger, HttpClient httpClient, ISecretManagerProvider secretManagerProvider, IScriptWebHostEnvironment webHostEnvironment, IEnvironment environment, HostNameProvider hostNameProvider, IFunctionMetadataManager functionMetadataManager, IAzureStorageProvider azureStorageProvider)
68+
public FunctionsSyncManager(IHostIdProvider hostIdProvider, IOptionsMonitor<ScriptApplicationHostOptions> applicationHostOptions, ILogger<FunctionsSyncManager> logger, HttpClient httpClient, ISecretManagerProvider secretManagerProvider, IScriptWebHostEnvironment webHostEnvironment, IEnvironment environment, HostNameProvider hostNameProvider, IFunctionMetadataManager functionMetadataManager, IAzureStorageProvider azureStorageProvider, IOptions<FunctionsHostingConfigOptions> functionsHostingConfigOptions)
6969
{
7070
_applicationHostOptions = applicationHostOptions;
7171
_logger = logger;
7272
_httpClient = httpClient;
7373
_secretManagerProvider = secretManagerProvider;
74-
_configuration = configuration;
7574
_hostIdProvider = hostIdProvider;
7675
_webHostEnvironment = webHostEnvironment;
7776
_environment = environment;
7877
_hostNameProvider = hostNameProvider;
7978
_functionMetadataManager = functionMetadataManager;
8079
_azureStorageProvider = azureStorageProvider;
80+
_hostingConfigOptions = functionsHostingConfigOptions;
8181
}
8282

8383
internal bool ArmCacheEnabled
@@ -688,9 +688,6 @@ internal HttpRequestMessage BuildSetTriggersRequest()
688688
// of triggers. It'll verify app ownership using a SWT token valid for 5 minutes. It should be plenty.
689689
private async Task<(bool, string)> SetTriggersAsync(string content)
690690
{
691-
string swtToken = SimpleWebTokenHelper.CreateToken(DateTime.UtcNow.AddMinutes(5));
692-
string jwtToken = JwtTokenHelper.CreateToken(DateTime.UtcNow.AddMinutes(5));
693-
694691
string sanitizedContentString = content;
695692
if (ArmCacheEnabled)
696693
{
@@ -708,10 +705,17 @@ internal HttpRequestMessage BuildSetTriggersRequest()
708705
var requestId = Guid.NewGuid().ToString();
709706
request.Headers.Add(ScriptConstants.AntaresLogIdHeaderName, requestId);
710707
request.Headers.Add("User-Agent", ScriptConstants.FunctionsUserAgent);
711-
request.Headers.Add(ScriptConstants.SiteRestrictedTokenHeaderName, swtToken);
712-
request.Headers.Add(ScriptConstants.SiteTokenHeaderName, jwtToken);
713708
request.Content = new StringContent(content, Encoding.UTF8, "application/json");
714709

710+
if (_hostingConfigOptions.Value.SwtIssuerEnabled)
711+
{
712+
string swtToken = SimpleWebTokenHelper.CreateToken(DateTime.UtcNow.AddMinutes(5));
713+
request.Headers.Add(ScriptConstants.SiteRestrictedTokenHeaderName, swtToken);
714+
}
715+
716+
string jwtToken = JwtTokenHelper.CreateToken(DateTime.UtcNow.AddMinutes(5));
717+
request.Headers.Add(ScriptConstants.SiteTokenHeaderName, jwtToken);
718+
715719
if (_environment.IsKubernetesManagedHosting())
716720
{
717721
request.Headers.Add(ScriptConstants.KubernetesManagedAppName, _environment.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteName));

src/WebJobs.Script.WebHost/Metrics/LinuxContainerMetricsPublisher.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System.Security.Cryptography.X509Certificates;
1212
using System.Threading;
1313
using System.Threading.Tasks;
14+
using Microsoft.Azure.WebJobs.Script.Config;
1415
using Microsoft.Azure.WebJobs.Script.WebHost.Models;
1516
using Microsoft.Azure.WebJobs.Script.WebHost.Security;
1617
using Microsoft.Extensions.Logging;
@@ -38,6 +39,7 @@ public class LinuxContainerMetricsPublisher : IMetricsPublisher
3839
private readonly IDisposable _standbyOptionsOnChangeSubscription;
3940
private readonly string _requestUri;
4041
private readonly IEnvironment _environment;
42+
private readonly IOptions<FunctionsHostingConfigOptions> _hostingConfigOptions;
4143

4244
// Buffer for all memory activities for this container.
4345
private BlockingCollection<MemoryActivity> _memoryActivities;
@@ -63,14 +65,15 @@ public class LinuxContainerMetricsPublisher : IMetricsPublisher
6365
private string _stampName;
6466
private bool _initialized = false;
6567

66-
public LinuxContainerMetricsPublisher(IEnvironment environment, IOptionsMonitor<StandbyOptions> standbyOptions, ILogger<LinuxContainerMetricsPublisher> logger, HostNameProvider hostNameProvider, HttpClient httpClient = null)
68+
public LinuxContainerMetricsPublisher(IEnvironment environment, IOptionsMonitor<StandbyOptions> standbyOptions, ILogger<LinuxContainerMetricsPublisher> logger, HostNameProvider hostNameProvider, IOptions<FunctionsHostingConfigOptions> functionsHostingConfigOptions, HttpClient httpClient = null)
6769
{
6870
_standbyOptions = standbyOptions ?? throw new ArgumentNullException(nameof(standbyOptions));
6971
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
7072
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
7173
_hostNameProvider = hostNameProvider ?? throw new ArgumentNullException(nameof(hostNameProvider));
7274
_memoryActivities = new BlockingCollection<MemoryActivity>(new ConcurrentQueue<MemoryActivity>(), boundedCapacity: _maxBufferSize);
7375
_functionActivities = new BlockingCollection<FunctionActivity>(new ConcurrentQueue<FunctionActivity>(), boundedCapacity: _maxBufferSize);
76+
_hostingConfigOptions = functionsHostingConfigOptions;
7477

7578
_currentMemoryActivities = new ConcurrentQueue<MemoryActivity>();
7679
_currentFunctionActivities = new ConcurrentQueue<FunctionActivity>();
@@ -284,16 +287,20 @@ private HttpRequestMessage BuildRequest<TContent>(HttpMethod method, string path
284287
Content = new ObjectContent<TContent>(content, new JsonMediaTypeFormatter())
285288
};
286289

287-
string swtToken = SimpleWebTokenHelper.CreateToken(DateTime.UtcNow.AddMinutes(5));
288-
string jwtToken = JwtTokenHelper.CreateToken(DateTime.UtcNow.AddMinutes(5));
289-
290290
// add the required authentication headers
291291
request.Headers.Add(ContainerNameHeader, _containerName);
292292
request.Headers.Add(HostNameHeader, _hostNameProvider.Value);
293-
request.Headers.Add(ScriptConstants.SiteRestrictedTokenHeaderName, swtToken);
294-
request.Headers.Add(ScriptConstants.SiteTokenHeaderName, jwtToken);
295293
request.Headers.Add(StampNameHeader, _stampName);
296294

295+
if (_hostingConfigOptions.Value.SwtIssuerEnabled)
296+
{
297+
string swtToken = SimpleWebTokenHelper.CreateToken(DateTime.UtcNow.AddMinutes(5));
298+
request.Headers.Add(ScriptConstants.SiteRestrictedTokenHeaderName, swtToken);
299+
}
300+
301+
string jwtToken = JwtTokenHelper.CreateToken(DateTime.UtcNow.AddMinutes(5));
302+
request.Headers.Add(ScriptConstants.SiteTokenHeaderName, jwtToken);
303+
297304
return request;
298305
}
299306
}

src/WebJobs.Script.WebHost/Security/Authentication/Arm/ArmAuthenticationHandler.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Threading.Tasks;
1010
using Microsoft.AspNetCore.Authentication;
1111
using Microsoft.Azure.WebJobs.Extensions.Http;
12+
using Microsoft.Azure.WebJobs.Script.Config;
1213
using Microsoft.Extensions.Logging;
1314
using Microsoft.Extensions.Options;
1415
using Microsoft.Extensions.Primitives;
@@ -18,11 +19,13 @@ namespace Microsoft.Azure.WebJobs.Script.WebHost.Security.Authentication
1819
public class ArmAuthenticationHandler : AuthenticationHandler<ArmAuthenticationOptions>
1920
{
2021
private readonly ILogger _logger;
22+
private readonly IOptions<FunctionsHostingConfigOptions> _hostingConfigOptions;
2123

22-
public ArmAuthenticationHandler(IOptionsMonitor<ArmAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
24+
public ArmAuthenticationHandler(IOptionsMonitor<ArmAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IOptions<FunctionsHostingConfigOptions> functionsHostingConfigOptions)
2325
: base(options, logger, encoder, clock)
2426
{
2527
_logger = logger.CreateLogger<ArmAuthenticationHandler>();
28+
_hostingConfigOptions = functionsHostingConfigOptions;
2629
}
2730

2831
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
@@ -35,7 +38,7 @@ protected override Task<AuthenticateResult> HandleAuthenticateAsync()
3538
private AuthenticateResult HandleAuthenticate()
3639
{
3740
string token = null;
38-
if (!Context.Request.Headers.TryGetValue(ScriptConstants.SiteRestrictedTokenHeaderName, out StringValues values))
41+
if (!_hostingConfigOptions.Value.SwtAuthenticationEnabled || !Context.Request.Headers.TryGetValue(ScriptConstants.SiteRestrictedTokenHeaderName, out StringValues values))
3942
{
4043
return AuthenticateResult.NoResult();
4144
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.Azure.WebJobs.Extensions.Http;
1313
using Microsoft.Azure.WebJobs.Script;
1414
using Microsoft.Azure.WebJobs.Script.Config;
15+
using Microsoft.Azure.WebJobs.Script.Extensions;
1516
using Microsoft.Azure.WebJobs.Script.WebHost;
1617
using Microsoft.Azure.WebJobs.Script.WebHost.Security.Authentication;
1718
using Microsoft.Extensions.Logging;
@@ -132,6 +133,11 @@ private static bool AudienceValidator(IEnumerable<string> audiences, SecurityTok
132133

133134
private static void LogAuthenticationFailure(AuthenticationFailedContext context)
134135
{
136+
if (!context.Request.IsAdminRequest())
137+
{
138+
return;
139+
}
140+
135141
var loggerFactory = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>();
136142
var logger = loggerFactory.CreateLogger(ScriptConstants.LogCategoryHostAuthentication);
137143

@@ -149,7 +155,7 @@ private static void LogAuthenticationFailure(AuthenticationFailedContext context
149155
break;
150156
}
151157

152-
logger.LogError(context.Exception, message);
158+
logger.LogDebug(context.Exception, message);
153159
}
154160
}
155161
}

src/WebJobs.Script.WebHost/WebHostServiceCollectionExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,8 @@ private static void AddLinuxContainerServices(this IServiceCollection services)
243243
var logger = s.GetService<ILogger<LinuxContainerMetricsPublisher>>();
244244
var standbyOptions = s.GetService<IOptionsMonitor<StandbyOptions>>();
245245
var hostNameProvider = s.GetService<HostNameProvider>();
246-
return new LinuxContainerMetricsPublisher(environment, standbyOptions, logger, hostNameProvider);
246+
var hostingConfigOptions = s.GetService<IOptions<FunctionsHostingConfigOptions>>();
247+
return new LinuxContainerMetricsPublisher(environment, standbyOptions, logger, hostNameProvider, hostingConfigOptions);
247248
}
248249

249250
return NullMetricsPublisher.Instance;

src/WebJobs.Script/Config/FunctionsHostingConfigOptions.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,38 @@ public string MaximumSupportedBundleV4Version
5353
}
5454
}
5555

56+
/// <summary>
57+
/// Gets or sets a value indicating whether SWT tokens should be accepted.
58+
/// </summary>
59+
public bool SwtAuthenticationEnabled
60+
{
61+
get
62+
{
63+
return GetFeatureOrDefault(ScriptConstants.HostingConfigSwtAuthenticationEnabled, "1") == "1";
64+
}
65+
66+
set
67+
{
68+
_features[ScriptConstants.HostingConfigSwtAuthenticationEnabled] = value ? "1" : "0";
69+
}
70+
}
71+
72+
/// <summary>
73+
/// Gets or sets a value indicating whether SWT tokens should be sent on outgoing requests.
74+
/// </summary>
75+
public bool SwtIssuerEnabled
76+
{
77+
get
78+
{
79+
return GetFeatureOrDefault(ScriptConstants.HostingConfigSwtIssuerEnabled, "1") == "1";
80+
}
81+
82+
set
83+
{
84+
_features[ScriptConstants.HostingConfigSwtIssuerEnabled] = value ? "1" : "0";
85+
}
86+
}
87+
5688
/// <summary>
5789
/// Gets feature by name.
5890
/// </summary>
@@ -66,5 +98,16 @@ public string GetFeature(string name)
6698
}
6799
return null;
68100
}
101+
102+
/// <summary>
103+
/// Gets a feature by name, returning the specified default value if not found.
104+
/// </summary>
105+
/// <param name="name">Feature name.</param>
106+
/// <param name="defaultValue">The default value to use.</param>
107+
/// <returns>String value from hostig configuration.</returns>
108+
public string GetFeatureOrDefault(string name, string defaultValue)
109+
{
110+
return GetFeature(name) ?? defaultValue;
111+
}
69112
}
70113
}

src/WebJobs.Script/ScriptConstants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ public static class ScriptConstants
124124
public const string FeatureFlagDisableWorkerIndexing = "DisableWorkerIndexing";
125125
public const string FeatureFlagEnableMultiLanguageWorker = "EnableMultiLanguageWorker";
126126
public const string FeatureFlagEnableLinuxEPExecutionCount = "EnableLinuxFEC";
127+
public const string HostingConfigSwtAuthenticationEnabled = "SwtAuthenticationEnabled";
128+
public const string HostingConfigSwtIssuerEnabled = "SwtIssuerEnabled";
127129

128130
public const string SiteAzureFunctionsUriFormat = "https://{0}.azurewebsites.net/azurefunctions";
129131
public const string ScmSiteUriFormat = "https://{0}.scm.azurewebsites.net";

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

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public class FunctionsSyncManagerTests : IDisposable
4747
private readonly HostNameProvider _hostNameProvider;
4848
private readonly Mock<ISecretManagerProvider> _secretManagerProviderMock;
4949
private readonly Mock<ISecretManager> _secretManagerMock;
50+
private readonly FunctionsHostingConfigOptions _hostingConfigOptions;
5051
private string _function1;
5152
private bool _emptyContent;
5253
private bool _secretsEnabled;
@@ -141,7 +142,10 @@ public FunctionsSyncManagerTests()
141142
var functionMetadataManager = TestFunctionMetadataManager.GetFunctionMetadataManager(new OptionsWrapper<ScriptJobHostOptions>(jobHostOptions), functionMetadataProvider, null, new OptionsWrapper<HttpWorkerOptions>(new HttpWorkerOptions()), loggerFactory, new OptionsWrapper<LanguageWorkerOptions>(CreateLanguageWorkerConfigSettings()));
142143
var azureStorageProvider = TestHelpers.GetAzureStorageProvider(configuration);
143144

144-
_functionsSyncManager = new FunctionsSyncManager(configuration, hostIdProviderMock.Object, optionsMonitor, loggerFactory.CreateLogger<FunctionsSyncManager>(), httpClient, _secretManagerProviderMock.Object, _mockWebHostEnvironment.Object, _mockEnvironment.Object, _hostNameProvider, functionMetadataManager, azureStorageProvider);
145+
_hostingConfigOptions = new FunctionsHostingConfigOptions();
146+
var hostingConfigOptionsWrapper = new OptionsWrapper<FunctionsHostingConfigOptions>(_hostingConfigOptions);
147+
148+
_functionsSyncManager = new FunctionsSyncManager(hostIdProviderMock.Object, optionsMonitor, loggerFactory.CreateLogger<FunctionsSyncManager>(), httpClient, _secretManagerProviderMock.Object, _mockWebHostEnvironment.Object, _mockEnvironment.Object, _hostNameProvider, functionMetadataManager, azureStorageProvider, hostingConfigOptionsWrapper);
145149
}
146150

147151
private string GetExpectedSyncTriggersPayload(string postedConnection = DefaultTestConnection, string postedTaskHub = DefaultTestTaskHub, string durableVersion = "V2")
@@ -214,11 +218,13 @@ public void ArmCacheEnabled_VerifyDefault()
214218
}
215219

216220
[Theory]
217-
[InlineData(true)]
218-
[InlineData(false)]
219-
public async Task TrySyncTriggers_PostsExpectedContent(bool cacheEnabled)
221+
[InlineData(true, true)]
222+
[InlineData(false, true)]
223+
[InlineData(true, false)]
224+
public async Task TrySyncTriggers_PostsExpectedContent(bool cacheEnabled, bool swtIssuerEnabled)
220225
{
221226
_mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteArmCacheEnabled)).Returns(cacheEnabled ? "1" : "0");
227+
_hostingConfigOptions.SwtIssuerEnabled = swtIssuerEnabled;
222228

223229
using (var env = new TestScopedEnvironmentVariable(_vars))
224230
{
@@ -234,7 +240,16 @@ public async Task TrySyncTriggers_PostsExpectedContent(bool cacheEnabled)
234240
// verify expected headers
235241
Assert.Equal(ScriptConstants.FunctionsUserAgent, _mockHttpHandler.LastRequest.Headers.UserAgent.ToString());
236242
Assert.True(_mockHttpHandler.LastRequest.Headers.Contains(ScriptConstants.AntaresLogIdHeaderName));
237-
Assert.NotEmpty(_mockHttpHandler.LastRequest.Headers.GetValues(ScriptConstants.SiteRestrictedTokenHeaderName));
243+
244+
if (swtIssuerEnabled)
245+
{
246+
Assert.NotEmpty(_mockHttpHandler.LastRequest.Headers.GetValues(ScriptConstants.SiteRestrictedTokenHeaderName));
247+
}
248+
else
249+
{
250+
Assert.False(_mockHttpHandler.LastRequest.Headers.Contains(ScriptConstants.SiteRestrictedTokenHeaderName));
251+
}
252+
238253
Assert.NotEmpty(_mockHttpHandler.LastRequest.Headers.GetValues(ScriptConstants.SiteTokenHeaderName));
239254

240255
if (cacheEnabled)

0 commit comments

Comments
 (0)