SwizlyPeasy.Gateway is a simple API gateway based on YARP Reverse Proxy. This gateway should support OIDC authentication and service discovery with Consul.
MIT License
SwizlyPeasy.Gateway is a simple API gateway based on YARP Reverse Proxy. This gateway should support OIDC authentication and service discovery with Consul.
Currently, YARP is the most advanced reverse proxy in .NET. The version 1 of this proxy was introduced by Microsoft at the end of 2021 (on my birthday...). Until now, I have used various API Gateways, including Ocelot. I wondered if it was possible to graft the minimal functionalities to propose an API Gateway based on YARP. https://devblogs.microsoft.com/dotnet/announcing-yarp-1-0-release/
about consul: https://developer.hashicorp.com/consul/download
The solution proposed here, "SwizlyPeasy.Gateway", is for now a PoC (Proof of Concept), but who knows, the results are promising so far, maybe I will be able to propose something more robust later on.
On this topic, thanks to Layla Porter: https://tanzu.vmware.com/developer/blog/build-api-gateway-csharp-yarp-eureka/ The approach is nevertheless different as I propose a modification of the routes on the fly using the KV store, and the authentication with OIDC is also implemented.
This is a demonstration, and it is clear that several security measures need to be taken in a production environment... But the demo has been improved. It can now be started with docker-compose, no need to download consul, it is provided as a docker container. -> You should make sure that docker is installed...
But first, clone the repo, easy git clone https://github.com/ggnaegi/SwizlyPeasy.Gateway.git
Open the solution in visual studio and start with docker-compose
Or open a powershell in the cloned folder...
And execute the following command:
docker compose -f docker-compose.yml -f docker-compose.override.yml up
You could try the two routes configured in YARP.
https://localhost:8001/api/v1/demo/weather
https://localhost:8001/api/v1/demo/weather-with-authorization
You must be authenticated to access these two paths, the system will ask you to authenticate. You should see the duende IdentityServer demo page.
Now you can choose between bob, alice, or use your google account. For the demo purpose you should please use alice...
And with weather-with-authorization...
You're obviously not Bob...
Open the Consul administration page, which should be accessible at http://localhost:8500
, and choose Key/Value, there you should find the routes configuration...
Now, let's change the configuration and wait for the gateway configuration refresh - by default every 120 seconds, but this parameter can be modified in ServiceDiscovery parameters -.
magic...
You should have a look at the SwizlyPeasy.Gateway.API project.
The setup is straight forward
program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSwizlyPeasyGateway(builder.Configuration);
var app = builder.Build();
app.UseSwizlyPeasyGateway();
app.Run();
routes.config.json
file (please have a look at the SwizlyPeasy.Gateway.API demo project).{
"Routes": {
"route1": {
"ClusterId": "DemoAPI",
"AuthorizationPolicy": "oidc",
"Match": {
"Path": "/api/v1/demo/weather"
},
"Transforms": [
{
"RequestHeader": "Accept-Language",
"Set": "de-CH"
}
]
},
"route2": {
"ClusterId": "DemoAPI",
"AuthorizationPolicy": "oidc",
"RateLimiterPolicy": "swizly1",
"Match": {
"Path": "/api/v1/demo/weather-with-authorization"
},
"Transforms": [
{
"RequestHeader": "Accept-Language",
"Set": "de-CH"
}
]
}
}
}
{
"AuthRedirectionConfig": {
"MainUrl": "/",
"IdpLogoutUrl": "https://demo.duendesoftware.com/Account/Logout/LoggedOut"
},
"OidcConfig": {
"RefreshThresholdMinutes": 1,
"Origins": [],
"Authority": "https://demo.duendesoftware.com/",
"CallbackUri": "/signin-oidc",
"ClientId": "interactive.confidential.short",
"ClientSecret": "secret",
"RedirectUri": "",
"Scopes": [ "openid", "profile", "email", "offline_access" ],
"DisableOidc": true
},
"ServiceDiscovery": {
"Scheme": "http",
"RefreshIntervalInSeconds": 20,
"LoadBalancingPolicy": "Random",
"KeyValueStoreKey": "SwizlyPeasy.Gateway",
"ServiceDiscoveryAddress": "http://localhost:8500"
},
"ClaimsConfig": {
"ClaimsHeaderPrefix": "SWIZLY-PEASY",
"ClaimsAsHeaders": [
"sub",
"email",
"name",
"family_name"
],
"JwtToIdentityClaimsMappings": {
"sub": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
"email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
}
},
"RateLimiterPolicies": [
{
"PolicyName": "swizly1",
"RateLimiterType": "FixedWindowRateLimiter",
"AutoReplenishment": true,
"PermitLimit": 5,
"QueueLimit": 0,
"QueueProcessingOrder": 1,
"Window": 12
},
{
"PolicyName": "swizly2",
"RateLimiterType": "SlidingWindowRateLimiter",
"AutoReplenishment": true,
"PermitLimit": 5,
"QueueLimit": 0,
"QueueProcessingOrder": 1,
"Window": 12,
"SegmentsPerWindow": 3
},
{
"PolicyName": "swizly3",
"RateLimiterType": "ConcurrencyLimiter",
"PermitLimit": 5,
"QueueLimit": 0,
"QueueProcessingOrder": 1
},
{
"PolicyName": "swizly4",
"RateLimiterType": "TokenBucketRateLimiter",
"AutoReplenishment": true,
"QueueLimit": 0,
"QueueProcessingOrder": 1,
"ReplenishmentPeriod": 60,
"TokenLimit": 20,
"TokensPerPeriod": 10
}
]
}
Please read the documentation for more information about the rate limiting algorithms used: https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit?view=aspnetcore-7.0
The current solution only supports the 4 algorithms proposed by Microsoft. At the minute, it is not possible to combine them and the policies configuration must be defined in gateway's app settings.
You should have a look at the SwizlyPeasy.DEMO.API project.
Production: Make sure you can't reach the client from the web (encapsulated like in docker), otherwise all this makes no sense.
The setup is a bit more complicated than for the gateway itself, because of the middlewares ordering...
First, use the extension method RegisterServiceToSwizlyPeasyGateway
, this will configure the consul client, the service registration to consul and configure the health checks.
Then, add a "custom" authentication method using SetSwizlyPeasyAuthentication
, as the claims will be provided as headers.
MiddleWares:
app.UseSwizlyPeasyExceptions();
Handling exceptions and returning them in RFC 7807 formatapp.UseSwizlyPeasyHealthChecks();
Formating the output from health endpointExample:
// swizly peasy consul & health checks
builder.Services.RegisterServiceToSwizlyPeasyGateway(builder.Configuration);
builder.Services.SetSwizlyPeasyAuthentication(builder.Configuration);
builder.Services.SetAuthorization();
...
var app = builder.Build();
app.UseSwizlyPeasyExceptions();
...
app.UseHttpsRedirection();
app.UseAuthentication();
//--------- Swizly Peasy MiddleWares ----------
// swizly peasy health checks middleware
app.UseSwizlyPeasyHealthChecks();
//---------------------------------------------
app.UseAuthorization();
app.MapControllers();
Configuration (appsettings):
"ServiceDiscovery": {
"Scheme": "http",
"RefreshIntervalInSeconds": 120,
"LoadBalancingPolicy": "Random",
"KeyValueStoreKey": "SwizlyPeasy.Gateway",
"ServiceDiscoveryAddress": "http://consul:8500"
},
"ServiceRegistration": {
"ServiceName": "DemoAPI",
"ServiceId": "1",
"ServiceAddress": "http://demo",
"HealthCheckPath": "health"
},
"ClaimsConfig": {
"ClaimsHeaderPrefix": "SWIZLY-PEASY",
"ClaimsAsHeaders": [
"sub",
"email",
"name",
"family_name"
],
"JwtToIdentityClaimsMappings": {
"sub": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
"email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
}
}
}