Skip to content

Commit 7ef0f51

Browse files
authored
Adding HostingConfig for SWT token handling (#9700)
1 parent 0504a35 commit 7ef0f51

File tree

13 files changed

+205
-32
lines changed

13 files changed

+205
-32
lines changed

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

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,11 @@
1515
using Microsoft.Azure.WebJobs.Host.Executors;
1616
using Microsoft.Azure.WebJobs.Host.Storage;
1717
using Microsoft.Azure.WebJobs.Script.Config;
18-
using Microsoft.Azure.WebJobs.Script.DependencyInjection;
1918
using Microsoft.Azure.WebJobs.Script.Description;
2019
using Microsoft.Azure.WebJobs.Script.Models;
2120
using Microsoft.Azure.WebJobs.Script.WebHost.Extensions;
2221
using Microsoft.Azure.WebJobs.Script.WebHost.Models;
2322
using Microsoft.Azure.WebJobs.Script.WebHost.Security;
24-
using Microsoft.Extensions.Configuration;
25-
using Microsoft.Extensions.Hosting;
2623
using Microsoft.Extensions.Logging;
2724
using Microsoft.Extensions.Options;
2825
using Newtonsoft.Json;
@@ -58,30 +55,30 @@ public class FunctionsSyncManager : IFunctionsSyncManager, IDisposable
5855
private readonly ILogger _logger;
5956
private readonly HttpClient _httpClient;
6057
private readonly ISecretManagerProvider _secretManagerProvider;
61-
private readonly IConfiguration _configuration;
6258
private readonly IHostIdProvider _hostIdProvider;
6359
private readonly IScriptWebHostEnvironment _webHostEnvironment;
6460
private readonly IEnvironment _environment;
6561
private readonly HostNameProvider _hostNameProvider;
6662
private readonly IFunctionMetadataManager _functionMetadataManager;
6763
private readonly SemaphoreSlim _syncSemaphore = new SemaphoreSlim(1, 1);
6864
private readonly IAzureBlobStorageProvider _azureBlobStorageProvider;
65+
private readonly IOptions<FunctionsHostingConfigOptions> _hostingConfigOptions;
6966

7067
private BlobClient _hashBlobClient;
7168

72-
public FunctionsSyncManager(IConfiguration configuration, IHostIdProvider hostIdProvider, IOptionsMonitor<ScriptApplicationHostOptions> applicationHostOptions, ILogger<FunctionsSyncManager> logger, IHttpClientFactory httpClientFactory, ISecretManagerProvider secretManagerProvider, IScriptWebHostEnvironment webHostEnvironment, IEnvironment environment, HostNameProvider hostNameProvider, IFunctionMetadataManager functionMetadataManager, IAzureBlobStorageProvider azureBlobStorageProvider)
69+
public FunctionsSyncManager(IHostIdProvider hostIdProvider, IOptionsMonitor<ScriptApplicationHostOptions> applicationHostOptions, ILogger<FunctionsSyncManager> logger, IHttpClientFactory httpClientFactory, ISecretManagerProvider secretManagerProvider, IScriptWebHostEnvironment webHostEnvironment, IEnvironment environment, HostNameProvider hostNameProvider, IFunctionMetadataManager functionMetadataManager, IAzureBlobStorageProvider azureBlobStorageProvider, IOptions<FunctionsHostingConfigOptions> functionsHostingConfigOptions)
7370
{
7471
_applicationHostOptions = applicationHostOptions;
7572
_logger = logger;
7673
_httpClient = httpClientFactory.CreateClient();
7774
_secretManagerProvider = secretManagerProvider;
78-
_configuration = configuration;
7975
_hostIdProvider = hostIdProvider;
8076
_webHostEnvironment = webHostEnvironment;
8177
_environment = environment;
8278
_hostNameProvider = hostNameProvider;
8379
_functionMetadataManager = functionMetadataManager;
8480
_azureBlobStorageProvider = azureBlobStorageProvider;
81+
_hostingConfigOptions = functionsHostingConfigOptions;
8582
}
8683

8784
internal bool ArmCacheEnabled
@@ -726,9 +723,6 @@ internal HttpRequestMessage BuildSetTriggersRequest()
726723
// of triggers. It'll verify app ownership using a SWT token valid for 5 minutes. It should be plenty.
727724
private async Task<(bool Success, string ErrorMessage)> SetTriggersAsync(string content)
728725
{
729-
string swtToken = SimpleWebTokenHelper.CreateToken(DateTime.UtcNow.AddMinutes(5));
730-
string jwtToken = JwtTokenHelper.CreateToken(DateTime.UtcNow.AddMinutes(5));
731-
732726
string sanitizedContentString = content;
733727
if (ArmCacheEnabled)
734728
{
@@ -746,10 +740,17 @@ internal HttpRequestMessage BuildSetTriggersRequest()
746740
var requestId = Guid.NewGuid().ToString();
747741
request.Headers.Add(ScriptConstants.AntaresLogIdHeaderName, requestId);
748742
request.Headers.Add("User-Agent", ScriptConstants.FunctionsUserAgent);
749-
request.Headers.Add(ScriptConstants.SiteRestrictedTokenHeaderName, swtToken);
750-
request.Headers.Add(ScriptConstants.SiteTokenHeaderName, jwtToken);
751743
request.Content = new StringContent(content, Encoding.UTF8, "application/json");
752744

745+
if (_hostingConfigOptions.Value.SwtIssuerEnabled)
746+
{
747+
string swtToken = SimpleWebTokenHelper.CreateToken(DateTime.UtcNow.AddMinutes(5));
748+
request.Headers.Add(ScriptConstants.SiteRestrictedTokenHeaderName, swtToken);
749+
}
750+
751+
string jwtToken = JwtTokenHelper.CreateToken(DateTime.UtcNow.AddMinutes(5));
752+
request.Headers.Add(ScriptConstants.SiteTokenHeaderName, jwtToken);
753+
753754
if (_environment.IsManagedAppEnvironment())
754755
{
755756
request.Headers.Add("K8SE-APP-NAME", _environment.GetEnvironmentVariable("CONTAINER_APP_NAME"));

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/WebHostServiceCollectionExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,8 @@ private static void AddLinuxContainerServices(this IServiceCollection services)
277277
var logger = s.GetService<ILogger<LinuxContainerMetricsPublisher>>();
278278
var standbyOptions = s.GetService<IOptionsMonitor<StandbyOptions>>();
279279
var hostNameProvider = s.GetService<HostNameProvider>();
280-
return new LinuxContainerMetricsPublisher(environment, standbyOptions, logger, hostNameProvider);
280+
var hostingConfigOptions = s.GetService<IOptions<FunctionsHostingConfigOptions>>();
281+
return new LinuxContainerMetricsPublisher(environment, standbyOptions, logger, hostNameProvider, hostingConfigOptions);
281282
}
282283

283284
return NullMetricsPublisher.Instance;

src/WebJobs.Script/Config/FunctionsHostingConfigOptions.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,38 @@ public bool WorkerIndexingEnabled
4343
}
4444
}
4545

46+
/// <summary>
47+
/// Gets or sets a value indicating whether SWT tokens should be accepted.
48+
/// </summary>
49+
public bool SwtAuthenticationEnabled
50+
{
51+
get
52+
{
53+
return GetFeatureOrDefault(ScriptConstants.HostingConfigSwtAuthenticationEnabled, "1") == "1";
54+
}
55+
56+
set
57+
{
58+
_features[ScriptConstants.HostingConfigSwtAuthenticationEnabled] = value ? "1" : "0";
59+
}
60+
}
61+
62+
/// <summary>
63+
/// Gets or sets a value indicating whether SWT tokens should be sent on outgoing requests.
64+
/// </summary>
65+
public bool SwtIssuerEnabled
66+
{
67+
get
68+
{
69+
return GetFeatureOrDefault(ScriptConstants.HostingConfigSwtIssuerEnabled, "1") == "1";
70+
}
71+
72+
set
73+
{
74+
_features[ScriptConstants.HostingConfigSwtIssuerEnabled] = value ? "1" : "0";
75+
}
76+
}
77+
4678
/// <summary>
4779
/// Gets a string delimited by '|' that contains the name of the apps with worker indexing disabled.
4880
/// </summary>
@@ -105,5 +137,16 @@ public string GetFeature(string name)
105137
}
106138
return null;
107139
}
140+
141+
/// <summary>
142+
/// Gets a feature by name, returning the specified default value if not found.
143+
/// </summary>
144+
/// <param name="name">Feature name.</param>
145+
/// <param name="defaultValue">The default value to use.</param>
146+
/// <returns>String value from hostig configuration.</returns>
147+
public string GetFeatureOrDefault(string name, string defaultValue)
148+
{
149+
return GetFeature(name) ?? defaultValue;
150+
}
108151
}
109152
}

