Skip to content

Commit eb1138c

Browse files
committed
Add resource filter for ARM Extension API requests
1 parent 0043a61 commit eb1138c

File tree

13 files changed

+246
-4
lines changed

13 files changed

+246
-4
lines changed

src/WebJobs.Script.WebHost/App_Start/WebApiConfig.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Autofac.Integration.WebApi;
99
using Microsoft.Azure.WebJobs.Script.Config;
1010
using Microsoft.Azure.WebJobs.Script.WebHost.Controllers;
11+
using Microsoft.Azure.WebJobs.Script.WebHost.Filters;
1112
using Microsoft.Azure.WebJobs.Script.WebHost.Handlers;
1213

1314
namespace Microsoft.Azure.WebJobs.Script.WebHost
@@ -41,14 +42,14 @@ public static void Register(HttpConfiguration config, ScriptSettingsManager sett
4142
// Invoke registration callback
4243
dependencyCallback?.Invoke(builder, settings);
4344

45+
// Web API configuration and services
4446
var container = builder.Build();
4547
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
4648
config.Formatters.Add(new PlaintextMediaTypeFormatter());
4749
config.Services.Replace(typeof(IExceptionHandler), new ExceptionProcessingHandler(config));
4850
AddMessageHandlers(config);
49-
50-
// Web API configuration and services
51-
51+
AddFilters(config);
52+
5253
// Web API routes
5354
config.MapHttpAttributeRoutes();
5455

@@ -74,6 +75,11 @@ public static void Register(HttpConfiguration config, ScriptSettingsManager sett
7475
config.InitializeReceiveSalesforceWebHooks();
7576
}
7677

78+
private static void AddFilters(HttpConfiguration config)
79+
{
80+
config.Filters.Add(new ArmExtensionResourceFilter());
81+
}
82+
7783
private static void AddMessageHandlers(HttpConfiguration config)
7884
{
7985
config.MessageHandlers.Add(new SystemTraceHandler(config));

src/WebJobs.Script.WebHost/Controllers/KeysController.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ namespace Microsoft.Azure.WebJobs.Script.WebHost.Controllers
2121
{
2222
[JwtAuthentication]
2323
[AuthorizationLevel(AuthorizationLevel.Admin)]
24+
[ResourceContainsSecrets]
2425
public class KeysController : ApiController
2526
{
2627
private const string MasterKeyName = "_master";
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System;
2+
using System.Net;
3+
using System.Net.Http;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using System.Web.Http.Controllers;
7+
using System.Web.Http.Filters;
8+
using Microsoft.Azure.WebJobs.Script.WebHost.Properties;
9+
10+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Filters
11+
{
12+
/// <summary>
13+
/// Resource filter used to ensure secrets aren't returned for GET requests made via the Functions ARM extension
14+
/// API (hostruntime), unless properly authorized.
15+
/// </summary>
16+
/// <remarks>
17+
/// All our first class ARM APIs handle RBAC naturally. For the hostruntime bridge, the runtime collaborates
18+
/// based on request details coming from ARM/Geo.
19+
/// </remarks>
20+
public sealed class ArmExtensionResourceFilter : IActionFilter
21+
{
22+
public bool AllowMultiple => false;
23+
24+
public async Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
25+
{
26+
// We only want to apply this filter for GET extension ARM requests that were forwarded directly to us via
27+
// hostruntime bridge, not hostruntime requests initiated internally by the geomaster. The latter requests
28+
// won't have the x-ms-arm-request-tracking-id header.
29+
var request = actionContext.Request;
30+
bool isArmExtensionRequest = request.HasHeader(ScriptConstants.AntaresARMRequestTrackingIdHeader) &&
31+
request.HasHeader(ScriptConstants.AntaresARMExtensionsRouteHeader);
32+
33+
if (isArmExtensionRequest && request.Method == HttpMethod.Get)
34+
{
35+
// requests made by owner/co-admin are not filtered
36+
if (!request.HasHeaderValue(ScriptConstants.AntaresClientAuthorizationSourceHeader, "legacy"))
37+
{
38+
var actionDescriptor = actionContext.ActionDescriptor as ReflectedHttpActionDescriptor;
39+
if (actionDescriptor != null && actionDescriptor.MethodInfo != null &&
40+
Utility.GetHierarchicalAttributeOrNull<ResourceContainsSecretsAttribute>(actionDescriptor.MethodInfo) != null)
41+
{
42+
// if the resource returned by the action contains secrets, fail the request
43+
return request.CreateResponse(HttpStatusCode.Unauthorized, Resources.UnauthorizedArmExtensionResourceRequest);
44+
}
45+
}
46+
}
47+
48+
return await continuation();
49+
}
50+
}
51+
}

src/WebJobs.Script.WebHost/GlobalSuppressions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,5 @@
130130
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sas", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.BlobStorageSasSecretsRepository.#.ctor(System.String,System.String,System.String)")]
131131
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.BlobStorageSasSecretsRepository.#.ctor(System.String,System.String,System.String)")]
132132
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.BlobStorageSecretsRepository.#.ctor(System.String,System.String,System.String)")]
133+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "hostruntime", Scope = "resource", Target = "Microsoft.Azure.WebJobs.Script.WebHost.Properties.Resources.resources")]
133134

