diff --git a/DysonNetwork.Gateway/Controllers/WellKnownController.cs b/DysonNetwork.Gateway/Controllers/WellKnownController.cs index 7e94544..811ec68 100644 --- a/DysonNetwork.Gateway/Controllers/WellKnownController.cs +++ b/DysonNetwork.Gateway/Controllers/WellKnownController.cs @@ -1,10 +1,12 @@ using Microsoft.AspNetCore.Mvc; +using Yarp.ReverseProxy.Configuration; namespace DysonNetwork.Gateway.Controllers; [ApiController] [Route("/.well-known")] -public class WellKnownController(IConfiguration configuration) : ControllerBase +public class WellKnownController(IConfiguration configuration, IProxyConfigProvider proxyConfigProvider) + : ControllerBase { [HttpGet("domains")] public IActionResult GetDomainMappings() @@ -13,4 +15,33 @@ public class WellKnownController(IConfiguration configuration) : ControllerBase .ToDictionary(x => x.Key, x => x.Value); return Ok(domainMappings); } + + [HttpGet("routes")] + public IActionResult GetProxyRules() + { + var config = proxyConfigProvider.GetConfig(); + var rules = config.Routes.Select(r => new + { + r.RouteId, + r.ClusterId, + Match = new + { + r.Match.Path, + Hosts = r.Match.Hosts != null ? string.Join(", ", r.Match.Hosts) : null + }, + Transforms = r.Transforms?.Select(t => t.Select(kv => $"{kv.Key}: {kv.Value}").ToList()) + }).ToList(); + + var clusters = config.Clusters.Select(c => new + { + c.ClusterId, + Destinations = c.Destinations?.Select(d => new + { + d.Key, + d.Value.Address + }).ToList() + }).ToList(); + + return Ok(new { Rules = rules, Clusters = clusters }); + } } \ No newline at end of file diff --git a/DysonNetwork.Gateway/Program.cs b/DysonNetwork.Gateway/Program.cs index df34149..3b97bbb 100644 --- a/DysonNetwork.Gateway/Program.cs +++ b/DysonNetwork.Gateway/Program.cs @@ -8,8 +8,7 @@ builder.Services.AddControllers(); var app = builder.Build(); -// app.UseHttpsRedirection(); - +app.MapControllers(); app.MapReverseProxy(); app.Run(); diff --git a/DysonNetwork.Gateway/EtcdProxyConfigProvider.cs b/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs similarity index 51% rename from DysonNetwork.Gateway/EtcdProxyConfigProvider.cs rename to DysonNetwork.Gateway/RegistryProxyConfigProvider.cs index 5984aea..f816ca6 100644 --- a/DysonNetwork.Gateway/EtcdProxyConfigProvider.cs +++ b/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs @@ -4,15 +4,15 @@ using Yarp.ReverseProxy.Configuration; namespace DysonNetwork.Gateway; -public class EtcdProxyConfigProvider : IProxyConfigProvider, IDisposable +public class RegistryProxyConfigProvider : IProxyConfigProvider, IDisposable { private readonly IEtcdClient _etcdClient; private readonly IConfiguration _configuration; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly CancellationTokenSource _watchCts = new(); private CancellationTokenSource _cts = new(); - public EtcdProxyConfigProvider(IEtcdClient etcdClient, IConfiguration configuration, ILogger logger) + public RegistryProxyConfigProvider(IEtcdClient etcdClient, IConfiguration configuration, ILogger logger) { _etcdClient = etcdClient; _configuration = configuration; @@ -34,20 +34,78 @@ public class EtcdProxyConfigProvider : IProxyConfigProvider, IDisposable var response = _etcdClient.GetRange("/services/"); var kvs = response.Kvs; + var serviceMap = kvs.ToDictionary( + kv => Encoding.UTF8.GetString(kv.Key.ToByteArray()).Replace("/services/", ""), + kv => Encoding.UTF8.GetString(kv.Value.ToByteArray()) + ); + var clusters = new List(); var routes = new List(); var domainMappings = _configuration.GetSection("DomainMappings").GetChildren() .ToDictionary(x => x.Key, x => x.Value); + var pathAliases = _configuration.GetSection("PathAliases").GetChildren() + .ToDictionary(x => x.Key, x => x.Value); + + var directRoutes = _configuration.GetSection("DirectRoutes").Get>() ?? new List(); + _logger.LogInformation("Indexing {ServiceCount} services from Etcd.", kvs.Count); - foreach (var kv in kvs) - { - var serviceName = Encoding.UTF8.GetString(kv.Key.ToByteArray()).Replace("/services/", ""); - var serviceUrl = Encoding.UTF8.GetString(kv.Value.ToByteArray()); + var gatewayServiceName = _configuration["Service:Name"]; - _logger.LogInformation(" Service: {ServiceName}, URL: {ServiceUrl}", serviceName, serviceUrl); + // Add direct routes + foreach (var directRoute in directRoutes) + { + if (serviceMap.TryGetValue(directRoute.Service, out var serviceUrl)) + { + var cluster = new ClusterConfig + { + ClusterId = directRoute.Service, + Destinations = new Dictionary + { + { "destination1", new DestinationConfig { Address = serviceUrl } } + } + }; + clusters.Add(cluster); + + var route = new RouteConfig + { + RouteId = $"direct-{directRoute.Service}-{directRoute.Path.Replace("/", "-")}", + ClusterId = directRoute.Service, + Match = new RouteMatch { Path = directRoute.Path } + }; + routes.Add(route); + _logger.LogInformation(" Added Direct Route: {Path} -> {Service}", directRoute.Path, directRoute.Service); + } + else + { + _logger.LogWarning(" Direct route service {Service} not found in Etcd.", directRoute.Service); + } + } + + foreach (var serviceName in serviceMap.Keys) + { + if (serviceName == gatewayServiceName) + { + _logger.LogInformation("Skipping gateway service: {ServiceName}", serviceName); + continue; + } + + var serviceUrl = serviceMap[serviceName]; + + // Determine the path alias + string pathAlias; + if (pathAliases.TryGetValue(serviceName, out var alias)) + { + pathAlias = alias; + } + else + { + pathAlias = serviceName.Split('.').Last().ToLowerInvariant(); + } + + _logger.LogInformation(" Service: {ServiceName}, URL: {ServiceUrl}, Path Alias: {PathAlias}", serviceName, serviceUrl, pathAlias); var cluster = new ClusterConfig { @@ -81,7 +139,11 @@ public class EtcdProxyConfigProvider : IProxyConfigProvider, IDisposable { RouteId = $"{serviceName}-path", ClusterId = serviceName, - Match = new RouteMatch { Path = $"/{serviceName}/{{**catch-all}}" } + Match = new RouteMatch { Path = $"/{pathAlias}/{{**catch-all}}" }, + Transforms = new List> + { + new Dictionary { { "PathRemovePrefix", $"/{pathAlias}" } } + } }; routes.Add(pathRoute); _logger.LogInformation(" Added Path-based Route: {Path}", pathRoute.Match.Path); @@ -98,6 +160,12 @@ public class EtcdProxyConfigProvider : IProxyConfigProvider, IDisposable public Microsoft.Extensions.Primitives.IChangeToken ChangeToken { get; } = new Microsoft.Extensions.Primitives.CancellationChangeToken(CancellationToken.None); } + private class DirectRouteConfig + { + public string Path { get; set; } + public string Service { get; set; } + } + public void Dispose() { _cts.Cancel(); diff --git a/DysonNetwork.Gateway/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Gateway/Startup/ServiceCollectionExtensions.cs index ba1f598..28a2153 100644 --- a/DysonNetwork.Gateway/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Gateway/Startup/ServiceCollectionExtensions.cs @@ -9,7 +9,7 @@ public static class ServiceCollectionExtensions { services.AddReverseProxy(); services.AddRegistryService(configuration); - services.AddSingleton(); + services.AddSingleton(); return services; } diff --git a/DysonNetwork.Gateway/appsettings.json b/DysonNetwork.Gateway/appsettings.json index 0e7c15b..6d0486f 100644 --- a/DysonNetwork.Gateway/appsettings.json +++ b/DysonNetwork.Gateway/appsettings.json @@ -21,5 +21,23 @@ "DysonNetwork.Drive": "drive.solsynth.dev", "DysonNetwork.Pusher": "push.solsynth.dev", "DysonNetwork.Sphere": "sphere.solsynth.dev" - } + }, + "PathAliases": { + "DysonNetwork.Pass": "id", + "DysonNetwork.Drive": "drive" + }, + "DirectRoutes": [ + { + "Path": "/ws", + "Service": "DysonNetwork.Pusher" + }, + { + "Path": "/.well-known/openid-configuration", + "Service": "DysonNetwork.Pass" + }, + { + "Path": "/.well-known/jwks", + "Service": "DysonNetwork.Pass" + } + ] }