diff --git a/Directory.Build.props b/Directory.Build.props index 87bcade..79b439c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,4 +1,8 @@ + + true + true + all @@ -6,6 +10,9 @@ all + + all + diff --git a/Directory.Build.targets b/Directory.Build.targets index fe29bd5..fab8080 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -6,6 +6,7 @@ enable enable Henrik Jensen + Copyright 2026 Henrik Jensen https://github.com/henrikhimself/DotNet-ReverseProxy https://github.com/henrikhimself/DotNet-ReverseProxy.git git diff --git a/Directory.Packages.props b/Directory.Packages.props index 9e391bf..e745511 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,6 +11,7 @@ + diff --git a/examples/Aspire.AppHost/Properties/launchSettings.json b/examples/Aspire.AppHost/Properties/launchSettings.json index 733fcff..4d51cff 100644 --- a/examples/Aspire.AppHost/Properties/launchSettings.json +++ b/examples/Aspire.AppHost/Properties/launchSettings.json @@ -7,6 +7,7 @@ "launchBrowser": true, "applicationUrl": "https://localhost:17294", "environmentVariables": { + "ASPNETCORE_PREVENTHOSTINGSTARTUP": "true", "ASPNETCORE_ENVIRONMENT": "Development", "DOTNET_ENVIRONMENT": "Development", "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21134", diff --git a/examples/Aspire.ReverseProxy/Program.cs b/examples/Aspire.ReverseProxy/Program.cs index 1fcac88..bbadb02 100644 --- a/examples/Aspire.ReverseProxy/Program.cs +++ b/examples/Aspire.ReverseProxy/Program.cs @@ -26,6 +26,11 @@ // Use the configured reverse proxy. app.UseReverseProxy(); +// Use the optional reverse proxy API to manage the reverse proxy configuration at runtime. +// Beware that adding new routes/clusters will clear discovered Aspire resources from the configuration. +// See ReverseProxyApi.http for example usage of the API. +app.UseReverseProxyApi("reverse-proxy-api"); + // No further routes are registered. This means that you will get a 404 error status on all requests // that do not match a proxied Aspire resource. await app.RunAsync(); diff --git a/examples/Aspire.ReverseProxy/ReverseProxyApi.http b/examples/Aspire.ReverseProxy/ReverseProxyApi.http new file mode 100644 index 0000000..64405c8 --- /dev/null +++ b/examples/Aspire.ReverseProxy/ReverseProxyApi.http @@ -0,0 +1,61 @@ +# Reverse Proxy API Examples (port can be changed in AppHost) +# The "/reverse-proxy-api" is the base path for the reverse proxy API, which is configured when adding +# the reverse proxy API to the application builder. +@Test_HostAddress = https://localhost:8443 + +# Get clusters +# View the current clusters configured in the reverse proxy. +GET {{Test_HostAddress}}/reverse-proxy-api/cluster + +### +# Add cluster +# Add a cluster that points to the catfact.ninja API. This cluster will be used in the route configuration +# to forward requests to the catfact API. +POST {{Test_HostAddress}}/reverse-proxy-api/cluster +Content-Type: application/json + +{ + "Clusters": [ + { + "ClusterId": "catfact-from-api", + "Destinations": { + "destination1": { + "Address": "https://catfact.ninja/fact" + } + } + } + ], + "AllowOverwrite": true +} + +### +# Get routes +# View the current routes configured in the reverse proxy. After adding the route, you should see +# the new route in the response. +GET {{Test_HostAddress}}/reverse-proxy-api/route + +### +# Add route +# Add a route that matches requests to /catfact and forwards them to the catfact cluster. The route also +# includes a transform to remove the /catfact prefix before forwarding the request to the destination. +POST {{Test_HostAddress}}/reverse-proxy-api/route +Content-Type: application/json + +{ + "Routes": [ + { + "RouteId": "catfact-from-api", + "ClusterId": "catfact-from-api", + "Order": 100, + "Match": { + "Path": "catfact-from-api/{**catch-all}" + }, + "Transforms": [ + { + "PathRemovePrefix": "/catfact-from-api" + } + ] + } + ], + "AllowOverwrite": true +} diff --git a/examples/Aspire.ReverseProxy/appsettings.json b/examples/Aspire.ReverseProxy/appsettings.json index 5149c54..cf8bde5 100644 --- a/examples/Aspire.ReverseProxy/appsettings.json +++ b/examples/Aspire.ReverseProxy/appsettings.json @@ -8,5 +8,29 @@ "AllowedHosts": "*", "SelfSignedCertificate": { "CaFilePath": "{REVERSEPROXY_HOME}" + }, + "ReverseProxy": { + "Routes": { + "catfact-route-from-appsettings": { + "ClusterId": "catfact-cluster-from-appsettings", + "Match": { + "Path": "catfact-from-appsettings/{**catch-all}" + }, + "Transforms": [ + { + "PathRemovePrefix": "/catfact-from-appsettings" + } + ] + } + }, + "Clusters": { + "catfact-cluster-from-appsettings": { + "Destinations": { + "destination1": { + "Address": "https://catfact.ninja/fact" + } + } + } + } } } diff --git a/examples/Aspire.Website/Views/Home/Index.cshtml b/examples/Aspire.Website/Views/Home/Index.cshtml index 8ff6797..3dbb35c 100644 --- a/examples/Aspire.Website/Views/Home/Index.cshtml +++ b/examples/Aspire.Website/Views/Home/Index.cshtml @@ -7,7 +7,7 @@

