From 499e2af992bfd9a1e9ee305e36c30f1c6c135b64 Mon Sep 17 00:00:00 2001 From: radu-popescu Date: Fri, 16 Jun 2023 15:31:38 +0100 Subject: [PATCH 01/13] YARP initial configuration --- src/OcelotGateway/OcelotGateway.csproj | 1 + src/OcelotGateway/Program.cs | 7 +++++++ src/OcelotGateway/appsettings.json | 24 ++++++++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/src/OcelotGateway/OcelotGateway.csproj b/src/OcelotGateway/OcelotGateway.csproj index 2ed2ee3e..b1107dbd 100644 --- a/src/OcelotGateway/OcelotGateway.csproj +++ b/src/OcelotGateway/OcelotGateway.csproj @@ -23,6 +23,7 @@ + diff --git a/src/OcelotGateway/Program.cs b/src/OcelotGateway/Program.cs index 44844163..1c7122a3 100644 --- a/src/OcelotGateway/Program.cs +++ b/src/OcelotGateway/Program.cs @@ -69,8 +69,14 @@ } }; +builder.Services.AddReverseProxy() + .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); + + var app = builder.Build(); +app.MapReverseProxy(); + app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); @@ -78,6 +84,7 @@ app.UseEndpoints(endpoints => { endpoints.MapControllers(); + //endpoints.MapReverseProxy(); }); await app.UseOcelot(ocelotConfig); diff --git a/src/OcelotGateway/appsettings.json b/src/OcelotGateway/appsettings.json index 64e06d0c..bf44d01c 100644 --- a/src/OcelotGateway/appsettings.json +++ b/src/OcelotGateway/appsettings.json @@ -21,6 +21,30 @@ } } }, + "ReverseProxy": { + "Routes": { + "redisinterface-route": { + "ClusterId": "redisinterface", + "Match": { + "Path": "redisinterface/{**catchall}" + }, + "Transforms": [ + { + "PathPattern": "{**catchall}" + } + ] + } + }, + "Clusters": { + "redisinterface": { + "Destinations": { + "destination1": { + "Address": "http://redisinterface.api/api/v1" + } + } + } + } + }, "AllowedHosts": "*", "ApplicationName": "gateway" } \ No newline at end of file From cd5205fcf4b357eaa939341d75064f7a46406e55 Mon Sep 17 00:00:00 2001 From: radu-popescu Date: Tue, 20 Jun 2023 12:05:38 +0100 Subject: [PATCH 02/13] WIP testing YARP and Ocelot --- .../Controllers/RouteController.cs | 69 +++++++++++++++++++ src/OcelotGateway/ocelot.Development.json | 24 +++++++ 2 files changed, 93 insertions(+) create mode 100644 src/OcelotGateway/Controllers/RouteController.cs diff --git a/src/OcelotGateway/Controllers/RouteController.cs b/src/OcelotGateway/Controllers/RouteController.cs new file mode 100644 index 00000000..ca494d5a --- /dev/null +++ b/src/OcelotGateway/Controllers/RouteController.cs @@ -0,0 +1,69 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +using Yarp.ReverseProxy.Configuration; +using Microsoft.AspNetCore.Routing; + + +namespace Middleware.OcelotGateway.Controllers; + + +[Route("api/v1")] +[ApiController] +public class RouteController : ControllerBase +{ + private readonly InMemoryConfigProvider _inMemoryConfigProvider; + + public string RouteId { get; set; } + + public string ClusterId { get; set; } + + + + public RouteController(IProxyConfigProvider inMemoryConfigProvider) + { + if (inMemoryConfigProvider is InMemoryConfigProvider imcp) + _inMemoryConfigProvider = imcp; + } + + + [HttpGet] + [Route("hello", Name = "Hello")] + public IActionResult Hello() + { + return Ok("hello"); + } + + [HttpPost] + [Route("configure", Name = "CreateDynamicRoute")] + public IActionResult CreateDynamicRoute() + { + var config = _inMemoryConfigProvider.GetConfig(); + + var clusterList = config.Clusters.ToList(); + var routeList = config.Routes.ToList(); + + ClusterConfig clusterCfg = new ClusterConfig + { + ClusterId = "testCluster", + Destinations = new Dictionary + { + { "testdest1", new DestinationConfig{Address = "http://localhost/api/v1/hello"} } + } + }; + RouteConfig routecfg = new RouteConfig() + { + RouteId = "test", + Match = new RouteMatch + { + Path = "my/test/endpoint" + } + }; + clusterList.Add(clusterCfg); + routeList.Add(routecfg); + + _inMemoryConfigProvider.Update(routeList, clusterList); + + return Ok(); + } + +} diff --git a/src/OcelotGateway/ocelot.Development.json b/src/OcelotGateway/ocelot.Development.json index 648a654f..a3761f70 100644 --- a/src/OcelotGateway/ocelot.Development.json +++ b/src/OcelotGateway/ocelot.Development.json @@ -25,6 +25,30 @@ "UpstreamPathTemplate": "/Register", "UpstreamHttpMethod": [ "POST" ] }, + { + "DownstreamPathTemplate": "/api/v1/configure", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "ocelotgateway.api", + "Port": "80" + } + ], + "UpstreamPathTemplate": "/configure", + "UpstreamHttpMethod": [ "POST" ] + }, + { + "DownstreamPathTemplate": "/api/v1/hello", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "ocelotgateway.api", + "Port": "80" + } + ], + "UpstreamPathTemplate": "/hello", + "UpstreamHttpMethod": [ "GET" ] + }, //RedisInterface API { "DownstreamPathTemplate": "/api/v1/{all}", From 0df86dfa1ab4214f6260d52f34d87fa606574605 Mon Sep 17 00:00:00 2001 From: radu-popescu Date: Wed, 21 Jun 2023 13:55:45 +0100 Subject: [PATCH 03/13] wip dynamicroute --- src/OcelotGateway/appsettings.json | 18 ++++++++++++++++++ src/TaskPlanner/Controllers/TestController.cs | 17 +++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 src/TaskPlanner/Controllers/TestController.cs diff --git a/src/OcelotGateway/appsettings.json b/src/OcelotGateway/appsettings.json index bf44d01c..f154e92f 100644 --- a/src/OcelotGateway/appsettings.json +++ b/src/OcelotGateway/appsettings.json @@ -33,6 +33,17 @@ "PathPattern": "{**catchall}" } ] + }, + "taskplanner-route": { + "ClusterId": "taskplanner", + "Match": { + "Path": "taskplanner/{**catchall}" + }, + "Transforms": [ + { + "PathPattern": "{**catchall}" + } + ] } }, "Clusters": { @@ -42,6 +53,13 @@ "Address": "http://redisinterface.api/api/v1" } } + }, + "taskplanner": { + "Destinations": { + "destination1": { + "Address": "http://taskplanner.api/api/v1" + } + } } } }, diff --git a/src/TaskPlanner/Controllers/TestController.cs b/src/TaskPlanner/Controllers/TestController.cs new file mode 100644 index 00000000..4b4a2a14 --- /dev/null +++ b/src/TaskPlanner/Controllers/TestController.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Middleware.TaskPlanner.Controllers; + + +[ApiController] +[Route("api/v1/[controller]")] +public class TestController : ControllerBase +{ + + [HttpGet] + [Route("hello", Name = "Hello")] + public IActionResult Hello() + { + return Ok("hello there"); + } +} From a34afa5b4ccf2074a38ac6484216200984c276a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Bratu=C5=9B?= Date: Wed, 21 Jun 2023 16:04:33 +0200 Subject: [PATCH 04/13] Basic automatic configuration of the paths --- .../Controllers/RouteController.cs | 33 ++++++++----------- src/OcelotGateway/Program.cs | 32 ++++++++---------- src/OcelotGateway/ocelot.Development.json | 2 +- src/TaskPlanner/Controllers/TestController.cs | 4 +-- 4 files changed, 30 insertions(+), 41 deletions(-) diff --git a/src/OcelotGateway/Controllers/RouteController.cs b/src/OcelotGateway/Controllers/RouteController.cs index ca494d5a..9d5e43fb 100644 --- a/src/OcelotGateway/Controllers/RouteController.cs +++ b/src/OcelotGateway/Controllers/RouteController.cs @@ -1,16 +1,12 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using Yarp.ReverseProxy.Configuration; -using Microsoft.AspNetCore.Routing; - namespace Middleware.OcelotGateway.Controllers; - -[Route("api/v1")] +[Route("api/v1/[controller]")] [ApiController] public class RouteController : ControllerBase -{ +{ private readonly InMemoryConfigProvider _inMemoryConfigProvider; public string RouteId { get; set; } @@ -18,7 +14,6 @@ public class RouteController : ControllerBase public string ClusterId { get; set; } - public RouteController(IProxyConfigProvider inMemoryConfigProvider) { if (inMemoryConfigProvider is InMemoryConfigProvider imcp) @@ -41,22 +36,23 @@ public IActionResult CreateDynamicRoute() var clusterList = config.Clusters.ToList(); var routeList = config.Routes.ToList(); - - ClusterConfig clusterCfg = new ClusterConfig + + var clusterCfg = new ClusterConfig { ClusterId = "testCluster", - Destinations = new Dictionary - { - { "testdest1", new DestinationConfig{Address = "http://localhost/api/v1/hello"} } + Destinations = new Dictionary + { + { "testdest1", new DestinationConfig { Address = "http://taskplanner.api/api/v1/test" } } } }; - RouteConfig routecfg = new RouteConfig() + var routecfg = new RouteConfig { RouteId = "test", - Match = new RouteMatch + Match = new() { - Path = "my/test/endpoint" - } + Path = "hello" + }, + ClusterId = "testCluster" // }; clusterList.Add(clusterCfg); routeList.Add(routecfg); @@ -65,5 +61,4 @@ public IActionResult CreateDynamicRoute() return Ok(); } - -} +} \ No newline at end of file diff --git a/src/OcelotGateway/Program.cs b/src/OcelotGateway/Program.cs index 1c7122a3..c71ae89d 100644 --- a/src/OcelotGateway/Program.cs +++ b/src/OcelotGateway/Program.cs @@ -1,22 +1,17 @@ -using System.Configuration; using System.Text; +using IdentityModel; using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using Middleware.Common.Config; using Middleware.Common.ExtensionMethods; using Middleware.DataAccess.ExtensionMethods; using Middleware.DataAccess.Repositories; using Middleware.DataAccess.Repositories.Abstract; +using Middleware.OcelotGateway.Services; using Ocelot.Cache.CacheManager; using Ocelot.DependencyInjection; using Ocelot.Middleware; -using Ocelot.Values; -using Microsoft.IdentityModel; -using static IdentityModel.ClaimComparer; -using Microsoft.IdentityModel.Claims; -using Ocelot.Authentication.Middleware; -using Middleware.OcelotGateway.Services; +using Yarp.ReverseProxy.Configuration; var builder = WebApplication.CreateBuilder(args); @@ -30,25 +25,25 @@ }); builder.Services.AddOcelot() - .AddCacheManager(settings => settings.WithDictionaryHandle()); + .AddCacheManager(settings => settings.WithDictionaryHandle()); builder.Services.DecorateClaimAuthoriser(); var config = builder.Configuration.GetSection(JwtConfig.ConfigName).Get(); builder.Services.Configure(builder.Configuration.GetSection(JwtConfig.ConfigName)); builder.Services.AddAuthentication(options => -{ - options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; -} + { + options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + } ).AddJwtBearer("Bearer", options => { - options.TokenValidationParameters = new TokenValidationParameters() + options.TokenValidationParameters = new() { IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.Key)), - NameClaimType = IdentityModel.JwtClaimTypes.Name, - RoleClaimType = IdentityModel.JwtClaimTypes.Role, + NameClaimType = JwtClaimTypes.Name, + RoleClaimType = JwtClaimTypes.Role, ValidAudience = "redisinterfaceAudience", ValidIssuer = "redisinterfaceIssuer", ValidateIssuerSigningKey = true, @@ -70,7 +65,8 @@ }; builder.Services.AddReverseProxy() - .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); + .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")) + .LoadFromMemory(new List(), new List()); var app = builder.Build(); diff --git a/src/OcelotGateway/ocelot.Development.json b/src/OcelotGateway/ocelot.Development.json index a3761f70..19f588e0 100644 --- a/src/OcelotGateway/ocelot.Development.json +++ b/src/OcelotGateway/ocelot.Development.json @@ -26,7 +26,7 @@ "UpstreamHttpMethod": [ "POST" ] }, { - "DownstreamPathTemplate": "/api/v1/configure", + "DownstreamPathTemplate": "/api/v1/route/configure", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { diff --git a/src/TaskPlanner/Controllers/TestController.cs b/src/TaskPlanner/Controllers/TestController.cs index 4b4a2a14..bc83599c 100644 --- a/src/TaskPlanner/Controllers/TestController.cs +++ b/src/TaskPlanner/Controllers/TestController.cs @@ -2,16 +2,14 @@ namespace Middleware.TaskPlanner.Controllers; - [ApiController] [Route("api/v1/[controller]")] public class TestController : ControllerBase { - [HttpGet] [Route("hello", Name = "Hello")] public IActionResult Hello() { return Ok("hello there"); } -} +} \ No newline at end of file From 14f2faacb86b65057b78a19f18468206d7e1991b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Bratu=C5=9B?= Date: Fri, 23 Jun 2023 14:19:23 +0200 Subject: [PATCH 05/13] Addted transforms to route the request to the specific endpoint --- src/OcelotGateway/Controllers/RouteController.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/OcelotGateway/Controllers/RouteController.cs b/src/OcelotGateway/Controllers/RouteController.cs index 9d5e43fb..7160abec 100644 --- a/src/OcelotGateway/Controllers/RouteController.cs +++ b/src/OcelotGateway/Controllers/RouteController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Yarp.ReverseProxy.Configuration; +using Yarp.ReverseProxy.Transforms; namespace Middleware.OcelotGateway.Controllers; @@ -9,11 +10,6 @@ public class RouteController : ControllerBase { private readonly InMemoryConfigProvider _inMemoryConfigProvider; - public string RouteId { get; set; } - - public string ClusterId { get; set; } - - public RouteController(IProxyConfigProvider inMemoryConfigProvider) { if (inMemoryConfigProvider is InMemoryConfigProvider imcp) @@ -42,10 +38,10 @@ public IActionResult CreateDynamicRoute() ClusterId = "testCluster", Destinations = new Dictionary { - { "testdest1", new DestinationConfig { Address = "http://taskplanner.api/api/v1/test" } } + { "testdest1", new DestinationConfig { Address = "http://taskplanner.api/api/v1/test/hello" } } } }; - var routecfg = new RouteConfig + var routeCfg = new RouteConfig { RouteId = "test", Match = new() @@ -54,8 +50,11 @@ public IActionResult CreateDynamicRoute() }, ClusterId = "testCluster" // }; + // transforms allow us to change the path that is requested like below to replace direct forwarding + routeCfg = routeCfg.WithTransformPathSet("/"); + clusterList.Add(clusterCfg); - routeList.Add(routecfg); + routeList.Add(routeCfg); _inMemoryConfigProvider.Update(routeList, clusterList); From 47b3d263ed6d5ec0c0c22f62c60532c9501da159 Mon Sep 17 00:00:00 2001 From: radu-popescu Date: Mon, 26 Jun 2023 13:02:41 +0100 Subject: [PATCH 06/13] Tested dynamic route creation for websocket connection --- .../Controllers/RouteController.cs | 4 ++-- src/OcelotGateway/appsettings.json | 18 ++++++++++++++++++ src/TaskPlanner/Controllers/TestController.cs | 9 +++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/OcelotGateway/Controllers/RouteController.cs b/src/OcelotGateway/Controllers/RouteController.cs index 7160abec..0b7e3cde 100644 --- a/src/OcelotGateway/Controllers/RouteController.cs +++ b/src/OcelotGateway/Controllers/RouteController.cs @@ -38,7 +38,7 @@ public IActionResult CreateDynamicRoute() ClusterId = "testCluster", Destinations = new Dictionary { - { "testdest1", new DestinationConfig { Address = "http://taskplanner.api/api/v1/test/hello" } } + { "testdest1", new DestinationConfig { Address = "http://websocketserver" } } } }; var routeCfg = new RouteConfig @@ -51,7 +51,7 @@ public IActionResult CreateDynamicRoute() ClusterId = "testCluster" // }; // transforms allow us to change the path that is requested like below to replace direct forwarding - routeCfg = routeCfg.WithTransformPathSet("/"); + routeCfg = routeCfg.WithTransformPathSet("/send"); clusterList.Add(clusterCfg); routeList.Add(routeCfg); diff --git a/src/OcelotGateway/appsettings.json b/src/OcelotGateway/appsettings.json index f154e92f..d5be81a7 100644 --- a/src/OcelotGateway/appsettings.json +++ b/src/OcelotGateway/appsettings.json @@ -44,6 +44,17 @@ "PathPattern": "{**catchall}" } ] + }, + "websocket-route": { + "ClusterId": "websocket", + "Match": { + "Path": "/send" + }, + "Transforms": [ + { + "PathPattern": "/send" + } + ] } }, "Clusters": { @@ -60,6 +71,13 @@ "Address": "http://taskplanner.api/api/v1" } } + }, + "websocket": { + "Destinations": { + "destination1": { + "Address": "http://WebSocketServer" + } + } } } }, diff --git a/src/TaskPlanner/Controllers/TestController.cs b/src/TaskPlanner/Controllers/TestController.cs index bc83599c..df12538f 100644 --- a/src/TaskPlanner/Controllers/TestController.cs +++ b/src/TaskPlanner/Controllers/TestController.cs @@ -12,4 +12,13 @@ public IActionResult Hello() { return Ok("hello there"); } + + + + [HttpPost] + [Route("send", Name = "send")] + public IActionResult Send() + { + return Ok("websocket test"); + } } \ No newline at end of file From 5ef7402c342f4978adde80dad600306739f60244 Mon Sep 17 00:00:00 2001 From: radu-popescu Date: Wed, 28 Jun 2023 15:29:49 +0100 Subject: [PATCH 07/13] Created GatewayConfigurationService Created new contracts for adding and deleting NetApp routes, Configured gateway for RabbitMQ consumers Added Documentation(WIP) --- docs/Administrator/Gateway/readme.md | 7 ++ src/Common/Helpers/QueueHelpers.cs | 31 +++++++ .../GatewayAddNetAppEntryMessage.cs | 13 +++ .../GatewayDeleteNetAppEntryMessage.cs | 17 ++++ .../Controllers/RouteController.cs | 63 -------------- .../ServiceCollectionExtensions.cs | 84 ++++++++++++++++++ .../Handlers/CreateDynamicRouteConsumer.cs | 35 ++++++++ .../Handlers/DeleteDynamicRouteConsumer.cs | 34 ++++++++ src/OcelotGateway/OcelotGateway.csproj | 2 + src/OcelotGateway/Program.cs | 1 + .../Services/GatewayConfigurationService.cs | 86 +++++++++++++++++++ src/OcelotGateway/appsettings.json | 40 +-------- src/TaskPlanner/Controllers/TestController.cs | 24 ------ 13 files changed, 312 insertions(+), 125 deletions(-) create mode 100644 docs/Administrator/Gateway/readme.md create mode 100644 src/Common/MessageContracts/GatewayAddNetAppEntryMessage.cs create mode 100644 src/Common/MessageContracts/GatewayDeleteNetAppEntryMessage.cs delete mode 100644 src/OcelotGateway/Controllers/RouteController.cs create mode 100644 src/OcelotGateway/ExtensionMethods/ServiceCollectionExtensions.cs create mode 100644 src/OcelotGateway/Handlers/CreateDynamicRouteConsumer.cs create mode 100644 src/OcelotGateway/Handlers/DeleteDynamicRouteConsumer.cs create mode 100644 src/OcelotGateway/Services/GatewayConfigurationService.cs delete mode 100644 src/TaskPlanner/Controllers/TestController.cs diff --git a/docs/Administrator/Gateway/readme.md b/docs/Administrator/Gateway/readme.md new file mode 100644 index 00000000..2aa38fe2 --- /dev/null +++ b/docs/Administrator/Gateway/readme.md @@ -0,0 +1,7 @@ +# Dynamic Gateway configuration + +The `Middleware` system offers the possibility to dynamically configure the `Gateway` in order to route the traffic through a websocket to a specific `Network Application` using the `YARP` reverse proxy functionalities. This allows enabling `RBAC` to the `Network Applications` and expose everything behind the `Gateway` using `SSL` termination. Moreover, this will also remove the need to enable the authentication on the `Network Application` level. + +## How it works + +The dynamic `Gateway` configuration is accomplished through standard `REST` request and `WebSockets`, in fact the whole mechanism is trigger through `RabbitMQ` queueing system for both creating the new route and deleting the route once the `Network Application` has finished conducting the task. diff --git a/src/Common/Helpers/QueueHelpers.cs b/src/Common/Helpers/QueueHelpers.cs index c89b2438..46d8da21 100644 --- a/src/Common/Helpers/QueueHelpers.cs +++ b/src/Common/Helpers/QueueHelpers.cs @@ -54,6 +54,37 @@ public static string ConstructSwitchoverDeleteActionQueueName(string organizatio return GetQueueName(organization, instanceName, "switchover-action-delete"); } + /// + /// Constructs the name of the queue that will be used to create a new YARP dynamic route + /// + /// + /// + /// + /// When parameters are not specified or contain empty or whitespace string + public static string ConstructGatewayAddNetAppEntryMessageQueueName(string organization, string instanceName) + { + if (string.IsNullOrWhiteSpace(organization)) throw new ArgumentNullException(nameof(organization)); + if (string.IsNullOrWhiteSpace(organization)) throw new ArgumentNullException(nameof(instanceName)); + + return GetQueueName(organization, instanceName, "gateway-add-entry"); + } + + + /// + /// Constructs the name of the queue that will be used to delete the YARP dynamic route + /// + /// + /// + /// + /// When parameters are not specified or contain empty or whitespace string + public static string ConstructGatewayDeleteNetAppEntryMessageQueueName(string organization, string instanceName) + { + if (string.IsNullOrWhiteSpace(organization)) throw new ArgumentNullException(nameof(organization)); + if (string.IsNullOrWhiteSpace(organization)) throw new ArgumentNullException(nameof(instanceName)); + + return GetQueueName(organization, instanceName, "gateway-delete-entry"); + } + /// /// Constructs the switchover deployment queue name for this specific Middleware instance /// diff --git a/src/Common/MessageContracts/GatewayAddNetAppEntryMessage.cs b/src/Common/MessageContracts/GatewayAddNetAppEntryMessage.cs new file mode 100644 index 00000000..fd97c462 --- /dev/null +++ b/src/Common/MessageContracts/GatewayAddNetAppEntryMessage.cs @@ -0,0 +1,13 @@ +namespace Middleware.Common.MessageContracts; +public record GatewayAddNetAppEntryMessage : Message +{ + public Guid ActionPlanId { get; set; } + + public Guid ServiceInstanceId { get; set; } + + public string NetAppName { get; set; } + + public string DeploymentLocation { get; init; } + + +} diff --git a/src/Common/MessageContracts/GatewayDeleteNetAppEntryMessage.cs b/src/Common/MessageContracts/GatewayDeleteNetAppEntryMessage.cs new file mode 100644 index 00000000..0846a987 --- /dev/null +++ b/src/Common/MessageContracts/GatewayDeleteNetAppEntryMessage.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Middleware.Common.MessageContracts; +public record GatewayDeleteNetAppEntryMessage : Message +{ + public Guid ActionPlanId { get; set; } + + public Guid ServiceInstanceId { get; set; } + + public string NetAppName { get; set; } + + public string DeploymentLocation { get; init; } +} diff --git a/src/OcelotGateway/Controllers/RouteController.cs b/src/OcelotGateway/Controllers/RouteController.cs deleted file mode 100644 index 0b7e3cde..00000000 --- a/src/OcelotGateway/Controllers/RouteController.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Yarp.ReverseProxy.Configuration; -using Yarp.ReverseProxy.Transforms; - -namespace Middleware.OcelotGateway.Controllers; - -[Route("api/v1/[controller]")] -[ApiController] -public class RouteController : ControllerBase -{ - private readonly InMemoryConfigProvider _inMemoryConfigProvider; - - public RouteController(IProxyConfigProvider inMemoryConfigProvider) - { - if (inMemoryConfigProvider is InMemoryConfigProvider imcp) - _inMemoryConfigProvider = imcp; - } - - - [HttpGet] - [Route("hello", Name = "Hello")] - public IActionResult Hello() - { - return Ok("hello"); - } - - [HttpPost] - [Route("configure", Name = "CreateDynamicRoute")] - public IActionResult CreateDynamicRoute() - { - var config = _inMemoryConfigProvider.GetConfig(); - - var clusterList = config.Clusters.ToList(); - var routeList = config.Routes.ToList(); - - var clusterCfg = new ClusterConfig - { - ClusterId = "testCluster", - Destinations = new Dictionary - { - { "testdest1", new DestinationConfig { Address = "http://websocketserver" } } - } - }; - var routeCfg = new RouteConfig - { - RouteId = "test", - Match = new() - { - Path = "hello" - }, - ClusterId = "testCluster" // - }; - // transforms allow us to change the path that is requested like below to replace direct forwarding - routeCfg = routeCfg.WithTransformPathSet("/send"); - - clusterList.Add(clusterCfg); - routeList.Add(routeCfg); - - _inMemoryConfigProvider.Update(routeList, clusterList); - - return Ok(); - } -} \ No newline at end of file diff --git a/src/OcelotGateway/ExtensionMethods/ServiceCollectionExtensions.cs b/src/OcelotGateway/ExtensionMethods/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..4232d4c0 --- /dev/null +++ b/src/OcelotGateway/ExtensionMethods/ServiceCollectionExtensions.cs @@ -0,0 +1,84 @@ +using MassTransit; +using Middleware.Common.Config; +using Middleware.Common.Helpers; +using Middleware.Common.MessageContracts; +using Middleware.OcelotGateway.Handlers; +using Middleware.OcelotGateway.Services; +using RabbitMQ.Client; + +namespace Middleware.OcelotGateway.ExtensionMethods; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection RegisterRabbitMqConsumers(this IServiceCollection services, + RabbitMqConfig mqConfig, MiddlewareConfig mwConfig) + { + var routingKey = QueueHelpers.ConstructRoutingKey(mwConfig.InstanceName, mwConfig.InstanceType); + + services.AddMassTransit(x => + { + services.AddScoped(); + x.AddConsumer(); + + services.AddScoped(); + x.AddConsumer(); + + x.UsingRabbitMq((busRegistrationContext, mqBusFactoryConfigurator) => + { + mqBusFactoryConfigurator.Host(mqConfig.Address, "/", hostConfig => + { + hostConfig.Username(mqConfig.User); + hostConfig.Password(mqConfig.Pass); + }); + + mqBusFactoryConfigurator.ReceiveEndpoint( + QueueHelpers.ConstructGatewayAddNetAppEntryMessageQueueName(mwConfig.Organization, mwConfig.InstanceName), + ec => + { + ec.ConfigureConsumeTopology = false; + ec.Bind(nameof(GatewayAddNetAppEntryMessage), b => + { + b.ExchangeType = ExchangeType.Direct; + b.RoutingKey = routingKey; + }); + ec.ConfigureConsumer(busRegistrationContext); + }); + + mqBusFactoryConfigurator.ReceiveEndpoint( + QueueHelpers.ConstructGatewayDeleteNetAppEntryMessageQueueName(mwConfig.Organization, mwConfig.InstanceName), + ec => + { + ec.ConfigureConsumeTopology = false; + ec.Bind(nameof(GatewayDeleteNetAppEntryMessage), b => + { + b.ExchangeType = ExchangeType.Direct; + b.RoutingKey = routingKey; + }); + ec.ConfigureConsumer(busRegistrationContext); + }); + + mqBusFactoryConfigurator.ConfigureEndpoints(busRegistrationContext); + }); + + + }); + + + services.AddOptions() + .Configure(options => + { + // if specified, waits until the bus is started before + // returning from IHostedService.StartAsync + // default is false + options.WaitUntilStarted = true; + + // if specified, limits the wait time when starting the bus + options.StartTimeout = TimeSpan.FromSeconds(10); + + // if specified, limits the wait time when stopping the bus + options.StopTimeout = TimeSpan.FromSeconds(30); + }); + + return services; + } +} diff --git a/src/OcelotGateway/Handlers/CreateDynamicRouteConsumer.cs b/src/OcelotGateway/Handlers/CreateDynamicRouteConsumer.cs new file mode 100644 index 00000000..c5d42830 --- /dev/null +++ b/src/OcelotGateway/Handlers/CreateDynamicRouteConsumer.cs @@ -0,0 +1,35 @@ +using Middleware.Common.MessageContracts; +using MassTransit; +using Middleware.OcelotGateway.Services; +using Middleware.Common.Config; +using System.Numerics; + +namespace Middleware.OcelotGateway.Handlers; + +public class CreateDynamicRouteConsumer : IConsumer +{ + private readonly ILogger _logger; + + private GatewayConfigurationService _gatewayConfigurationService; + + private readonly IConfiguration _cfg; + + public CreateDynamicRouteConsumer(ILogger logger, GatewayConfigurationService gatewayConfigurationService, IConfiguration cfg) + { + _logger = logger; + _gatewayConfigurationService = gatewayConfigurationService; + _cfg = cfg; + + } + + + public Task Consume(ConsumeContext context) + { + _logger.LogInformation("Started processing GatewayAddNetAppEntryMessage"); + var mwconfig = _cfg.GetSection(MiddlewareConfig.ConfigName).Get(); + var msg = context.Message; + _logger.LogDebug("Location {0}-{1} received message request addressed to {2}", mwconfig.InstanceName, mwconfig.InstanceType, msg.DeploymentLocation); + _gatewayConfigurationService.CreateDynamicRoute(msg); + return Task.CompletedTask; + } +} diff --git a/src/OcelotGateway/Handlers/DeleteDynamicRouteConsumer.cs b/src/OcelotGateway/Handlers/DeleteDynamicRouteConsumer.cs new file mode 100644 index 00000000..d02e42b8 --- /dev/null +++ b/src/OcelotGateway/Handlers/DeleteDynamicRouteConsumer.cs @@ -0,0 +1,34 @@ +using MassTransit; +using Middleware.Common.Config; +using Middleware.Common.MessageContracts; +using Middleware.OcelotGateway.Services; + +namespace Middleware.OcelotGateway.Handlers; + +public class DeleteDynamicRouteConsumer : IConsumer +{ + private readonly ILogger _logger; + + private GatewayConfigurationService _gatewayConfigurationService; + + private readonly IConfiguration _cfg; + + public DeleteDynamicRouteConsumer(ILogger logger, GatewayConfigurationService gatewayConfigurationService, IConfiguration cfg) + { + _logger = logger; + _gatewayConfigurationService = gatewayConfigurationService; + _cfg = cfg; + } + + + + public Task Consume(ConsumeContext context) + { + _logger.LogInformation("Started processing GatewayDeleteNetAppEntryMessage"); + var mwconfig = _cfg.GetSection(MiddlewareConfig.ConfigName).Get(); + var msg = context.Message; + _logger.LogDebug("Location {0}-{1} received message request addressed to {2}", mwconfig.InstanceName, mwconfig.InstanceType, msg.DeploymentLocation); + _gatewayConfigurationService.DeleteDynamicRoute(msg); + return Task.CompletedTask; + } +} diff --git a/src/OcelotGateway/OcelotGateway.csproj b/src/OcelotGateway/OcelotGateway.csproj index b1107dbd..060f856e 100644 --- a/src/OcelotGateway/OcelotGateway.csproj +++ b/src/OcelotGateway/OcelotGateway.csproj @@ -14,6 +14,8 @@ + + diff --git a/src/OcelotGateway/Program.cs b/src/OcelotGateway/Program.cs index c71ae89d..bf223a81 100644 --- a/src/OcelotGateway/Program.cs +++ b/src/OcelotGateway/Program.cs @@ -55,6 +55,7 @@ builder.RegisterRedis(); builder.Services.AddScoped(); +builder.Services.AddScoped(); var ocelotConfig = new OcelotPipelineConfiguration { diff --git a/src/OcelotGateway/Services/GatewayConfigurationService.cs b/src/OcelotGateway/Services/GatewayConfigurationService.cs new file mode 100644 index 00000000..d9b69135 --- /dev/null +++ b/src/OcelotGateway/Services/GatewayConfigurationService.cs @@ -0,0 +1,86 @@ +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Middleware.Common.MessageContracts; +using Yarp.ReverseProxy.Configuration; +using Yarp.ReverseProxy.Transforms; + +namespace Middleware.OcelotGateway.Services; + +public class GatewayConfigurationService +{ + private readonly InMemoryConfigProvider _inMemoryConfigProvider; + + public GatewayConfigurationService(IProxyConfigProvider inMemoryConfigProvider) + { + if (inMemoryConfigProvider is InMemoryConfigProvider imcp) + _inMemoryConfigProvider = imcp; + } + + public void CreateDynamicRoute(GatewayAddNetAppEntryMessage msg) + { + var config = _inMemoryConfigProvider.GetConfig(); + + var clusterList = config.Clusters.ToList(); + var routeList = config.Routes.ToList(); + + var clusterCfg = new ClusterConfig + { + ClusterId = msg.NetAppName + "-Cluster", + Destinations = new Dictionary + { + { "destination1", new DestinationConfig { Address = $"http://{msg.NetAppName}" } } + } + }; + var routeCfg = new RouteConfig + { + RouteId = msg.NetAppName + "-Route", + Match = new() + { + Path = msg.NetAppName + }, + ClusterId = clusterCfg.ClusterId // + }; + // transforms allow us to change the path that is requested like below to replace direct forwarding + routeCfg = routeCfg.WithTransformPathSet("/"); + + clusterList.Add(clusterCfg); + routeList.Add(routeCfg); + + _inMemoryConfigProvider.Update(routeList, clusterList); + + } + + public void DeleteDynamicRoute(GatewayDeleteNetAppEntryMessage msg) + { + var config = _inMemoryConfigProvider.GetConfig(); + + var clusterList = config.Clusters.ToList(); + var routeList = config.Routes.ToList(); + + var matchRouteToDelete = msg.NetAppName + "-Route"; + RouteConfig routeToDelete = null; + foreach (var route in routeList) + { + + if (route.RouteId == matchRouteToDelete) + { + routeToDelete = route; + } + } + routeList.Remove(routeToDelete); + + var matchClusterToDelete = msg.NetAppName + "-Cluster"; + ClusterConfig clusterToDelete = null; + foreach (var cluster in clusterList) + { + + if (cluster.ClusterId == matchClusterToDelete) + { + clusterToDelete = cluster; + } + } + clusterList.Remove(clusterToDelete); + + _inMemoryConfigProvider.Update(routeList, clusterList); + } +} diff --git a/src/OcelotGateway/appsettings.json b/src/OcelotGateway/appsettings.json index d5be81a7..d199a86a 100644 --- a/src/OcelotGateway/appsettings.json +++ b/src/OcelotGateway/appsettings.json @@ -23,30 +23,8 @@ }, "ReverseProxy": { "Routes": { - "redisinterface-route": { - "ClusterId": "redisinterface", - "Match": { - "Path": "redisinterface/{**catchall}" - }, - "Transforms": [ - { - "PathPattern": "{**catchall}" - } - ] - }, - "taskplanner-route": { - "ClusterId": "taskplanner", - "Match": { - "Path": "taskplanner/{**catchall}" - }, - "Transforms": [ - { - "PathPattern": "{**catchall}" - } - ] - }, "websocket-route": { - "ClusterId": "websocket", + "ClusterId": "websocket-cluster", "Match": { "Path": "/send" }, @@ -58,21 +36,7 @@ } }, "Clusters": { - "redisinterface": { - "Destinations": { - "destination1": { - "Address": "http://redisinterface.api/api/v1" - } - } - }, - "taskplanner": { - "Destinations": { - "destination1": { - "Address": "http://taskplanner.api/api/v1" - } - } - }, - "websocket": { + "websocket-cluster": { "Destinations": { "destination1": { "Address": "http://WebSocketServer" diff --git a/src/TaskPlanner/Controllers/TestController.cs b/src/TaskPlanner/Controllers/TestController.cs deleted file mode 100644 index df12538f..00000000 --- a/src/TaskPlanner/Controllers/TestController.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace Middleware.TaskPlanner.Controllers; - -[ApiController] -[Route("api/v1/[controller]")] -public class TestController : ControllerBase -{ - [HttpGet] - [Route("hello", Name = "Hello")] - public IActionResult Hello() - { - return Ok("hello there"); - } - - - - [HttpPost] - [Route("send", Name = "send")] - public IActionResult Send() - { - return Ok("websocket test"); - } -} \ No newline at end of file From 38f46590fe8a17372ca482f0b02318cea5370780 Mon Sep 17 00:00:00 2001 From: radu-popescu Date: Thu, 29 Jun 2023 13:01:10 +0100 Subject: [PATCH 08/13] Updated Administrator/Gateway/readme.md --- docs/Administrator/Gateway/readme.md | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/Administrator/Gateway/readme.md b/docs/Administrator/Gateway/readme.md index 2aa38fe2..85d12ddc 100644 --- a/docs/Administrator/Gateway/readme.md +++ b/docs/Administrator/Gateway/readme.md @@ -1,7 +1,21 @@ -# Dynamic Gateway configuration +# Gateway -The `Middleware` system offers the possibility to dynamically configure the `Gateway` in order to route the traffic through a websocket to a specific `Network Application` using the `YARP` reverse proxy functionalities. This allows enabling `RBAC` to the `Network Applications` and expose everything behind the `Gateway` using `SSL` termination. Moreover, this will also remove the need to enable the authentication on the `Network Application` level. +The `Middleware` system includes diverse microservices. When a `robot/user` has to consume multiple microservices, setting up distinct endpoints for each microservice and managing them separately can be challenging. A solution for handling such tasks is placing a gateway in front of the microservices. In this way, the `robot/user` can communicate with the various microservices by using a single entry-point. + +## What technologies have been used and for what purposes + +The gateway for the `5G-ERA Middleware` has been implemented using the `Ocelot` and `YARP` reverse proxy server. `Ocelot` is an open-source application, which is lightweight, fast, scalable, cross-platform, and most importantly it was specifically designed for `.NET Core` Microservices Architecture. `Ocelot` is responsible for handling the incoming requests, by routing the traffic to the desired endpoints, retrieving the information from the backend, and relaying it back to the `robots/users`. On the other hand, `YARP` is as well an open source application built on top of the `.NET` environment, being designed to be easily customizable and tweaked. `YARP` will accommodate the functionalities for dynamic gateway configuration and websocket for robotic communication purposes. + +# Gateway functionalities -## How it works +While both technologies serve the common purpose for routing the incoming requests, their distinct functionalities are described below. -The dynamic `Gateway` configuration is accomplished through standard `REST` request and `WebSockets`, in fact the whole mechanism is trigger through `RabbitMQ` queueing system for both creating the new route and deleting the route once the `Network Application` has finished conducting the task. +## Ocelot authentication/authorization and RBAC + +The `Ocelot` also fulfils the role of an `Identity Service`, through a `REST API` implementation. The user credentials are stored in a safe manner using a `HASH + SALT` model, following current best practices and using cryptographically strong libraries from `.NET` environment. The passwords are SALT-ed and hashed using the `PBKDF2` algorithm with `HMACSHA256` hashing. Authentication is achieved by reconstructing the salted hash from the credentials provided at log-in and comparing the result with the salted hash stored in the system at registration. +Furthermore, the security of the `Middleware` systems is enhanced using the `JWT Bearer Token` standard and `Role Based Access Control` (`RBAC`) model. More precisely when the `robots/users` are successfully authenticated they will receive a `JWT Token`. In terms of authorization, the `robots/users` will have to pass the generated token along with the request in order to be able to perform operations on the data, through the endpoints that are implemented in the `Middleware` system. Finally, the security of the system is boosted with the implementation of the `RBAC` model that will provide restricted access to the `Middleware` system based on the role of the user. + +## YARP dynamic gateway configuration and WebSockets + +The `Middleware` system offers the possibility to dynamically configure the `Gateway` in order to route the traffic through a websocket to a specific `Network Application` using the `YARP` reverse proxy functionalities. This allows enabling `RBAC` to the `Network Applications` and expose everything behind the `Gateway` using `SSL` termination. Moreover, this will also remove the need to enable the authentication on the `Network Application` level. +The dynamic `Gateway` configuration is accomplished through standard `REST` request and `WebSockets`, in fact the whole mechanism is trigger through `RabbitMQ` queueing system for both creating the new route and deleting the route once the `Network Application` has finished conducting the task. \ No newline at end of file From 71eea7d889337a1d83a5ff71c9043cfce953b279 Mon Sep 17 00:00:00 2001 From: radu-popescu Date: Thu, 29 Jun 2023 14:09:00 +0100 Subject: [PATCH 09/13] removed unnecessary routes --- src/OcelotGateway/ocelot.Development.json | 24 ----------------------- 1 file changed, 24 deletions(-) diff --git a/src/OcelotGateway/ocelot.Development.json b/src/OcelotGateway/ocelot.Development.json index 19f588e0..648a654f 100644 --- a/src/OcelotGateway/ocelot.Development.json +++ b/src/OcelotGateway/ocelot.Development.json @@ -25,30 +25,6 @@ "UpstreamPathTemplate": "/Register", "UpstreamHttpMethod": [ "POST" ] }, - { - "DownstreamPathTemplate": "/api/v1/route/configure", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "ocelotgateway.api", - "Port": "80" - } - ], - "UpstreamPathTemplate": "/configure", - "UpstreamHttpMethod": [ "POST" ] - }, - { - "DownstreamPathTemplate": "/api/v1/hello", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "ocelotgateway.api", - "Port": "80" - } - ], - "UpstreamPathTemplate": "/hello", - "UpstreamHttpMethod": [ "GET" ] - }, //RedisInterface API { "DownstreamPathTemplate": "/api/v1/{all}", From 785b8879c540ffb66110bf984356c39d846ea956 Mon Sep 17 00:00:00 2001 From: Radu Popescu <93524768+radu-popescu@users.noreply.github.com> Date: Thu, 29 Jun 2023 15:08:24 +0100 Subject: [PATCH 10/13] Update docs/Administrator/Gateway/readme.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit accept changes Co-authored-by: Bartosz Bratuś --- docs/Administrator/Gateway/readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Administrator/Gateway/readme.md b/docs/Administrator/Gateway/readme.md index 85d12ddc..6d1ea653 100644 --- a/docs/Administrator/Gateway/readme.md +++ b/docs/Administrator/Gateway/readme.md @@ -18,4 +18,5 @@ Furthermore, the security of the `Middleware` systems is enhanced using the `JWT ## YARP dynamic gateway configuration and WebSockets The `Middleware` system offers the possibility to dynamically configure the `Gateway` in order to route the traffic through a websocket to a specific `Network Application` using the `YARP` reverse proxy functionalities. This allows enabling `RBAC` to the `Network Applications` and expose everything behind the `Gateway` using `SSL` termination. Moreover, this will also remove the need to enable the authentication on the `Network Application` level. -The dynamic `Gateway` configuration is accomplished through standard `REST` request and `WebSockets`, in fact the whole mechanism is trigger through `RabbitMQ` queueing system for both creating the new route and deleting the route once the `Network Application` has finished conducting the task. \ No newline at end of file +The dynamic `Gateway` configuration is accomplished through standard `REST` requests and `WebSockets`, in fact, the whole mechanism is triggered through `RabbitMQ` queueing system for both creating the new route and deleting the Route once the `Network Application` has finished conducting the task. +The messages to open or close the route are sent from the Orchestrator after the desired `Network Application` is deployed or terminated. \ No newline at end of file From 21c4d54e9f18baa86bc4c5c603b90190b7be48c3 Mon Sep 17 00:00:00 2001 From: radu-popescu Date: Thu, 29 Jun 2023 15:11:40 +0100 Subject: [PATCH 11/13] Removed "ReverseProxy" section from appsettings.json --- src/OcelotGateway/appsettings.json | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/OcelotGateway/appsettings.json b/src/OcelotGateway/appsettings.json index d199a86a..64e06d0c 100644 --- a/src/OcelotGateway/appsettings.json +++ b/src/OcelotGateway/appsettings.json @@ -21,30 +21,6 @@ } } }, - "ReverseProxy": { - "Routes": { - "websocket-route": { - "ClusterId": "websocket-cluster", - "Match": { - "Path": "/send" - }, - "Transforms": [ - { - "PathPattern": "/send" - } - ] - } - }, - "Clusters": { - "websocket-cluster": { - "Destinations": { - "destination1": { - "Address": "http://WebSocketServer" - } - } - } - } - }, "AllowedHosts": "*", "ApplicationName": "gateway" } \ No newline at end of file From 7cb861f535b22e38a8e2b598f23622172d30c2ce Mon Sep 17 00:00:00 2001 From: Radu Popescu <93524768+radu-popescu@users.noreply.github.com> Date: Thu, 29 Jun 2023 15:13:27 +0100 Subject: [PATCH 12/13] Update src/OcelotGateway/Program.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartosz Bratuś --- src/OcelotGateway/Program.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/OcelotGateway/Program.cs b/src/OcelotGateway/Program.cs index bf223a81..2951c682 100644 --- a/src/OcelotGateway/Program.cs +++ b/src/OcelotGateway/Program.cs @@ -66,7 +66,6 @@ }; builder.Services.AddReverseProxy() - .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")) .LoadFromMemory(new List(), new List()); From d470455b935b2789692d73d8e26c00fbe4c07d1d Mon Sep 17 00:00:00 2001 From: Radu Popescu <93524768+radu-popescu@users.noreply.github.com> Date: Thu, 29 Jun 2023 15:13:46 +0100 Subject: [PATCH 13/13] Update src/OcelotGateway/Services/GatewayConfigurationService.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartosz Bratuś --- src/OcelotGateway/Services/GatewayConfigurationService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OcelotGateway/Services/GatewayConfigurationService.cs b/src/OcelotGateway/Services/GatewayConfigurationService.cs index d9b69135..7f0d3cda 100644 --- a/src/OcelotGateway/Services/GatewayConfigurationService.cs +++ b/src/OcelotGateway/Services/GatewayConfigurationService.cs @@ -41,7 +41,7 @@ public void CreateDynamicRoute(GatewayAddNetAppEntryMessage msg) ClusterId = clusterCfg.ClusterId // }; // transforms allow us to change the path that is requested like below to replace direct forwarding - routeCfg = routeCfg.WithTransformPathSet("/"); + routeCfg = routeCfg.WithTransformPathRemovePrefix($"/{msg.NetAppName}"); clusterList.Add(clusterCfg); routeList.Add(routeCfg);