NicolasDorier / NicolasDorier.RateLimits

Control access to your resources or MVC routes based on the leaky bucket model of NGINX

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

NuGet

RateLimits library

Introduction

This library is meant to allow you to control access to your resources or MVC routes based on the leaky bucket model of NGINX.

You can find more documentation on this abstraction on this NGinx article.

Concept

You might want to limit access to your resources based on some logic.

The solution implemented by this repository is based on the same principles than NGinx.

Imagine that requests coming for access to your resource falls into a bucket.

The dimensions of your bucket are called LimitRequestZone.

If you want to create a hole in the bucket to be sure that only 10 requests per minutes pass, your LimitRequestZone string would be:

zone=myzone rate=10r/m

If you apply such zone, as explained later, then only 1 request every 6 sec (ie, the inverse of 10 requests per minutes) will be able to be serviced.

If 3 requests are sent in 6 seconds:

  • The first requests will execute immediately
  • The second will be dropped (typically, HTTP 429)
  • The third will be dropped

What if you want up to 3 requests to NOT be dropped?

zone=myzone rate=10r/m burst=3

Now, if there is 4 requests in 6 seconds,

  • The first requests will execute immediately
  • The second will be delayed until the 10 sec period is finished
  • The third will be delayed until the second 10 sec period is finished
  • The fourth will be dropped.

Now what if, you don't do not want to delay the execution, but still want to be sure about having in average 10 requests per min?

zone=myzone rate=10r/m burst=3 nodelay

If there is 4 requests in 6 seconds,

  • The first requests will execute immediately
  • The second will execute immediately
  • The third will execute immediately
  • The fourth will be dropped

Now what if you want to allow 10r/m, process without delay 3 of them, then delay the rest?

zone=myzone rate=10r/m burst=6 delay=3
  • The first requests will execute immediately
  • The second will execute immediately
  • The third will execute immediately
  • The fourth will be delayed
  • The fifth will be delayed
  • The sixth will be delayed
  • Then the rest will be dropped

How to use

Setup

In your ASP.NET project, add reference to this library:

dotnet add package NicolasDorier.RateLimits

Then add the service in your Startup.cs class, for example:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Text;
+using NicolasDorier.RateLimits;

namespace MyAwesomeApp
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
+           services.AddRateLimits();
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseMvc();
        }
    }
}

From now, you will be able to resolve the RateLimitService class to setup your zones.

Our goal will be to make sure that:

  • a specific IP can't try to login to our website more than 10 times per min (ie, 1 attempt every 6 seconds).
  • We allow the user to bursts at most 3 attempts per 6 seconds.
  • We don't want to delay his requests (if the request can be treated, it should be done immedialely)
public class ZoneLimits
{
    public const string Login = "login";
}

https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer In your Startup.cs:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.HttpOverrides;
using NicolasDorier.RateLimits;

namespace MyAwesomeApp
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRateLimits();
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app, RateLimitService rates)
        {
            // This make sure X-Forwarded-For is taken into account
            // See https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer
            var forwardingOptions = new ForwardedHeadersOptions();
            forwardingOptions.KnownNetworks.Clear();
            forwardingOptions.KnownProxies.Clear();
            forwardingOptions.ForwardedHeaders = ForwardedHeaders.All;
            app.UseForwardedHeaders(forwardingOptions);

+           rates.SetZone($"zone={ZoneLimits.Login} rate=10r/m burst=3 nodelay");
            app.UseMvc();
        }
    }
}

You then have two way of applying the rate limit to Login.

Rate limits on MVC routes

The easiest is to apply RateLimitsFilterAttribute on your action.

  [RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)]
  public async Task<IActionResult> Login(string redirectUrl, LoginViewModel vm) 
  {
      ....
  }

RateLimitsScope.RemoteAddress will use the client's IP address to apply the limit. (Or from X-Forwarded-For/X-Real-IP header if present)

Available scopes are:

  • RateLimitsScope.Global limit rate globally.
  • RateLimitsScope.RemoteAddress limit rate per IP.
  • RateLimitsScope.RouteData limit rate per RouteData value data with key specified by RateLimitsFilterAttribute.DataKey
  • RateLimitsScope.ActionArgument limit rate per Action Argument value with key specified by RateLimitsFilterAttribute.DataKey

The filter will throw HTTP error 429 Too Many Requests if the user is sending too much requests.

Rate limits in your own code

Imagine that you want to limit the rate from the login's username instead of IP address.

First, you resolve the IRateLimitService in your constructor.

IRateLimitService rateLimit;
public MyController(IRateLimitService rateLimit)
{
     this.rateLimit = rateLimit;
}

The you use it in your login with RateLimitService.Throttle:

  public async Task<IActionResult> Login(string redirectUrl, LoginViewModel vm) 
  {
      if(!await rateLimit.Throttle(ZoneLimits.Login, vm.Username.Trim()))
        return new TooManyRequestsResult(ZoneLimits.Login);
      ....
  }

License

This library is under the MIT License.

About

Control access to your resources or MVC routes based on the leaky bucket model of NGINX

License:MIT License


Languages

Language:C# 99.2%Language:PowerShell 0.8%