Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clientruntime/sanitize request header #28169

Merged
merged 2 commits into from
Apr 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using Microsoft.Rest.Utilities;
using System;
using System.Collections.Generic;
using System.Net.Http;
Expand All @@ -26,6 +27,7 @@ public HttpRequestMessageWrapper(HttpRequestMessage httpRequest, string content)

this.CopyHeaders(httpRequest.Headers);
this.CopyHeaders(httpRequest.GetContentHeaders());
HttpRequestSanitizer.SanitizerHeaders(Headers);

this.Content = content;
this.Method = httpRequest.Method;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
<Description>Infrastructure for error handling, tracing, and HttpClient pipeline configuration. Required by client libraries generated using AutoRest.</Description>
<AssemblyName>Microsoft.Rest.ClientRuntime</AssemblyName>
<AssemblyTitle>Client Runtime Library for Microsoft AutoRest Generated Clients</AssemblyTitle>
<Version>2.3.23</Version>
<Version>2.3.24</Version>
<PackageId>Microsoft.Rest.ClientRuntime</PackageId>
<PackageTags>Microsoft AutoRest ClientRuntime $(NugetCommonTags) $(NugetCommonProfileTags)</PackageTags>
<PackageReleaseNotes>
<![CDATA[
Added support for specifying MaxRetries in RetryAfterDelegatingHandler.
- Improving sanitization of exception messages. For details, see "CVE-2022-26907 - Security Update Guide - Microsoft - Azure SDK for .NET Information Disclosure Vulnerability": https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-26907
- Optimize performance of parsing JSON content
]]>
</PackageReleaseNotes>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;

namespace Microsoft.Rest.Utilities
{
/// <summary>
/// Sanitizer used internall by <see cref="HttpRequestMessageWrapper"/>.
/// </summary>
internal class HttpRequestSanitizer
{
private readonly static string _redactedPlaceholder = "REDACTED";
private readonly static HashSet<string> _allowedHeaders = new HashSet<string>(new string[]
{
"x-ms-request-id",
"x-ms-client-request-id",
"x-ms-return-client-request-id",
"traceparent",
"MS-CV",

"Accept",
"Cache-Control",
"Connection",
"Content-Length",
"Content-Type",
"Date",
"ETag",
"Expires",
"If-Match",
"If-Modified-Since",
"If-None-Match",
"If-Unmodified-Since",
"Last-Modified",
"Pragma",
"Request-Id",
"Retry-After",
"Server",
"Transfer-Encoding",
"User-Agent",
"WWW-Authenticate" // OAuth Challenge header.
}, StringComparer.OrdinalIgnoreCase);

/// <summary>
/// Sanitize value of sensitive headers in the given <paramref name="headers"/>.
/// </summary>
/// <param name="headers">A collection of headers to sanitize.</param>
public static void SanitizerHeaders(IDictionary<string, IEnumerable<string>> headers)
{
if (headers == null)
{
return;
}

var namesOfHeaderToSanitize = headers.Keys.Except(_allowedHeaders, StringComparer.OrdinalIgnoreCase).ToList();

foreach (string name in namesOfHeaderToSanitize)
{
headers[name] = new string[] { _redactedPlaceholder };
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

namespace Microsoft.Rest.ClientRuntime.Tests
{
using System.Net;
using System.Net.Http;
using System.Collections.Generic;
using Xunit;
using System.Linq;

public class HttpRequestSanitizerTest
{
[Theory]
[InlineData("authorization")]
[InlineData("Authorization")]
[InlineData("AUTHORIZATION")]
public void SanitizeAuthorizationHeader(string headerName)
{
var request = CreateRequestWrapper(headerName, "test");

Assert.True(request.Headers.TryGetValue(HttpRequestHeader.Authorization.ToString(), out IEnumerable<string> sanitizedValues));
Assert.Single(sanitizedValues);
Assert.NotEqual("test", sanitizedValues.First());
Assert.Equal("REDACTED", sanitizedValues.First());
}

[Theory]
[InlineData("custom")]
[InlineData("foo")]
[InlineData("x-ms-secret")]
public void SanitizeCustomHeader(string headerName)
{
var request = CreateRequestWrapper(headerName, "test");

Assert.True(request.Headers.TryGetValue(headerName, out IEnumerable<string> sanitizedValues));
Assert.Single(sanitizedValues);
Assert.NotEqual("test", sanitizedValues.First());
Assert.Equal("REDACTED", sanitizedValues.First());
}

[Theory]
[InlineData("Accept", "application/json")]
[InlineData("User-Agent", "azure-sdk")]
[InlineData("Pragma", "foo")]
public void KeepAllowedHeaders(string headerName, string headerValue)
{
var request = CreateRequestWrapper(headerName, headerValue);

Assert.True(request.Headers.TryGetValue(headerName, out IEnumerable<string> sanitizedValues));
Assert.Single(sanitizedValues);
Assert.Equal(headerValue, sanitizedValues.First());
}

[Theory]
[InlineData("accept", "Accept", "application/json")]
[InlineData("user-agent", "User-Agent", "azure-sdk")]
[InlineData("pragma", "Pragma", "foo")]
public void AllowedHeaderNamesAreCaseInsensitive(string headerName, string standardName, string headerValue)
{
var request = CreateRequestWrapper(headerName, headerValue);

Assert.True(request.Headers.TryGetValue(standardName, out IEnumerable<string> sanitizedValues));
Assert.Single(sanitizedValues);
Assert.Equal(headerValue, sanitizedValues.First());
}

[Fact]
public void OnlySanitizeNotAllowedHeader()
{
var request = CreateRequestWrapper("Authorization", "test");
request.Headers.Add("Pragma", new string[] { "foo" });
request.Headers.Add("User-Agent", new string[] { "azure-sdk" });

Assert.True(request.Headers.TryGetValue(HttpRequestHeader.Authorization.ToString(), out IEnumerable<string> sanitizedValues));
Assert.Single(sanitizedValues);
Assert.NotEqual("test", sanitizedValues.First());
Assert.Equal("REDACTED", sanitizedValues.First());

Assert.True(request.Headers.TryGetValue("Pragma", out sanitizedValues));
Assert.Single(sanitizedValues);
Assert.Equal("foo", sanitizedValues.First());

Assert.True(request.Headers.TryGetValue("User-Agent", out sanitizedValues));
Assert.Single(sanitizedValues);
Assert.Equal("azure-sdk", sanitizedValues.First());
}

private HttpRequestMessageWrapper CreateRequestWrapper(string headerName, string headerValue)
{
var request = new HttpRequestMessage();
request.Headers.Add(headerName, headerValue);
return new HttpRequestMessageWrapper(request, "");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ public void HeadersAndPayloadAreNotDisposed()
GC.Collect();
Assert.NotNull(ex.Request);
Assert.NotNull(ex.Response);
Assert.Equal("2013-11-01", ex.Request.Headers["x-ms-version"].First());
Assert.Equal("REDACTED", ex.Request.Headers["x-ms-version"].First());
Assert.Equal("Text", ex.Response.Content);
}
}
Expand Down