src/WebJobs.Script/Config/FunctionsHostingConfigSource.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public FunctionsHostingConfigSource(IEnvironment environment)
2929
/// Builds the <see cref="FunctionsHostingConfigSource"/> for this source.
3030
/// </summary>
3131
/// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
32-
/// <returns>A <see cref="FunctionsHostingConfigProvider"/></returns>
32+
/// <returns>A <see cref="FunctionsHostingConfigProvider"/>.</returns>
3333
public override IConfigurationProvider Build(IConfigurationBuilder builder)
3434
{
3535
Path = _path;

src/WebJobs.Script/ScriptConstants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ public static class ScriptConstants
131131
public const string HostingConfigDisableLinuxAppServiceDetailedExecutionEvents = "DisableLinuxExecutionDetails";
132132
public const string HostingConfigDisableLinuxAppServiceExecutionEventLogBackoff = "DisableLinuxLogBackoff";
133133
public const string FeatureFlagEnableLegacyDurableVersionCheck = "EnableLegacyDurableVersionCheck";
134+
public const string HostingConfigSwtAuthenticationEnabled = "SwtAuthenticationEnabled";
135+
public const string HostingConfigSwtIssuerEnabled = "SwtIssuerEnabled";
134136

135137
public const string SiteAzureFunctionsUriFormat = "https://{0}.azurewebsites.net/azurefunctions";
136138
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
@@ -49,6 +49,7 @@ public class FunctionsSyncManagerTests : IDisposable
4949
private readonly Mock<ISecretManagerProvider> _secretManagerProviderMock;
5050
private readonly Mock<ISecretManager> _secretManagerMock;
5151
private readonly TestScriptHostService _scriptHostManager; // To refresh underlying IConfiguration for IAzureBlobStorageProvider
52+
private readonly FunctionsHostingConfigOptions _hostingConfigOptions;
5253
private string _function1;
5354
private bool _emptyContent;
5455
private bool _secretsEnabled;
@@ -152,7 +153,10 @@ public FunctionsSyncManagerTests()
152153
_scriptHostManager = new TestScriptHostService(configuration);
153154
var azureBlobStorageProvider = TestHelpers.GetAzureBlobStorageProvider(configuration, scriptHostManager: _scriptHostManager);
154155

155-
_functionsSyncManager = new FunctionsSyncManager(configuration, hostIdProviderMock.Object, optionsMonitor, loggerFactory.CreateLogger<FunctionsSyncManager>(), httpClientFactory, _secretManagerProviderMock.Object, _mockWebHostEnvironment.Object, _mockEnvironment.Object, _hostNameProvider, functionMetadataManager, azureBlobStorageProvider);
156+
_hostingConfigOptions = new FunctionsHostingConfigOptions();
157+
var hostingConfigOptionsWrapper = new OptionsWrapper<FunctionsHostingConfigOptions>(_hostingConfigOptions);
158+
159+
_functionsSyncManager = new FunctionsSyncManager(hostIdProviderMock.Object, optionsMonitor, loggerFactory.CreateLogger<FunctionsSyncManager>(), httpClientFactory, _secretManagerProviderMock.Object, _mockWebHostEnvironment.Object, _mockEnvironment.Object, _hostNameProvider, functionMetadataManager, azureBlobStorageProvider, hostingConfigOptionsWrapper);
156160
}
157161

