public async Task <object> Dispatch(IanvsContext ianvsContext) { HttpRequestMessage downstreamMsg = ianvsContext.BackendMessage.Message; try { using HttpClient client = new HttpClient( new HttpClientHandler() { AllowAutoRedirect = false } ); _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Sending request to {downstreamMsg.Method} {downstreamMsg.RequestUri.ToString()}"); HttpResponseMessage backendResponse = await client.SendAsync(downstreamMsg); _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Received response from {downstreamMsg.Method} {downstreamMsg.RequestUri.ToString()}"); return(backendResponse); } catch (Exception e) { // Ok, something wrong happened - call the SWAT team _logger.LogError($"{Environment.MachineName} {ianvsContext.RequestId} Error occured sending request to {downstreamMsg.Method} {downstreamMsg.RequestUri.ToString()}"); _logger.LogError($"{Environment.MachineName} {ianvsContext.RequestId} Error: {e.ToString()}"); HttpResponseMessage error = new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError); return(error); } }
/// <summary> /// Promote request context variables to be used in variable swap operations /// </summary> /// <param name="httpContext">The incoming HTTP context</param> /// <param name="ianvsContext">The Ianvs processing context</param> public void PromoteVariables(HttpContext httpContext, IanvsContext ianvsContext) { ianvsContext.Variables = new Dictionary <string, string>(); if (httpContext.Request.Body != null && httpContext.Request.Body.CanRead) { ianvsContext.IncomingRequest = httpContext.Request.ReadAsStringAsync().Result; ianvsContext.Variables.TryAdd("{request.body}", ianvsContext.IncomingRequest); ianvsContext.Variables.TryAdd("{request.header.content-Type}", httpContext.Request.ContentType); if (httpContext.Request.ContentLength.HasValue) { ianvsContext.Variables.TryAdd("{request.header.content-Length}", httpContext.Request.ContentLength.Value.ToString()); } else { ianvsContext.Variables.TryAdd("{request.header.content-Length}", ianvsContext.IncomingRequest.Length.ToString()); } } foreach (var header in httpContext.Request.Headers) { ianvsContext.Variables.TryAdd($"{{request.header.{header.Key}}}", header.Value.ToString()); } foreach (var query in httpContext.Request.Query) { ianvsContext.Variables.TryAdd($"{{request.query.{query.Key}}}", query.Value.ToString()); } ianvsContext.Variables.TryAdd("{request.received-at}", ianvsContext.ReceivedAt.ToUnixTimeMilliseconds().ToString()); ianvsContext.Variables.TryAdd("{request.id}", ianvsContext.RequestId); ianvsContext.Variables.TryAdd("{request.track-id}", ianvsContext.TrackId); ianvsContext.Variables.TryAdd("{url}", ianvsContext.Url); ianvsContext.Variables.TryAdd("{method}", ianvsContext.Method); ianvsContext.Variables.TryAdd("{protocol}", ianvsContext.Protocol); }
/// <summary> /// Retireves the token value from the incoming request /// </summary> /// <param name="httpContext">The incoming request HTTP context</param> /// <param name="ianvsContext">The incoming request Ianvs context</param> /// <returns>The token value</returns> private string GetTokenValue(HttpContext httpContext, IanvsContext ianvsContext) { string tokenIn = ianvsContext.SecurityScheme.In ?? "header"; return(tokenIn switch { "header" => GetTokenFromHeader(httpContext), "cookie" => GetTokenFromCookie(httpContext, ianvsContext.SecurityScheme.BearerFormat), _ => GetTokenFromHeader(httpContext) });
public bool CanAuthenticate(HttpContext httpContext, IanvsContext ianvsContext) { // Get token value, only header and cookie are supported string tokenValue = GetTokenValue(httpContext, ianvsContext); if (!string.IsNullOrWhiteSpace(tokenValue)) { return(true); } return(false); }
public async Task InvokeAsync(HttpContext httpContext, IanvsContext ianvsContext) { // TODO: Implement Transformation // https://github.com/onyx-ws/ianvs/issues/10 ianvsContext.BackendMessage = new BackendMessage(); await _next(httpContext); ianvsContext.Response = ianvsContext.BackendResponse.Content; }
public async Task InvokeAsync(HttpContext context, IanvsContext ianvsContext, IIanvsConfigurationStore ianvsConfiguration, LoadBalancerFactory loadBalancerFactory) { // TODO: Implement Load Balancing // WIP - https://github.com/onyx-ws/ianvs/issues/5 // Support for different load balancing modes - Random/Round Robin/etc. _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} finding load balancer"); string loadBalancerMode = ianvsContext.MatchedOperation.LoadBalancerMethod; if (string.IsNullOrWhiteSpace(loadBalancerMode)) { loadBalancerMode = ianvsContext.MatchedEndpoint.LoadBalancerMethod; } if (string.IsNullOrWhiteSpace(loadBalancerMode)) { loadBalancerMode = ianvsConfiguration.LoadBalancerMethod; } ILoadBalancer loadBalancer = loadBalancerFactory.GetLoadBalancer(loadBalancerMode); _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} {loadBalancerMode} load balancer found"); // Operation level servers take priority if (!(ianvsContext.MatchedOperation.Servers?.Count == 0)) { _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Selecting server out of {ianvsContext.MatchedOperation.Servers.Count} server(s)"); ianvsContext.TargetServer = await loadBalancer.Next(ianvsContext.MatchedOperation.Servers); _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Server {ianvsContext.TargetServer.Url} selected"); } // If not operation level servers then use Endpoint level servers else if (!(ianvsContext.MatchedEndpoint.Servers?.Count == 0)) { _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Selecting server out of {ianvsContext.MatchedEndpoint.Servers.Count} server(s)"); ianvsContext.TargetServer = await loadBalancer.Next(ianvsContext.MatchedEndpoint.Servers); _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Server {ianvsContext.TargetServer.Url} selected"); } // Else use global servers defined else { _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Selecting server out of {ianvsConfiguration.Servers.Count} server(s)"); ianvsContext.TargetServer = await loadBalancer.Next(ianvsConfiguration.Servers); _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Server {ianvsContext.TargetServer.Url} selected"); } ianvsContext.TargetUrl = ianvsContext.TargetServer.Url + (ianvsContext.MatchedEndpoint.Url == "/" ? "" : ianvsContext.MatchedEndpoint.Url); await _next(context); }
/// <summary> /// Prepare HTTP request message /// </summary> /// <param name="ianvsContext">Ianvs processing context</param> /// <returns>The HTTP request message to send to the backend</returns> public object PrepareRequest(IanvsContext ianvsContext, ISpan egressSpan) { string targetUrl = $"{ianvsContext.TargetUrl}?"; foreach (var parameter in ianvsContext.MatchedOperation.Parameters) { parameter.To ??= parameter.In; if (parameter.To == "query") { if (ianvsContext.Variables.TryGetValue($"{{request.query.{parameter.Name}}}", out string value)) { targetUrl += $"{parameter.Name}={value}&"; } else if (parameter.Default != null) { targetUrl += $"{parameter.Name}={parameter.Default}&"; } } } // Remove last character - either ? or & targetUrl = targetUrl.Remove(targetUrl.Length - 1, 1); HttpRequestMessage downstreamMsg = new HttpRequestMessage { Method = new HttpMethod(ianvsContext.MatchedOperation.Method), RequestUri = new Uri(targetUrl) }; if (!string.IsNullOrWhiteSpace(ianvsContext.IncomingRequest)) { downstreamMsg.Content = new StringContent( ianvsContext.IncomingRequest, Encoding.UTF8, ianvsContext.Variables.GetValueOrDefault("{request.header.content-Type}", "application/json") ); } if (egressSpan.Context.IsValid) { ianvsContext.Tracer.TextFormat.Inject( egressSpan.Context, downstreamMsg.Headers, (headers, name, value) => headers.Add(name, value)); } return(downstreamMsg); }
public async Task InvokeAsync(HttpContext httpContext, IanvsContext ianvsContext, DispatcherFactory dispatcherFactory, Tracer tracer) { var egressSpan = tracer.StartSpan("ianvs-egress"); // TODO: Implement Protocol Translation - e.g. REST to gRPC // https://github.com/onyx-ws/ianvs/issues/11 // Get the dispatcher matching for the backend ianvsContext.Dispatcher = dispatcherFactory.GetDispatcher(ianvsContext.MatchedEndpoint.Protocol); egressSpan.AddEvent("Preparing Request"); // Prepare backend request message ianvsContext.BackendMessage.Message = ianvsContext.Dispatcher.PrepareRequest(ianvsContext, egressSpan); egressSpan.AddEvent("Request prepared"); Stopwatch backendTimer = new Stopwatch(); backendTimer.Start(); egressSpan.AddEvent("Sending backend request"); _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Sending request to backend service {ianvsContext.MatchedOperation.OperationId} {ianvsContext.MatchedOperation.Method} {ianvsContext.MatchedEndpoint.Url} {ianvsContext.TargetServer.Url}"); Task <object> backendResponse = ianvsContext.Dispatcher.Dispatch(ianvsContext); _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Waiting for backend service response"); backendResponse.Wait(); backendTimer.Stop(); egressSpan.AddEvent("Backend service response received"); ianvsContext.BackendResponse = new BackendMessage() { Message = backendResponse.Result }; ianvsContext.Dispatcher.ProcessResponse(ianvsContext); _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Backend service response received in {backendTimer.ElapsedMilliseconds}ms {ianvsContext.StatusCode}"); egressSpan.End(); // Egress middleware is the last in the Ianvs processing chain // No further calls made to _next }
/// <summary> /// Gets the OpenId config for the authentication authority /// </summary> /// <param name="ianvsContext">The incoming request Ianvs processing context</param> /// <returns>The OpenId config for the authentication authority</returns> private static async Task <OpenIdConnectConfiguration> GetIssuerConfig(IanvsContext ianvsContext) { OpenIdConnectConfiguration openIdConfig = new OpenIdConnectConfiguration(); if (!string.IsNullOrWhiteSpace(ianvsContext.SecurityScheme.OpenIdConnectUrl)) { IConfigurationManager <OpenIdConnectConfiguration> configurationManager = new ConfigurationManager <OpenIdConnectConfiguration>( ianvsContext.SecurityScheme.OpenIdConnectUrl, new OpenIdConnectConfigurationRetriever(), new HttpDocumentRetriever() { RequireHttps = false } ); openIdConfig = await configurationManager.GetConfigurationAsync(CancellationToken.None); } // If user defined x-issuer then use this value instead of discovered value openIdConfig.Issuer = ianvsContext.SecurityScheme.Issuer ?? openIdConfig.Issuer; return(openIdConfig); }
/// <summary> /// Process backend HTTP response message into common Ianvs data /// </summary> /// <param name="ianvsContext">The Ianvs processing context</param> public async void ProcessResponse(IanvsContext ianvsContext) { HttpResponseMessage backendHttpResponse = ianvsContext.BackendResponse.Message as HttpResponseMessage; ianvsContext.StatusCode = (int)backendHttpResponse.StatusCode; if (backendHttpResponse.Content != null) { ianvsContext.BackendResponse.Content = await backendHttpResponse.ReadBodyAsStringAsync(); ianvsContext.BackendResponse.Headers = backendHttpResponse.Headers.ToDictionary( header => header.Key, header => header.Value ) .Concat( backendHttpResponse.Content.Headers.ToDictionary( header => header.Key, header => header.Value )) .ToDictionary( header => header.Key, header => header.Value ); } else { ianvsContext.BackendResponse.Content = string.Empty; ianvsContext.BackendResponse.Headers = backendHttpResponse.Headers.ToDictionary( header => header.Key, header => header.Value ); } }
public async Task InvokeAsync(HttpContext httpContext, IanvsContext ianvsContext, Tracer tracer) { using (tracer.StartActiveSpan("ianvs-ingress", SpanKind.Server, out ISpan ianvsSpan)) { ianvsContext.TraceSpan = ianvsSpan; ianvsContext.Tracer = tracer; try { // TODO: Implement Ingress operations Stopwatch ianvsTimer = new Stopwatch(); ianvsTimer.Start(); ianvsContext.ReceivedAt = DateTimeOffset.UtcNow; ianvsContext.RequestId = httpContext.TraceIdentifier; ianvsContext.Url = httpContext.Request.GetDisplayUrl(); ianvsContext.Method = httpContext.Request.Method; ianvsContext.Protocol = httpContext.Request.Protocol; _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Request starting {ianvsContext.Protocol} {ianvsContext.Method} {ianvsContext.Url}"); _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Request recieved at {ianvsContext.ReceivedAt}"); if (httpContext.Request.Headers.TryGetValue("x-ianvs-trackid", out StringValues trackId)) { ianvsContext.TrackId = trackId.ToString(); } else { ianvsContext.TrackId = Guid.NewGuid().ToString(); } PromoteVariables(httpContext, ianvsContext); // When response is starting capture and log time Ianvs took to process httpContext.Response.OnStarting(async() => { ianvsContext.ProcessingTime = ianvsTimer.ElapsedMilliseconds; ianvsContext.ResponseSentAt = DateTimeOffset.UtcNow; _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Response being sent at {ianvsContext.ResponseSentAt}."); _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Processing took {ianvsContext.ProcessingTime}ms"); }); // When response is completed (i.e. Received by client) capture and log time it took httpContext.Response.OnCompleted(async() => { ianvsTimer.Stop(); ianvsContext.ProcessingCompletedAt = DateTimeOffset.UtcNow; _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Response received by client at {ianvsContext.ProcessingCompletedAt}"); _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Request finished in {ianvsTimer.ElapsedMilliseconds}ms {httpContext.Response.StatusCode}"); }); await _next(httpContext); httpContext.Response.StatusCode = ianvsContext.StatusCode; await httpContext.Response.WriteAsync(ianvsContext.Response); } catch (Exception e) { // Ok, something wrong happened - call the SWAT team _logger.LogError($"{Environment.MachineName} {ianvsContext.RequestId} Error occured processing request"); _logger.LogError($"{Environment.MachineName} {ianvsContext.RequestId} Error: {e.ToString()}"); ianvsContext.ResponseSentAt = DateTimeOffset.UtcNow; ianvsContext.StatusCode = 500; httpContext.Response.StatusCode = ianvsContext.StatusCode; await httpContext.Response.WriteAsync(string.Empty); } } }
private static TokenValidationParameters GetValidationParameters(OpenIdConnectConfiguration issureConfig, IanvsContext ianvsContext) { return(new TokenValidationParameters { ValidIssuer = issureConfig.Issuer, ValidAudiences = ianvsContext.SecurityScheme.Audiences, IssuerSigningKeys = issureConfig.SigningKeys }); }
/// <summary> /// Authenticates the incoming request using JWT /// </summary> /// <param name="httpContext">The incoming request HTTP context</param> /// <param name="ianvsContext">The incoming request Ianvs context</param> /// <returns>The Ianvs Authentication result</returns> public async Task <AuthenticationResult> Authenticate(HttpContext httpContext, IanvsContext ianvsContext) { _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Starting JWT Validation"); // Get token value, only header and cookie are supported string tokenValue = GetTokenValue(httpContext, ianvsContext); if (string.IsNullOrWhiteSpace(tokenValue)) { _logger.LogError($"{Environment.MachineName} {ianvsContext.RequestId} No token found"); return(new AuthenticationResult() { Authenticated = false, Error = "Token missing" }); } _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Preparing validation parameters"); // Get issuer configuration values OpenIdConnectConfiguration issureConfig = await GetIssuerConfig(ianvsContext); TokenValidationParameters validationParameters = GetValidationParameters(issureConfig, ianvsContext); ClaimsPrincipal principal; try { _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Validating request token"); JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler(); principal = handler.ValidateToken(tokenValue, validationParameters, out SecurityToken validatedToken); } catch (Exception e) { _logger.LogError($"{Environment.MachineName} {ianvsContext.RequestId} Token validation failed. {e.Message}"); return(new AuthenticationResult() { Authenticated = false, Error = e.Message }); } _logger.LogInformation($"{Environment.MachineName} {ianvsContext.RequestId} Token validated successfully"); return(new AuthenticationResult() { Authenticated = true, Principal = principal }); }
public async Task InvokeAsync(HttpContext context, IanvsContext ianvsContext) { await _next(context); }