// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHttpProxy httpProxy, WPMessageInvokerFactory messageInvoker, WPProxySettings settings) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseStaticFiles(); app.UseRouting(); app.UseCookiePolicy(); app.UseAuthentication(); app.UseAuthorization(); app.Use(next => context => { // Force https scheme (since request inside a docker container may appear to be http) context.Request.Scheme = "https"; return(next(context)); }); var transformer = new WPRequestTransformer(); // or HttpTransformer.Default; var requestOptions = new RequestProxyOptions { Timeout = TimeSpan.FromSeconds(100), Version = new Version(1, 1), // Not all servers support HTTP 2 VersionPolicy = HttpVersionPolicy.RequestVersionOrHigher }; app.UseEndpoints(endpoints => { // Dont't authenticate manifest.json endpoints.Map("/manifest.json", async httpContext => // { var siteSettings = settings.GetForHost(httpContext.Request.Host.ToString()); await httpProxy.ProxyAsync(httpContext, siteSettings.SourceAddress, messageInvoker.Create(), requestOptions, transformer); }); endpoints.Map("/{**catch-all}", async httpContext => // { var siteSettings = settings.GetForHost(httpContext.Request.Host.ToString()); await httpProxy.ProxyAsync(httpContext, siteSettings.SourceAddress, messageInvoker.Create(), requestOptions, transformer); var errorFeature = httpContext.Features.Get <IProxyErrorFeature>(); if (errorFeature != null) { var error = errorFeature.Error; var exception = errorFeature.Exception; throw errorFeature.Exception; } }) .RequireAuthorization(WPProxySettings.AuthorizationPolicy); endpoints.MapDefaultControllerRoute(); }); }
/// <summary> /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. /// </summary> public void Configure(IApplicationBuilder app, IHttpProxy httpProxy) { var httpClient = new HttpMessageInvoker(new SocketsHttpHandler() { UseProxy = false, AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.None, UseCookies = false }); // Copy all request headers except Host var transformer = new CustomTransformer(); // or HttpTransformer.Default; var requestOptions = new RequestProxyOptions(TimeSpan.FromSeconds(100), null); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.Map("/{**catch-all}", async httpContext => { await httpProxy.ProxyAsync(httpContext, "https://localhost:10000/", httpClient, requestOptions, transformer); var errorFeature = httpContext.Features.Get <IProxyErrorFeature>(); if (errorFeature != null) { var error = errorFeature.Error; var exception = errorFeature.Exception; } }); }); }
/// <summary> /// Proxies the incoming request to the destination server, and the response back to the client. /// </summary> /// <param name="context">The HttpContent to proxy from.</param> /// <param name="destinationPrefix">The url prefix for where to proxy the request to.</param> /// <param name="httpClient">The HTTP client used to send the proxy request.</param> public static Task ProxyAsync( this IHttpProxy proxy, HttpContext context, string destinationPrefix, HttpMessageInvoker httpClient) { return(proxy.ProxyAsync(context, destinationPrefix, httpClient, requestOptions: default, HttpTransformer.Default));
/// <summary> /// Proxies the incoming request to the destination server, and the response back to the client. /// </summary> /// <param name="context">The HttpContent to proxy from.</param> /// <param name="destinationPrefix">The url prefix for where to proxy the request to.</param> /// <param name="httpClient">The HTTP client used to send the proxy request.</param> /// <returns>The result of the request proxying to the destination.</returns> public static ValueTask <ProxyError> ProxyAsync( this IHttpProxy proxy, HttpContext context, string destinationPrefix, HttpMessageInvoker httpClient) { return(proxy.ProxyAsync(context, destinationPrefix, httpClient, RequestProxyConfig.Empty, HttpTransformer.Default)); }
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHttpProxy httpProxy) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } var httpClient = new HttpMessageInvoker(new SocketsHttpHandler() { UseProxy = false, AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.None, UseCookies = false }); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.Map("/{**catch-all}", async httpContext => { var mocks = httpContext.RequestServices.GetRequiredService <Mocks>(); var logger = httpContext.RequestServices.GetRequiredService <ILogger <Startup> >(); var match = mocks.Files.Values .SelectMany(v => v.Log.Entries) .Where(e => new Uri(e.Request.Url).AbsolutePath.ToLower() == httpContext.Request.Path.Value.ToLower()) .FirstOrDefault(); // Found match in HAR file mock api use HAR response if (match != null) { logger.LogInformation($"Mocking API {new Uri(match.Request.Url).AbsolutePath}"); foreach (var header in match.Response.Headers) { if (!new[] { "content-length", "content-encoding" }.Any(h => header.Name.ToLower() == h)) { httpContext.Response.Headers.TryAdd(header.Name, header.Value); } } httpContext.Response.StatusCode = match.Response.Status; await httpContext.Response.WriteAsync(match.Response.Content.Text); return; } // No match found, forward request to original api await httpProxy.ProxyAsync(httpContext, _config.GetValue <string>("Api:Url"), httpClient); // Log errors from forwarded request var errorFeature = httpContext.Features.Get <IProxyErrorFeature>(); if (errorFeature != null) { logger.LogError(errorFeature.Exception, $"{errorFeature.Error}"); } }); }); }
/// <summary> /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. /// </summary> public void Configure(IApplicationBuilder app, IHttpProxy httpProxy) { var httpClient = new HttpMessageInvoker(new SocketsHttpHandler() { UseProxy = false, AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.None, UseCookies = false }); var proxyOptions = new RequestProxyOptions() { RequestTimeout = TimeSpan.FromSeconds(100), // Copy all request headers except Host Transforms = new Transforms( copyRequestHeaders: true, requestTransforms: Array.Empty <RequestParametersTransform>(), requestHeaderTransforms: new Dictionary <string, RequestHeaderTransform>() { { HeaderNames.Host, new RequestHeaderValueTransform(string.Empty, append: false) } }, responseHeaderTransforms: new Dictionary <string, ResponseHeaderTransform>(), responseTrailerTransforms: new Dictionary <string, ResponseHeaderTransform>()) }; app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.Map("/{**catch-all}", async httpContext => { await httpProxy.ProxyAsync(httpContext, "https://localhost:10000/", httpClient, proxyOptions); var errorFeature = httpContext.Features.Get <IProxyErrorFeature>(); if (errorFeature != null) { var error = errorFeature.Error; var exception = errorFeature.Exception; } }); }); }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHttpProxy httpProxy) { var httpClient = new HttpMessageInvoker(new SocketsHttpHandler() { UseProxy = false, AllowAutoRedirect = false, AutomaticDecompression = System.Net.DecompressionMethods.None, UseCookies = false }); var transformer = new CustomTransformer(); var requestOptions = new RequestProxyOptions(); app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.Map("/{**catch-all}", async httpContext => { var urls = getUrls(); var teste = httpContext.Request.Headers.FirstOrDefault(c => c.Key == "teste").Value; var url = urls.FirstOrDefault(x => x.key == "1").url; if (teste.Count != 0) { url = urls?.FirstOrDefault(x => x.key == teste.ToString()).url; } await httpProxy.ProxyAsync(httpContext, url, httpClient, requestOptions); var errorFeature = httpContext.GetProxyErrorFeature(); if (errorFeature != null) { var error = errorFeature.Error; var exception = errorFeature.Exception; } }); }); }
/// <summary> /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. /// </summary> public void Configure(IApplicationBuilder app, IHttpProxy httpProxy) { var httpClient = new HttpMessageInvoker(new SocketsHttpHandler() { UseProxy = false, AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.None, UseCookies = false }); var transformBuilder = app.ApplicationServices.GetRequiredService <ITransformBuilder>(); var transformer = transformBuilder.Create(context => { context.AddQueryRemoveKey("param1"); context.AddQueryValue("area", "xx2", false); context.AddOriginalHost(false); }); // or var transformer = new CustomTransformer(); // or var transformer = HttpTransformer.Default; var requestOptions = new RequestProxyOptions { Timeout = TimeSpan.FromSeconds(100) }; app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.Map("/{**catch-all}", async httpContext => { await httpProxy.ProxyAsync(httpContext, "https://example.com", httpClient, requestOptions, transformer); var errorFeature = httpContext.GetProxyErrorFeature(); if (errorFeature != null) { var error = errorFeature.Error; var exception = errorFeature.Exception; } }); }); }
/// <summary> /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. /// </summary> public void Configure(IApplicationBuilder app, IHttpProxy httpProxy) { // Configure our own HttpMessageInvoker for outbound calls for proxy operations var httpClient = new HttpMessageInvoker(new SocketsHttpHandler() { UseProxy = false, AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.None, UseCookies = false }); // Setup our own request transform class var transformer = new CustomTransformer(); // or HttpTransformer.Default; var requestOptions = new RequestProxyOptions { Timeout = TimeSpan.FromSeconds(100) }; app.UseRouting(); app.UseEndpoints(endpoints => { // When using IHttpProxy for direct proxying you are responsible for routing, destination discovery, load balancing, affinity, etc.. // For an alternate example that includes those features see BasicYarpSample. endpoints.Map("/{**catch-all}", async httpContext => { await httpProxy.ProxyAsync(httpContext, "https://example.com", httpClient, requestOptions, transformer); var errorFeature = httpContext.Features.Get <IProxyErrorFeature>(); // Check if the proxy operation was successful if (errorFeature != null) { var error = errorFeature.Error; var exception = errorFeature.Exception; } }); }); }
/// <inheritdoc/> public async Task InvokeAsync(HttpContext context) { Contracts.CheckValue(context, nameof(context)); var aspNetCoreEndpoint = context.GetEndpoint(); if (aspNetCoreEndpoint == null) { throw new ReverseProxyException($"ASP .NET Core Endpoint wasn't set for the current request. This is a coding defect."); } var routeConfig = aspNetCoreEndpoint.Metadata.GetMetadata <RouteConfig>(); if (routeConfig == null) { throw new ReverseProxyException($"ASP .NET Core Endpoint is missing {typeof(RouteConfig).FullName} metadata. This is a coding defect."); } var backend = routeConfig.BackendOrNull; if (backend == null) { throw new ReverseProxyException($"Route has no backend information."); } var dynamicState = backend.DynamicState.Value; if (dynamicState == null) { throw new ReverseProxyException($"Route has no up to date information on its backend '{backend.BackendId}'. Perhaps the backend hasn't been probed yet? This can happen when a new backend is added but isn't ready to serve traffic yet."); } // TODO: Set defaults properly BackendConfig.BackendLoadBalancingOptions loadBalancingOptions = default; var backendConfig = backend.Config.Value; if (backendConfig != null) { loadBalancingOptions = backendConfig.LoadBalancingOptions; } var endpoint = _operationLogger.Execute( "ReverseProxy.PickEndpoint", () => _loadBalancer.PickEndpoint(dynamicState.HealthyEndpoints, dynamicState.AllEndpoints, in loadBalancingOptions)); if (endpoint == null) { throw new ReverseProxyException($"No available endpoints."); } var endpointConfig = endpoint.Config.Value; if (endpointConfig == null) { throw new ReverseProxyException($"Chosen endpoint has no configs set: '{endpoint.EndpointId}'"); } // TODO: support StripPrefix and other url transformations var targetUrl = BuildOutgoingUrl(context, endpointConfig.Address); _logger.LogInformation($"Proxying to {targetUrl}"); var targetUri = new Uri(targetUrl, UriKind.Absolute); using (var shortCts = CancellationTokenSource.CreateLinkedTokenSource(context.RequestAborted)) { // TODO: Configurable timeout, measure from request start, make it unit-testable shortCts.CancelAfter(TimeSpan.FromSeconds(30)); // TODO: Retry against other endpoints try { // TODO: Apply caps backend.ConcurrencyCounter.Increment(); endpoint.ConcurrencyCounter.Increment(); // TODO: Duplex channels should not have a timeout (?), but must react to Proxy force-shutdown signals. var longCancellation = context.RequestAborted; var proxyTelemetryContext = new ProxyTelemetryContext( backendId: backend.BackendId, routeId: routeConfig.Route.RouteId, endpointId: endpoint.EndpointId); await _operationLogger.ExecuteAsync( "ReverseProxy.Proxy", () => _httpProxy.ProxyAsync(context, targetUri, backend.ProxyHttpClientFactory, proxyTelemetryContext, shortCancellation: shortCts.Token, longCancellation: longCancellation)); } finally { endpoint.ConcurrencyCounter.Decrement(); backend.ConcurrencyCounter.Decrement(); } } }
public async Task Invoke( HttpContext context, AuthenticationManager authenticationManager, SingleSignOnHandler singleSignOnHandler, ConfigurationProvider configurationProvider, SecureRandom secureRandom ) { MemorySingletonProxyConfigProvider.Route?route = GetMatchingRoute(context); if (route != null) { bool shouldHandle = true; PathString requestPath = context.Request.Path; if (requestPath.StartsWithSegments("/.well-known/acme-challenge")) { string challenge = ((string)requestPath).Split('/').Last(); if (_acmeChallengeSingleton.Challenges.ContainsKey(challenge)) { shouldHandle = false; } } if (shouldHandle) { configurationProvider.TryGet(AuthServer.Server.GRPC.InstallService.PRIMARY_DOMAIN_KEY, out string primaryDomain); bool isAuthRequest = singleSignOnHandler.IsAuthRequest(context); if (isAuthRequest) { singleSignOnHandler.Handle(context); return; } bool isPublicEndpoint = route.PublicRoutes.Contains(context.Request.Path); if (!isPublicEndpoint) { bool isAuthenticated = authenticationManager.IsAuthenticated(context, out Guid? sessionId); if (!isAuthenticated) { string csrf = secureRandom.GetRandomString(16); context.Response.Cookies.Append("gatekeeper.csrf", csrf); Dictionary <string, string> queryDictionary = new Dictionary <string, string>() { { "id", route.ProxySettingId.ToString() }, { "csrf", csrf }, }; UriBuilder uriBuilder = new UriBuilder(); uriBuilder.Scheme = "https"; uriBuilder.Host = primaryDomain; uriBuilder.Path = "/auth/sso-connect"; uriBuilder.Query = await((new System.Net.Http.FormUrlEncodedContent(queryDictionary)).ReadAsStringAsync()); context.Response.Redirect(uriBuilder.ToString(), false); return; } else { if (sessionId == null) { // This should never happen return; } bool isAuthorized = await authenticationManager.IsAuthorizedAsync((Guid)sessionId, route); if (!isAuthorized) { context.Response.Redirect("https://" + primaryDomain + "/auth/403"); return; } } } Dictionary <string, RequestHeaderTransform> requestHeaderTransforms = new Dictionary <string, RequestHeaderTransform>() { { "X-Forwarded-For", new RequestHeaderValueTransform(context.Connection.RemoteIpAddress.ToString(), append: false) }, { "X-Forwarded-Host", new RequestHeaderValueTransform(route.PublicHostname, append: false) }, { HeaderNames.Host, new RequestHeaderValueTransform(String.Empty, append: false) } }; if (context.Request.Cookies.TryGetValue(AuthenticationManager.AUTH_COOKIE, out string?authCookieValue)) { // FIXME: This is currently also sent as cookie. Remove this and only send it as header. requestHeaderTransforms.Add( "X-Gatekeeper-Jwt-Assertion", new RequestHeaderValueTransform(authCookieValue, append: false) ); } RequestProxyOptions proxyOptions = new RequestProxyOptions() { RequestTimeout = TimeSpan.FromSeconds(100), Transforms = new Transforms( copyRequestHeaders: true, requestTransforms: Array.Empty <RequestParametersTransform>(), requestHeaderTransforms: requestHeaderTransforms, responseHeaderTransforms: new Dictionary <string, ResponseHeaderTransform>(), responseTrailerTransforms: new Dictionary <string, ResponseHeaderTransform>() ) }; await _httpProxy.ProxyAsync(context, route.InternalHostname, _httpClient, proxyOptions); return; } } await _nextMiddleware(context); }
/// <inheritdoc/> public async Task Invoke(HttpContext context) { Contracts.CheckValue(context, nameof(context)); var backend = context.Features.Get <BackendInfo>() ?? throw new InvalidOperationException("Backend unspecified."); var endpoints = context.Features.Get <IAvailableBackendEndpointsFeature>()?.Endpoints ?? throw new InvalidOperationException("The AvailableBackendEndpoints collection was not set."); var routeConfig = context.GetEndpoint()?.Metadata.GetMetadata <RouteConfig>() ?? throw new InvalidOperationException("RouteConfig unspecified."); if (endpoints.Count == 0) { _logger.LogWarning("No available endpoints."); context.Response.StatusCode = 503; return; } var endpoint = endpoints[0]; if (endpoints.Count > 1) { _logger.LogWarning("More than one endpoint available, load balancing may not be configured correctly. Choosing randomly."); endpoint = endpoints[_random.Next(endpoints.Count)]; } var endpointConfig = endpoint.Config.Value; if (endpointConfig == null) { throw new InvalidOperationException($"Chosen endpoint has no configs set: '{endpoint.EndpointId}'"); } // TODO: support StripPrefix and other url transformations var targetUrl = BuildOutgoingUrl(context, endpointConfig.Address); _logger.LogInformation($"Proxying to {targetUrl}"); var targetUri = new Uri(targetUrl, UriKind.Absolute); using (var shortCts = CancellationTokenSource.CreateLinkedTokenSource(context.RequestAborted)) { // TODO: Configurable timeout, measure from request start, make it unit-testable shortCts.CancelAfter(TimeSpan.FromSeconds(30)); // TODO: Retry against other endpoints try { // TODO: Apply caps backend.ConcurrencyCounter.Increment(); endpoint.ConcurrencyCounter.Increment(); // TODO: Duplex channels should not have a timeout (?), but must react to Proxy force-shutdown signals. var longCancellation = context.RequestAborted; var proxyTelemetryContext = new ProxyTelemetryContext( backendId: backend.BackendId, routeId: routeConfig.Route.RouteId, endpointId: endpoint.EndpointId); await _operationLogger.ExecuteAsync( "ReverseProxy.Proxy", () => _httpProxy.ProxyAsync(context, targetUri, backend.ProxyHttpClientFactory, proxyTelemetryContext, shortCancellation: shortCts.Token, longCancellation: longCancellation)); } finally { endpoint.ConcurrencyCounter.Decrement(); backend.ConcurrencyCounter.Decrement(); } } }
public async Task Invoke( HttpContext context, AuthenticationManager authenticationManager, SingleSignOnHandler singleSignOnHandler, ConfigurationProvider configurationProvider, SecureRandom secureRandom ) { MemorySingletonProxyConfigProvider.Route?route = GetMatchingRoute(context); if (route != null) { bool shouldHandle = true; PathString requestPath = context.Request.Path; if (requestPath.StartsWithSegments("/.well-known/acme-challenge")) { string challenge = ((string)requestPath).Split('/').Last(); if (_acmeChallengeSingleton.Challenges.ContainsKey(challenge)) { shouldHandle = false; } } if (shouldHandle) { configurationProvider.TryGet(AuthServer.Server.GRPC.InstallService.PRIMARY_DOMAIN_KEY, out string primaryDomain); bool isAuthRequest = singleSignOnHandler.IsAuthRequest(context); if (isAuthRequest) { singleSignOnHandler.Handle(context); return; } bool isPublicEndpoint = route.PublicRoutes.Contains(context.Request.Path); if (!isPublicEndpoint) { bool isAuthenticated = authenticationManager.IsAuthenticated(context, out Guid? sessionId); if (!isAuthenticated) { string csrf = secureRandom.GetRandomString(16); context.Response.Cookies.Append("gatekeeper.csrf", csrf); Dictionary <string, string> queryDictionary = new Dictionary <string, string>() { { "id", route.ProxySettingId.ToString() }, { "csrf", csrf }, }; UriBuilder uriBuilder = new UriBuilder(); uriBuilder.Scheme = "https"; uriBuilder.Host = primaryDomain; uriBuilder.Path = "/auth/sso-connect"; uriBuilder.Query = await((new System.Net.Http.FormUrlEncodedContent(queryDictionary)).ReadAsStringAsync()); context.Response.Redirect(uriBuilder.ToString(), false); return; } else { if (sessionId == null) { // This should never happen return; } bool isAuthorized = await authenticationManager.IsAuthorizedAsync((Guid)sessionId, route); if (!isAuthorized) { context.Response.Redirect("https://" + primaryDomain + "/auth/403"); return; } } } RequestProxyOptions proxyOptions = new RequestProxyOptions( TimeSpan.FromSeconds(100), null ); await _httpProxy.ProxyAsync( context, route.InternalHostname, _httpClient, proxyOptions, new Gatekeeper.Server.Web.Services.ReverseProxy.Transformer.RequestTransformer(route) ); return; } } await _nextMiddleware(context); }