158162
private string GetExpectedTriggersPayload(string postedConnection = DefaultTestConnection, string postedTaskHub = DefaultTestTaskHub, string durableVersion = "V2")
@@ -242,11 +246,13 @@ public void ArmCacheEnabled_VerifyDefault()
242246
}
243247

244248
[Theory]
245-
[InlineData(true)]
246-
[InlineData(false)]
247-
public async Task TrySyncTriggers_PostsExpectedContent(bool cacheEnabled)
249+
[InlineData(true, true)]
250+
[InlineData(false, true)]
251+
[InlineData(true, false)]
252+
public async Task TrySyncTriggers_PostsExpectedContent(bool cacheEnabled, bool swtIssuerEnabled)
248253
{
249254
_mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteArmCacheEnabled)).Returns(cacheEnabled ? "1" : "0");
255+
_hostingConfigOptions.SwtIssuerEnabled = swtIssuerEnabled;
250256

251257
using (var env = new TestScopedEnvironmentVariable(_vars))
252258
{
@@ -262,7 +268,16 @@ public async Task TrySyncTriggers_PostsExpectedContent(bool cacheEnabled)
262268
// verify expected headers
263269
Assert.Equal(ScriptConstants.FunctionsUserAgent, _mockHttpHandler.LastRequest.Headers.UserAgent.ToString());
264270
Assert.True(_mockHttpHandler.LastRequest.Headers.Contains(ScriptConstants.AntaresLogIdHeaderName));
265-
Assert.NotEmpty(_mockHttpHandler.LastRequest.Headers.GetValues(ScriptConstants.SiteRestrictedTokenHeaderName));
271+
272+
if (swtIssuerEnabled)
273+
{
274+
Assert.NotEmpty(_mockHttpHandler.LastRequest.Headers.GetValues(ScriptConstants.SiteRestrictedTokenHeaderName));
275+
}
276+
else
277+
{
278+
Assert.False(_mockHttpHandler.LastRequest.Headers.Contains(ScriptConstants.SiteRestrictedTokenHeaderName));
279+
}
280+
266281
Assert.NotEmpty(_mockHttpHandler.LastRequest.Headers.GetValues(ScriptConstants.SiteTokenHeaderName));
267282

268283
if (cacheEnabled)

0 commit comments

Comments
 (0)