public async Task LoadAsync_ConfigFilterRouteActionThrows_Throws() { var route1 = new ProxyRoute { RouteId = "route1", Match = { Hosts = new[] { "example.com" } }, Order = 1, ClusterId = "cluster1" }; var route2 = new ProxyRoute { RouteId = "route2", Match = { Hosts = new[] { "example2.com" } }, Order = 1, ClusterId = "cluster2" }; var services = CreateServices(new List <ProxyRoute>() { route1, route2 }, new List <Cluster>(), proxyBuilder => { proxyBuilder.AddProxyConfigFilter <ClusterAndRouteThrows>(); proxyBuilder.AddProxyConfigFilter <ClusterAndRouteThrows>(); }); var configManager = services.GetRequiredService <IProxyConfigManager>(); var ioEx = await Assert.ThrowsAsync <InvalidOperationException>(() => configManager.InitialLoadAsync()); Assert.Equal("Unable to load or apply the proxy configuration.", ioEx.Message); var agex = Assert.IsType <AggregateException>(ioEx.InnerException); Assert.Equal(2, agex.InnerExceptions.Count); Assert.IsType <NotFiniteNumberException>(agex.InnerExceptions.First().InnerException); Assert.IsType <NotFiniteNumberException>(agex.InnerExceptions.Skip(1).First().InnerException); }
public async Task Accepts_CustomAuthorizationPolicy() { var route = new ProxyRoute { RouteId = "route1", AuthorizationPolicy = "custom", Match = new ProxyMatch { Hosts = new[] { "localhost" }, }, ClusterId = "cluster1", }; var services = CreateServices(services => { services.AddAuthorization(options => { options.AddPolicy("custom", builder => builder.RequireAuthenticatedUser()); }); }); var validator = services.GetRequiredService <IConfigValidator>(); var result = await validator.ValidateRouteAsync(route); Assert.Empty(result); }
public void UnknownTransforms_Error() { var transformBuilder = CreateTransformBuilder(); var transforms = new[] { new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase) // Unknown transform { { "string1", "value1" }, { "string2", "value2" } }, new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase) // Unknown transform { { "string3", "value3" }, { "string4", "value4" } }, }; var route = new ProxyRoute() { Transforms = transforms }; var errors = transformBuilder.ValidateRoute(route); //All errors reported Assert.Equal(2, errors.Count); Assert.Equal("Unknown transform: string1;string2", errors.First().Message); Assert.Equal("Unknown transform: string3;string4", errors.Skip(1).First().Message); var ex = Assert.Throws <ArgumentException>(() => transformBuilder.BuildInternal(route, new Cluster())); // First error reported Assert.Equal("Unknown transform: string1;string2", ex.Message); }
/// <summary> /// Clones the route and adds the transform which will set the given header with the Base64 encoded client certificate. /// </summary> public static ProxyRoute WithTransformClientCertHeader(this ProxyRoute proxyRoute, string headerName) { return(proxyRoute.WithTransform(transform => { transform[ForwardedTransformFactory.ClientCertKey] = headerName; })); }
public async Task Accepts_RouteHeader_ExistsWithNoValue() { var route = new ProxyRoute { RouteId = "route1", Match = new ProxyMatch { Path = "/", Headers = new[] { new RouteHeader() { Name = "header1", Mode = HeaderMatchMode.Exists } }, }, ClusterId = "cluster1", }; var services = CreateServices(); var validator = services.GetRequiredService <IConfigValidator>(); var result = await validator.ValidateRouteAsync(route); Assert.Empty(result); }
public void DefaultsCanBeOverridenByForwarded() { var transformBuilder = CreateTransformBuilder(); var transforms = new[] { new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase) { { "RequestHeaderOriginalHost", "true" } }, new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase) { { "Forwarded", "proto" } }, }; var route = new ProxyRoute() { Transforms = transforms }; var errors = transformBuilder.ValidateRoute(route); Assert.Empty(errors); var results = transformBuilder.BuildInternal(route, new Cluster()); var transform = Assert.Single(results.RequestTransforms); var forwardedTransform = Assert.IsType <RequestHeaderForwardedTransform>(transform); Assert.True(forwardedTransform.ProtoEnabled); }
/// <summary> /// Clones the route and adds the transform which will remove the matching prefix from the request path. /// </summary> public static ProxyRoute WithTransformPathRemovePrefix(this ProxyRoute proxyRoute, PathString prefix) { return(proxyRoute.WithTransform(transform => { transform[PathTransformFactory.PathRemovePrefixKey] = prefix.Value; })); }
public static void AddTransformPathRouteValues(this ProxyRoute proxyRoute, PathString pattern) { proxyRoute.Transforms.Add(new Dictionary <string, string> { ["PathPattern"] = pattern.Value, }); }
public static void AddTransformSuppressRequestHeaders(this ProxyRoute proxyRoute) { proxyRoute.Transforms.Add(new Dictionary <string, string> { ["RequestHeadersCopy"] = "False", }); }
public static void AddTransformRemoveQueryParameter(this ProxyRoute proxyRoute, string queryKey) { proxyRoute.Transforms.Add(new Dictionary <string, string> { ["QueryRemoveParameter"] = queryKey }); }
public static void AddTransformPathRemovePrefix(this ProxyRoute proxyRoute, PathString prefix) { proxyRoute.Transforms.Add(new Dictionary <string, string> { ["PathRemovePrefix"] = prefix.Value, }); }
public static void AddTransformXForwarded(this ProxyRoute proxyRoute, string headerPrefix = "X-Forwarded", bool useFor = true, bool useHost = true, bool useProto = true, bool usePathBase = true, bool append = true) { var headers = new List <string>(); if (useFor) { headers.Add("For"); } if (usePathBase) { headers.Add("PathBase"); } if (useHost) { headers.Add("Host"); } if (useProto) { headers.Add("Proto"); } proxyRoute.Transforms.Add(new Dictionary <string, string> { ["X-Forwarded"] = string.Join(',', headers), ["Append"] = append.ToString(), ["Prefix"] = headerPrefix }); }
public async Task BuildConfigAsync_RouteValidationError_SkipsRoute() { // Arrange var errorReporter = new TestConfigErrorReporter(); Mock <IBackendsRepo>() .Setup(r => r.GetBackendsAsync(It.IsAny <CancellationToken>())) .ReturnsAsync(new Dictionary <string, Backend>()); var route1 = new ProxyRoute { RouteId = "route1", Match = { Host = "example.com" }, Priority = 1, BackendId = "backend1" }; Mock <IRoutesRepo>() .Setup(r => r.GetRoutesAsync(It.IsAny <CancellationToken>())) .ReturnsAsync(new[] { route1 }); var parsedRoute1 = new ParsedRoute(); Mock <IRouteValidator>() .Setup(r => r.ValidateRoute(parsedRoute1, errorReporter)) .Returns(false); // Act var configManager = Create <DynamicConfigBuilder>(); var result = await configManager.BuildConfigAsync(errorReporter, CancellationToken.None); // Assert Assert.True(result.IsSuccess); Assert.NotNull(result.Value); Assert.Empty(result.Value.Backends); Assert.Empty(result.Value.Routes); }
public async Task BuildConfig_OneClusterOneDestinationOneRoute_Works() { const string TestAddress = "https://localhost:123/"; var cluster = new Cluster { Id = "cluster1", Destinations = { { "d1", new Destination { Address = TestAddress } } } }; var route = new ProxyRoute { RouteId = "route1", ClusterId = "cluster1", Match = { Path = "/" } }; var services = CreateServices(new List <ProxyRoute>() { route }, new List <Cluster>() { cluster }); var manager = services.GetRequiredService <IProxyConfigManager>(); var dataSource = await manager.InitialLoadAsync(); Assert.NotNull(dataSource); var endpoints = dataSource.Endpoints; Assert.Single(endpoints); var clusterManager = services.GetRequiredService <IClusterManager>(); var actualClusters = clusterManager.GetItems(); Assert.Single(actualClusters); Assert.Equal("cluster1", actualClusters[0].ClusterId); Assert.NotNull(actualClusters[0].DestinationManager); Assert.NotNull(actualClusters[0].Config.Value); var actualDestinations = actualClusters[0].DestinationManager.GetItems(); Assert.Single(actualDestinations); Assert.Equal("d1", actualDestinations[0].DestinationId); Assert.NotNull(actualDestinations[0].Config); Assert.Equal(TestAddress, actualDestinations[0].Config.Address); var routeManager = services.GetRequiredService <IRouteManager>(); var actualRoutes = routeManager.GetItems(); Assert.Single(actualRoutes); Assert.Equal("route1", actualRoutes[0].RouteId); Assert.NotNull(actualRoutes[0].Config.Value); Assert.Same(actualClusters[0], actualRoutes[0].Config.Value.Cluster); }
public void CallsTransformProviders() { var provider1 = new TestTransformProvider(); var provider2 = new TestTransformProvider(); var provider3 = new TestTransformProvider(); var builder = new TransformBuilder(new ServiceCollection().BuildServiceProvider(), Array.Empty <ITransformFactory>(), new[] { provider1, provider2, provider3 }); var route = new ProxyRoute(); var errors = builder.ValidateRoute(route); Assert.Empty(errors); Assert.Equal(1, provider1.ValidateRouteCalls); Assert.Equal(1, provider2.ValidateRouteCalls); Assert.Equal(1, provider3.ValidateRouteCalls); var cluster = new Cluster(); errors = builder.ValidateCluster(cluster); Assert.Empty(errors); Assert.Equal(1, provider1.ValidateClusterCalls); Assert.Equal(1, provider2.ValidateClusterCalls); Assert.Equal(1, provider3.ValidateClusterCalls); var transforms = builder.BuildInternal(route, cluster); Assert.Equal(1, provider1.ApplyCalls); Assert.Equal(1, provider2.ApplyCalls); Assert.Equal(1, provider3.ApplyCalls); Assert.Equal(3, transforms.ResponseTrailerTransforms.Count); }
public static void AddTransformUseOriginalHostHeader(this ProxyRoute proxyRoute) { proxyRoute.Transforms.Add(new Dictionary <string, string> { ["RequestHeaderOriginalHost"] = "True", }); }
public void DefaultsCanBeDisabled() { var transformBuilder = CreateTransformBuilder(); var transforms = new[] { new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase) { { "RequestHeaderOriginalHost", "true" } }, new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase) { { "X-Forwarded", "" } }, }; var route = new ProxyRoute() { Transforms = transforms }; var errors = transformBuilder.ValidateRoute(route); Assert.Empty(errors); var results = transformBuilder.BuildInternal(route, new Cluster()); Assert.NotNull(results); Assert.Null(results.ShouldCopyRequestHeaders); Assert.Empty(results.RequestTransforms); Assert.Empty(results.ResponseTransforms); Assert.Empty(results.ResponseTrailerTransforms); }
public static void AddTransformPathSet(this ProxyRoute proxyRoute, PathString path) { proxyRoute.Transforms.Add(new Dictionary <string, string> { ["PathSet"] = path.Value, }); }
/// <summary> /// Clones the route and adds the transform which sets the request path with the given value. /// </summary> public static ProxyRoute WithTransformPathSet(this ProxyRoute proxyRoute, PathString path) { return(proxyRoute.WithTransform(transform => { transform[PathTransformFactory.PathSetKey] = path.Value; })); }
public static void AddTransformClientCert(this ProxyRoute proxyRoute, string headerName) { proxyRoute.Transforms.Add(new Dictionary <string, string> { ["ClientCert"] = headerName }); }
/// <summary> /// Clones the route and adds the transform which will set the request path with the given value. /// </summary> public static ProxyRoute WithTransformPathRouteValues(this ProxyRoute proxyRoute, PathString pattern) { return(proxyRoute.WithTransform(transform => { transform[PathTransformFactory.PathPatternKey] = pattern.Value; })); }
public void AddEndpoint_JustHost_Works() { var services = CreateServices(); var factory = services.GetRequiredService <ProxyEndpointFactory>(); factory.SetProxyPipeline(context => Task.CompletedTask); var route = new ProxyRoute { RouteId = "route1", Match = new ProxyMatch { Hosts = new[] { "example.com" }, }, Order = 12, }; var cluster = new ClusterInfo("cluster1", new DestinationManager()); var routeInfo = new RouteInfo("route1"); var(routeEndpoint, routeConfig) = CreateEndpoint(factory, routeInfo, route, cluster); Assert.Same(cluster, routeConfig.Cluster); Assert.Equal("route1", routeEndpoint.DisplayName); Assert.Same(routeConfig, routeEndpoint.Metadata.GetMetadata <RouteConfig>()); Assert.Equal("/{**catchall}", routeEndpoint.RoutePattern.RawText); Assert.Equal(12, routeEndpoint.Order); Assert.False(routeConfig.HasConfigChanged(route, cluster, routeInfo.ClusterRevision)); var hostMetadata = routeEndpoint.Metadata.GetMetadata <HostAttribute>(); Assert.NotNull(hostMetadata); Assert.Single(hostMetadata.Hosts); Assert.Equal("example.com", hostMetadata.Hosts[0]); }
/// <summary> /// Clones the route and adds the transform which will add X-Forwarded-* headers. /// </summary> public static ProxyRoute WithTransformXForwarded(this ProxyRoute proxyRoute, string headerPrefix = "X-Forwarded-", bool useFor = true, bool useHost = true, bool useProto = true, bool usePathBase = true, bool append = true) { var headers = new List <string>(); if (useFor) { headers.Add(ForwardedTransformFactory.ForKey); } if (usePathBase) { headers.Add(ForwardedTransformFactory.PathBaseKey); } if (useHost) { headers.Add(ForwardedTransformFactory.HostKey); } if (useProto) { headers.Add(ForwardedTransformFactory.ProtoKey); } return(proxyRoute.WithTransform(transform => { transform[ForwardedTransformFactory.XForwardedKey] = string.Join(',', headers); transform[ForwardedTransformFactory.AppendKey] = append.ToString(); transform[ForwardedTransformFactory.PrefixKey] = headerPrefix; })); }
// Note this performs all validation steps without short circuiting in order to report all possible errors. public async ValueTask <IList <Exception> > ValidateRouteAsync(ProxyRoute route) { _ = route ?? throw new ArgumentNullException(nameof(route)); var errors = new List <Exception>(); if (string.IsNullOrEmpty(route.RouteId)) { errors.Add(new ArgumentException("Missing Route Id.")); } errors.AddRange(_transformBuilder.ValidateRoute(route)); await ValidateAuthorizationPolicyAsync(errors, route.AuthorizationPolicy, route.RouteId); await ValidateCorsPolicyAsync(errors, route.CorsPolicy, route.RouteId); if (route.Match == null) { errors.Add(new ArgumentException($"Route '{route.RouteId}' did not set any match criteria, it requires Hosts or Path specified. Set the Path to '/{{**catchall}}' to match all requests.")); return(errors); } if ((route.Match.Hosts == null || !route.Match.Hosts.Any(host => !string.IsNullOrEmpty(host))) && string.IsNullOrEmpty(route.Match.Path)) { errors.Add(new ArgumentException($"Route '{route.RouteId}' requires Hosts or Path specified. Set the Path to '/{{**catchall}}' to match all requests.")); } ValidateHost(errors, route.Match.Hosts, route.RouteId); ValidatePath(errors, route.Match.Path, route.RouteId); ValidateMethods(errors, route.Match.Methods, route.RouteId); ValidateHeaders(errors, route.Match.Headers, route.RouteId); return(errors); }
public async Task Rejects_InvalidRouteHeader(string name, string value, HeaderMatchMode mode, string error) { var routeHeader = new RouteHeader() { Name = name, Mode = mode, Values = value == null ? null : new[] { value }, }; var route = new ProxyRoute { RouteId = "route1", Match = new ProxyMatch { Path = "/", Headers = new[] { routeHeader }, }, ClusterId = "cluster1", }; var services = CreateServices(); var validator = services.GetRequiredService <IConfigValidator>(); var result = await validator.ValidateRouteAsync(route); var ex = Assert.Single(result); Assert.Contains(error, ex.Message); }
public void CallsTransformFactories() { var factory1 = new TestTransformFactory("1"); var factory2 = new TestTransformFactory("2"); var factory3 = new TestTransformFactory("3"); var builder = new TransformBuilder(new ServiceCollection().BuildServiceProvider(), new[] { factory1, factory2, factory3 }, Array.Empty <ITransformProvider>()); var route = new ProxyRoute().WithTransform(transform => { transform["2"] = "B"; }); var errors = builder.ValidateRoute(route); Assert.Empty(errors); Assert.Equal(1, factory1.ValidationCalls); Assert.Equal(1, factory2.ValidationCalls); Assert.Equal(0, factory3.ValidationCalls); var transforms = builder.BuildInternal(route, new Cluster()); Assert.Equal(1, factory1.BuildCalls); Assert.Equal(1, factory2.BuildCalls); Assert.Equal(0, factory3.BuildCalls); Assert.Single(transforms.ResponseTrailerTransforms); }
public async Task LoadAsync_ConfigFilterRouteActions_CanFixBrokenRoute() { var route1 = new ProxyRoute { RouteId = "route1", Match = { Hosts = new[] { "invalid host name" } }, Order = 1, ClusterId = "cluster1" }; var services = CreateServices(new List <ProxyRoute>() { route1 }, new List <Cluster>(), proxyBuilder => { proxyBuilder.AddProxyConfigFilter <FixRouteHostFilter>(); }); var configManager = services.GetRequiredService <IProxyConfigManager>(); var dataSource = await configManager.InitialLoadAsync(); var endpoints = dataSource.Endpoints; Assert.Single(endpoints); var endpoint = endpoints.Single(); Assert.Same(route1.RouteId, endpoint.DisplayName); var hostMetadata = endpoint.Metadata.GetMetadata <HostAttribute>(); Assert.NotNull(hostMetadata); var host = Assert.Single(hostMetadata.Hosts); Assert.Equal("example.com", host); }
public void BuildEndpoints_Headers_Works() { var services = CreateServices(); var factory = services.GetRequiredService <ProxyEndpointFactory>(); factory.SetProxyPipeline(context => Task.CompletedTask); var route = new ProxyRoute { RouteId = "route1", Match = new ProxyMatch { Path = "/", Headers = new[] { new RouteHeader() { Name = "header1", Values = new[] { "value1" }, Mode = HeaderMatchMode.HeaderPrefix, IsCaseSensitive = true, }, new RouteHeader() { Name = "header2", Mode = HeaderMatchMode.Exists, } } }, }; var cluster = new ClusterInfo("cluster1", new DestinationManager()); var routeInfo = new RouteInfo("route1"); var(routeEndpoint, routeConfig) = CreateEndpoint(factory, routeInfo, route, cluster); Assert.Same(cluster, routeConfig.Cluster); Assert.Equal("route1", routeEndpoint.DisplayName); var metadata = routeEndpoint.Metadata.GetMetadata <IHeaderMetadata>(); Assert.Equal(2, metadata.Matchers.Count); var firstMetadata = metadata.Matchers.First(); Assert.NotNull(firstMetadata); Assert.Equal("header1", firstMetadata.Name); Assert.Equal(new[] { "value1" }, firstMetadata.Values); Assert.Equal(HeaderMatchMode.HeaderPrefix, firstMetadata.Mode); Assert.True(firstMetadata.IsCaseSensitive); var secondMetadata = metadata.Matchers.Skip(1).Single(); Assert.NotNull(secondMetadata); Assert.Equal("header2", secondMetadata.Name); Assert.Null(secondMetadata.Values); Assert.Equal(HeaderMatchMode.Exists, secondMetadata.Mode); Assert.False(secondMetadata.IsCaseSensitive); Assert.False(routeConfig.HasConfigChanged(route, cluster)); }
public async Task BuildConfig_OneClusterOneDestinationOneRoute_Works() { const string TestAddress = "https://localhost:123/"; var cluster = new Cluster { Id = "cluster1", Destinations = new Dictionary <string, Destination>(StringComparer.OrdinalIgnoreCase) { { "d1", new Destination { Address = TestAddress } } } }; var route = new ProxyRoute { RouteId = "route1", ClusterId = "cluster1", Match = new RouteMatch { Path = "/" } }; var services = CreateServices(new List <ProxyRoute>() { route }, new List <Cluster>() { cluster }); var manager = services.GetRequiredService <ProxyConfigManager>(); var dataSource = await manager.InitialLoadAsync(); Assert.NotNull(dataSource); var endpoints = dataSource.Endpoints; var endpoint = Assert.Single(endpoints); var routeConfig = endpoint.Metadata.GetMetadata <RouteConfig>(); Assert.NotNull(routeConfig); Assert.Equal("route1", routeConfig.ProxyRoute.RouteId); var clusterInfo = routeConfig.Cluster; Assert.NotNull(clusterInfo); Assert.Equal("cluster1", clusterInfo.ClusterId); Assert.NotNull(clusterInfo.Destinations); Assert.NotNull(clusterInfo.Config); Assert.NotNull(clusterInfo.Config.HttpClient); Assert.Same(clusterInfo, routeConfig.Cluster); var actualDestinations = clusterInfo.Destinations.Values; var destination = Assert.Single(actualDestinations); Assert.Equal("d1", destination.DestinationId); Assert.NotNull(destination.Config); Assert.Equal(TestAddress, destination.Config.Options.Address); }
public async Task <ActionResult> UpdateProxyRoute(ProxyRoute proxyRoute) { _dbContext.Set <ProxyRoute>().Update(proxyRoute); await _dbContext.SaveChangesAsync(); _reverseProxyStore.Reload(); return(Ok()); }
public bool Handle(Stream proxyStream, HttpResponse httpResponse, string responseContent, NetworkCredential networkCredential, ProxyRoute routeUntilHere) { var match = _redirectEx.Match(responseContent); if (!match.Success) return false; var url = match.Groups["url"].Value; var uri = new Uri(url); using (var redirectStream = routeUntilHere.GetPrevious().Connect(uri.Host, uri.Port, true)) { new HttpRequest("GET", uri.PathAndQuery).Write(redirectStream); var redirectResponse = new HttpResponse().Read(redirectStream); var redirectResponseContent = redirectResponse.ReadContentString(redirectStream); } return true; }