Website

-

You should see this page when navigating to "https://example-website.local/".

+

You should see this page when navigating to "https://example-website.local:8443/" (port can be changed in AppHost).

This website does not use SSL. Instead, a secure HTTPS connection is provided by the reverse proxy.

In case "example-website.local" cannot be found then you need to map it in your local hosts file.

diff --git a/src/ReverseProxy.Aspire/ReverseProxy.Aspire.csproj b/src/ReverseProxy.Aspire/ReverseProxy.Aspire.csproj index 4ef0813..fa930fd 100644 --- a/src/ReverseProxy.Aspire/ReverseProxy.Aspire.csproj +++ b/src/ReverseProxy.Aspire/ReverseProxy.Aspire.csproj @@ -13,7 +13,7 @@ testing;reverse-proxy;aspire README.md LICENSE - 1.0.0-beta6 + 1.0.0-beta7 diff --git a/src/ReverseProxy/Certificate/CertificateFactory.cs b/src/ReverseProxy/Certificate/CertificateFactory.cs index 552a282..486a3e8 100644 --- a/src/ReverseProxy/Certificate/CertificateFactory.cs +++ b/src/ReverseProxy/Certificate/CertificateFactory.cs @@ -83,11 +83,12 @@ public X509Certificate2 CreateCertificate(AsymmetricAlgorithm key, X509Certifica validTo, serialNumber); - // EphemeralKeySet is not supported on macOS (requires keychain which writes to disk) - // but is supported on Windows, Linux, iOS/tvOS/MacCatalyst, and Android - var keyStorageFlags = OperatingSystem.IsMacOS() - ? X509KeyStorageFlags.Exportable - : X509KeyStorageFlags.Exportable | X509KeyStorageFlags.EphemeralKeySet; + // EphemeralKeySet is only usable on Linux where SslStream is backed by OpenSSL. + // On Windows, Schannel requires the private key to be persisted in a CNG key + // container. On macOS, EphemeralKeySet is not supported (requires keychain). + var keyStorageFlags = OperatingSystem.IsLinux() + ? X509KeyStorageFlags.Exportable | X509KeyStorageFlags.EphemeralKeySet + : X509KeyStorageFlags.Exportable; #pragma warning disable SYSLIB0057 // Type or member is obsolete var pfx = new X509Certificate2(pfxBytes, (string?)null, keyStorageFlags); diff --git a/src/ReverseProxy/ReverseProxy.csproj b/src/ReverseProxy/ReverseProxy.csproj index 86c6c45..1db3112 100644 --- a/src/ReverseProxy/ReverseProxy.csproj +++ b/src/ReverseProxy/ReverseProxy.csproj @@ -13,7 +13,7 @@ testing;reverse-proxy;certificates README.md LICENSE - 1.0.0-beta6 + 1.0.0-beta7