diff --git a/src/OpenRasta.Plugins.ReverseProxy/FluentApiExtensions.cs b/src/OpenRasta.Plugins.ReverseProxy/FluentApiExtensions.cs index e1090443..ede7daea 100644 --- a/src/OpenRasta.Plugins.ReverseProxy/FluentApiExtensions.cs +++ b/src/OpenRasta.Plugins.ReverseProxy/FluentApiExtensions.cs @@ -1,12 +1,6 @@ -using System; -using System.Net; -using System.Net.Http; -using System.Threading; -using OpenRasta.Configuration; +using OpenRasta.Configuration; using OpenRasta.Configuration.Fluent; using OpenRasta.Configuration.Fluent.Extensions; -using OpenRasta.Configuration.MetaModel; -using OpenRasta.Configuration.MetaModel.Handlers; using OpenRasta.Plugins.ReverseProxy.HttpClientFactory; using OpenRasta.Plugins.ReverseProxy.HttpMessageHandlers; @@ -26,7 +20,7 @@ public static void ReverseProxyFor(this IUriDefinition uriConfiguration, string public static T ReverseProxy(this T uses, ReverseProxyOptions options = null) where T : IUses { options = options ?? new ReverseProxyOptions(); - + if (options.HttpClient.RoundRobin.Enabled) { var handler = options.HttpClient.Handler; @@ -37,13 +31,14 @@ public static T ReverseProxy(this T uses, ReverseProxyOptions options = null) options.HttpClient.RoundRobin.ClientCount, handler, options.HttpClient.RoundRobin.LeaseTime); - + uses.Dependency(d => d.Singleton(() => new ReverseProxy( options.Timeout, options.ForwardedHeaders.ConvertLegacyHeaders, options.Via.Pseudonym, factory.GetClient, - options.OnSend + options.OnSend, + options.ForwardedHeaders.ByIdentifierOverride ))); } else @@ -53,7 +48,8 @@ public static T ReverseProxy(this T uses, ReverseProxyOptions options = null) options.ForwardedHeaders.ConvertLegacyHeaders, options.Via.Pseudonym, options.HttpClient.Factory, - options.OnSend + options.OnSend, + options.ForwardedHeaders.ByIdentifierOverride ))); } diff --git a/src/OpenRasta.Plugins.ReverseProxy/ReverseProxy.cs b/src/OpenRasta.Plugins.ReverseProxy/ReverseProxy.cs index f71e2812..27459fb2 100644 --- a/src/OpenRasta.Plugins.ReverseProxy/ReverseProxy.cs +++ b/src/OpenRasta.Plugins.ReverseProxy/ReverseProxy.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Net.Http; @@ -18,17 +17,19 @@ public class ReverseProxy readonly TimeSpan _timeout; readonly bool _convertForwardedHeaders; readonly string _viaIdentifier; + readonly string _byIdentifierOverride; public ReverseProxy(TimeSpan requestTimeout, bool convertForwardedHeaders, string viaIdentifier, Func clientFactory, - Action onSend) + Action onSend, + string byIdentifierOverride = null) { _timeout = requestTimeout; _httpClient = clientFactory; _onSend = onSend; _convertForwardedHeaders = convertForwardedHeaders; _viaIdentifier = viaIdentifier; - + _byIdentifierOverride = byIdentifierOverride; } public async Task Send(ICommunicationContext context, string target) @@ -42,7 +43,7 @@ public async Task Send(ICommunicationContext context, stri PrepareRequestBody(context, requestMessage); - PrepareRequestHeaders(context, requestMessage, _convertForwardedHeaders); + PrepareRequestHeaders(context, requestMessage, _convertForwardedHeaders, _byIdentifierOverride); var viaIdentifier = PrepareViaHeader(context, requestMessage); @@ -90,7 +91,7 @@ static void PrepareRequestBody(ICommunicationContext context, HttpRequestMessage } static void PrepareRequestHeaders(ICommunicationContext context, HttpRequestMessage request, - bool convertLegacyHeaders) + bool convertLegacyHeaders, string byIdentifierOverride = null) { StringBuilder legacyForward = null; @@ -142,12 +143,19 @@ void appendParameter(string key, string value) request.Headers.Add(header.Key, header.Value); } + var byIdentifier = + byIdentifierOverride ?? + (context.PipelineData.ContainsKey("server.localIpAddress") + ? context.PipelineData["server.localIpAddress"].ToString() + : $"_{Environment.MachineName}"); + if (convertLegacyHeaders && legacyForward?.Length > 0) { + legacyForward.Append($";by={byIdentifier}"); request.Headers.Add("forwarded", legacyForward.ToString()); } - request.Headers.Add("forwarded", CurrentForwarded(context)); + request.Headers.Add("forwarded", CurrentForwarded(context, byIdentifier)); } static Uri GetProxyTargetUri(TemplatedUriMatch requestUriMatch, string target, @@ -192,9 +200,9 @@ static Uri GetProxyTargetUri(TemplatedUriMatch requestUriMatch, string target, return proxyTargetUri; } - static string CurrentForwarded(ICommunicationContext context) + static string CurrentForwarded(ICommunicationContext context, string byIdentifier) { - return $"proto={context.Request.Uri.Scheme};host={context.Request.Uri.Host}"; + return $"proto={context.Request.Uri.Scheme};host={context.Request.Uri.Host};by={byIdentifier}"; } } } \ No newline at end of file diff --git a/src/OpenRasta.Plugins.ReverseProxy/ReverseProxyOptions.cs b/src/OpenRasta.Plugins.ReverseProxy/ReverseProxyOptions.cs index 3c31a089..382402bd 100644 --- a/src/OpenRasta.Plugins.ReverseProxy/ReverseProxyOptions.cs +++ b/src/OpenRasta.Plugins.ReverseProxy/ReverseProxyOptions.cs @@ -25,6 +25,7 @@ public class ForwardedHeadersOptions { public bool ConvertLegacyHeaders { get; set; } public bool RunAsForwardedHost { get; set; } + public string ByIdentifierOverride { get; set; } } public class HttpClientOptions diff --git a/src/Tests/Plugins.ReverseProxy/Implementation/ProxyApiFrom.cs b/src/Tests/Plugins.ReverseProxy/Implementation/ProxyApiFrom.cs index 473d2dae..4c4607c3 100644 --- a/src/Tests/Plugins.ReverseProxy/Implementation/ProxyApiFrom.cs +++ b/src/Tests/Plugins.ReverseProxy/Implementation/ProxyApiFrom.cs @@ -10,15 +10,18 @@ public class ProxyApiFrom : IConfigurationSource readonly string from; readonly string to; readonly ReverseProxyOptions options; + readonly string localIpAddress; public ProxyApiFrom( string from, string to, - ReverseProxyOptions options) + ReverseProxyOptions options, + string localIpAddress = null) { this.from = from; this.to = to; this.options = options; + this.localIpAddress = localIpAddress; } public void Configure() @@ -30,6 +33,8 @@ public void Configure() ResourceSpace.Uses.ReverseProxy(options); ResourceSpace.Uses.PipelineContributor(); + + ResourceSpace.Uses.PipelineContributor(() => new SetLocalIpAddress(localIpAddress)); } } @@ -45,4 +50,31 @@ public void Initialize(IPipeline pipelineRunner) .Before(); } } + + public class SetLocalIpAddress : IPipelineContributor + { + readonly string localIpAddress; + + public SetLocalIpAddress(string localIpAddress) + { + this.localIpAddress = localIpAddress; + } + + public void Initialize(IPipeline pipelineRunner) + { + pipelineRunner.Notify(context => + { + if (localIpAddress != null) + { + context.PipelineData["server.localIpAddress"] = localIpAddress; + } + else + { + context.PipelineData.Remove("server.localIpAddress"); + } + return PipelineContinuation.Continue; + }) + .Before(); + } + } } \ No newline at end of file diff --git a/src/Tests/Plugins.ReverseProxy/Implementation/ProxyServer.cs b/src/Tests/Plugins.ReverseProxy/Implementation/ProxyServer.cs index f165edfd..1d56c81e 100644 --- a/src/Tests/Plugins.ReverseProxy/Implementation/ProxyServer.cs +++ b/src/Tests/Plugins.ReverseProxy/Implementation/ProxyServer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Net; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; @@ -9,7 +10,6 @@ using OpenRasta.Hosting.AspNetCore; using OpenRasta.Plugins.ReverseProxy; using OpenRasta.Web; -using Tests.Hosting.Owin; using HttpMethod = System.Net.Http.HttpMethod; namespace Tests.Plugins.ReverseProxy.Implementation @@ -18,6 +18,7 @@ public class ProxyServer { Func _fromUri; Func _toUri; + string _fromLocalIpAddress; Action _fromOptions; Action _toOptions; @@ -32,10 +33,13 @@ public ProxyServer() _serverFactory = CreateTestServers; } - public ProxyServer FromServer(string fromUri, Action options = null) + public ProxyServer FromServer(string fromUri, + Action options = null, + string localIpAddress = null) { _fromUri = port => fromUri; _fromOptions = options; + _fromLocalIpAddress = localIpAddress; return this; } @@ -47,10 +51,10 @@ public ProxyServer FromServer(Func fromUri, Action> handler = null, Action options = null - ,string resourceRegistrationUri = null) + , string resourceRegistrationUri = null) { _toUri = port => toUri; _toOptions = options; @@ -158,7 +162,7 @@ async Task SendAsync(string method, string uri) (IDisposable host, int port) CreateKestrelFromServer(int toPort) { var options = new ReverseProxyOptions(); - + _fromOptions?.Invoke(options); var host = new WebHostBuilder() @@ -240,7 +244,7 @@ TestServer CreateFromServer(Func httpMessageHandler) .Configure(app => { app.UseOpenRasta( - new ProxyApiFrom(_fromUri(80), _toUri(80), options)); + new ProxyApiFrom(_fromUri(80), _toUri(80), options, _fromLocalIpAddress)); })); } } diff --git a/src/Tests/Plugins.ReverseProxy/forwarded_headers/forwarded_header_generation.cs b/src/Tests/Plugins.ReverseProxy/forwarded_headers/forwarded_header_generation.cs index 7a527397..0e277d73 100644 --- a/src/Tests/Plugins.ReverseProxy/forwarded_headers/forwarded_header_generation.cs +++ b/src/Tests/Plugins.ReverseProxy/forwarded_headers/forwarded_header_generation.cs @@ -1,4 +1,9 @@ -using System.Threading.Tasks; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using OpenRasta.Collections; using Shouldly; using Tests.Plugins.ReverseProxy.Implementation; using Xunit; @@ -20,7 +25,7 @@ public async Task legacy_is_rewritten() .GetAsync("proxy")) { - response.Content.ShouldBe("|host=openrasta.example;proto=https;base=\"/app\",proto=http;host=localhost"); + response.Content.ShouldMatch("^\\|host=openrasta.example;proto=https;base=\\\"/app\\\";by=.*,proto=http;host=localhost;by=.*$"); } } @@ -31,9 +36,49 @@ public async Task forwarded_chain_is_preserved() .FromServer("/proxy") .ToServer("/proxied", async ctx => ctx.Request.Headers["Forwarded"]) .AddHeader("Forwarded", "host=openrasta.example") + .AddHeader("Forwarded", "host=openrasta.example2") .GetAsync("proxy")) { - response.Content.ShouldBe("host=openrasta.example,proto=http;host=localhost"); + response.Content.ShouldMatch("^host=openrasta.example,host=openrasta.example2,proto=http;host=localhost;by=.*$"); + } + } + + [Fact] + public async Task by_is_set_to_owin_local_ip_when_it_is_present() + { + using (var response = await new ProxyServer() + .FromServer("/proxy", localIpAddress: "10.0.10.1") + .ToServer("/proxied", async ctx => ctx.Request.Headers["Forwarded"]) + .AddHeader("Forwarded", "host=openrasta.example") + .GetAsync("proxy")) + { + response.Content.ShouldBe("host=openrasta.example,proto=http;host=localhost;by=10.0.10.1"); + } + } + + [Fact] + public async Task by_is_set_to_configured_override_when_it_is_present() + { + using (var response = await new ProxyServer() + .FromServer("/proxy", options => options.ForwardedHeaders.ByIdentifierOverride = "foo", "10.0.10.1") + .ToServer("/proxied", async ctx => ctx.Request.Headers["Forwarded"]) + .AddHeader("Forwarded", "host=openrasta.example") + .GetAsync("proxy")) + { + response.Content.ShouldBe("host=openrasta.example,proto=http;host=localhost;by=foo"); + } + } + + [Fact] + public async Task by_is_set_to_obfuscated_machine_name_when_no_owin_local_ip_is_present_and_no_override_configured() + { + using (var response = await new ProxyServer() + .FromServer("/proxy") + .ToServer("/proxied", async ctx => ctx.Request.Headers["Forwarded"]) + .AddHeader("Forwarded", "host=openrasta.example") + .GetAsync("proxy")) + { + response.Content.ShouldBe($"host=openrasta.example,proto=http;host=localhost;by=_{Environment.MachineName}"); } } }