dotnet / dotnet/yarp

StreamCopyHttpContent.CreateContentReadStreamAsync() not implemented stops signing code that needs to get a hash of the request body

Open
#1,695 5 comments 0 reactions 0 assignees View on GitHub
help wanted Type: Documentation
Dominant language
C#
Stars
9.6k
Forks
933
Avg merge
12d 18h
Merged PRs (30d)
2

Description

### Description

I'm writing a proxy with YARP that signs requests for an upstream service. The proxy needs to take all request types and paths and simply send them upstream - the proxy may be reused as a sidecar in a number of projects, so must handle any kind of request.

The signature is derived from a hash of: an external key, request method, request path, request headers, and the body itself. The resulting signature is added to the request headers and is validated by the remote server (approximately - the exact details are specified in https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)

I'm currently using https://github.com/FantasticFiasco/aws-signature-version-4 to do the signing, and am integrating it into YARP by using a Request Transform.

Within the transform, the signing library calls `requestContext.ProxyRequest.Content.ReadAsStreamAsync()` so it can then calculate a SHA256 on the request body as part of the signing algorithm.

The problem here is that `ProxyRequest.Content` is implemented as `StreamCopyHttpContent` from YARP, and the method `ReadAsStreamAsync()` on that class simply throws a `NotImplementedException()`.

I'm happy to raise a PR in https://github.com/FantasticFiasco/aws-signature-version-4 if there is a better way of getting the body that works, or failing that, how else should I address the problem of needed to read the request content to add an additional header?

### To Reproduce
A simple approximation of the issue of getting the content stream to hash can be shown by the following:

```c#
public void ConfigureServices(IServiceCollection services)
{
var proxyBuilder = services.AddReverseProxy();
proxyBuilder.LoadFromConfig(Configuration.GetSection("ReverseProxy"));

proxyBuilder.AddTransforms(context =>
{
context.AddRequestTransform(async requestContext =>
{
using (SHA256 hash = SHA256.Create())
{
if (requestContext.ProxyRequest.Content != null)
{
var contentStream = await requestContext.ProxyRequest.Content.ReadAsStreamAsync();
var value = await hash.ComputeHashAsync(contentStream);
requestContext.ProxyRequest.AddHeader("X-Hash", BitConverter.ToString(value));
}
}
});
});
}

```

That throws the following:

```
System.NotImplementedException: The method or operation is not implemented.
at Yarp.ReverseProxy.Forwarder.StreamCopyHttpContent.CreateContentReadStreamAsync()
at System.Net.Http.HttpContent.CreateContentReadStreamAsync(CancellationToken cancellationToken)
at System.Net.Http.HttpContent.ReadAsStreamAsync(CancellationToken cancellationToken)
at System.Net.Http.HttpContent.ReadAsStreamAsync()
at BasicYARPSample.Startup.<>c.<b__7_1>d.MoveNext() in /Users/Edouard.Poor/Projects/YarpPoc/YarpPoc/Startup.cs:line 46
--- End of stack trace from previous location ---
at Yarp.ReverseProxy.Transforms.Builder.StructuredTransformer.TransformRequestAsync(HttpContext httpContext, HttpRequestMessage proxyRequest, String destinationPrefix)
at Yarp.ReverseProxy.Forwarder.HttpForwarder.CreateRequestMessageAsync(HttpContext context, String destinationPrefix, HttpTransformer transformer, ForwarderRequestConfig requestConfig, Boolean isStreamingRequest, ActivityCancellationTokenSource activityToken)
at Yarp.ReverseProxy.Forwarder.HttpForwarder.SendAsync(HttpContext context, String destinationPrefix, HttpMessageInvoker httpClient, ForwarderRequestConfig requestConfig, HttpTransformer transformer)
at Yarp.ReverseProxy.Forwarder.ForwarderMiddleware.Invoke(HttpContext context)
at Yarp.ReverseProxy.Health.PassiveHealthCheckMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
```

because StreamCopyHttpContent.CreateContentReadStreamAsync's implementation is:
```c#
// this is used internally by HttpContent.ReadAsStreamAsync(...)
protected override Task CreateContentReadStreamAsync()
{
// Nobody should be calling this...
throw new NotImplementedException();
}
```

### Further technical details

- YARP 1.1
- macOS

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.