src/WebJobs.Script.WebHost/Properties/Resources.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/WebJobs.Script.WebHost/Properties/Resources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,4 +319,7 @@
319319
<data name="TraceStaleHostSecretRefresh" xml:space="preserve">
320320
<value>Stale host secrets detected. Refreshing secrets.</value>
321321
</data>
322+
<data name="UnauthorizedArmExtensionResourceRequest" xml:space="preserve">
323+
<value>GET requests for this resource via the hostruntime extensions API are not authorized. Please use an alternate first class ARM API.</value>
324+
</data>
322325
</root>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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+
6+
namespace Microsoft.Azure.WebJobs.Script.WebHost
7+
{
8+
/// <summary>
9+
/// Attribute applied to actions to indicate whether the resource returned by an action
10+
/// contains secrets.
11+
/// </summary>
12+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
13+
public sealed class ResourceContainsSecretsAttribute : Attribute
14+
{
15+
}
16+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,7 @@
508508
<Compile Include="Extensions\HttpRouteCollectionExtensions.cs" />
509509
<Compile Include="Extensions\HttpRouteFactoryExtensions.cs" />
510510
<Compile Include="Extensions\IEnumerableTasksExtensions.cs" />
511+
<Compile Include="Filters\ArmExtensionResourceFilter.cs" />
511512
<Compile Include="Filters\EnableDebugModeAttribute.cs" />
512513
<Compile Include="Filters\RequiresRunningHostAttribute.cs" />
513514
<Compile Include="HostNameProvider.cs" />
@@ -517,6 +518,7 @@
517518
<Compile Include="Management\SyncTriggersResult.cs" />
518519
<Compile Include="Management\WebFunctionsManager.cs" />
519520
<Compile Include="Models\FunctionMetadataResponse.cs" />
521+
<Compile Include="ResourceContainsSecretsAttribute.cs" />
520522
<Compile Include="Security\SecretsUtility.cs" />
521523
<Compile Include="Security\SimpleWebTokenHelper.cs" />
522524
<Compile Include="StandbyManager.cs" />

src/WebJobs.Script/Extensions/HttpRequestMessageExtensions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ namespace Microsoft.Azure.WebJobs.Script
1212
{
1313
public static class HttpRequestMessageExtensions
1414
{
15+
public static bool HasHeader(this HttpRequestMessage request, string headerName)
16+
{
17+
return !string.IsNullOrEmpty(request.GetHeaderValueOrDefault(headerName));
18+
}
19+
20+
public static bool HasHeaderValue(this HttpRequestMessage request, string headerName, string value)
21+
{
22+
return string.Equals(request.GetHeaderValueOrDefault(headerName), value, StringComparison.OrdinalIgnoreCase);
23+
}
24+
1525
public static bool MatchRoute(this HttpRequestMessage request, string route)
1626
{
1727
return request.RequestUri.LocalPath.Trim('/').ToLowerInvariant().Equals(route.Trim('/').ToLowerInvariant());

src/WebJobs.Script/GlobalSuppressions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,4 +246,6 @@
246246
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.ScriptHost.#ConfigureLogging()")]
247247
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Dir", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.Scale.ApplicationPerformanceCounters.#RemoteDirMonitorLimit")]
248248
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Dir", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.Scale.ApplicationPerformanceCounters.#RemoteDirMonitors")]
249-
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sas", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.EnvironmentSettingNames.#AzureWebJobsSecretStorageSas")]
249+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sas", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.EnvironmentSettingNames.#AzureWebJobsSecretStorageSas")]
250+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ARM", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.ScriptConstants.#AntaresARMRequestTrackingIdHeader")]
251+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ARM", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.ScriptConstants.#AntaresARMExtensionsRouteHeader")]

0 commit comments

Comments
 (0)