StreamCopyHttpContent.CreateContentReadStreamAsync() not implemented stops signing code that needs to get a hash of the request body
- 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
Assessment
This issue has not been assessed yet.