gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

README.md (9581B)


      1 # Limiter
      2 
      3 [![Documentation][godoc-img]][godoc-url]
      4 ![License][license-img]
      5 [![Build Status][circle-img]][circle-url]
      6 [![Go Report Card][goreport-img]][goreport-url]
      7 
      8 _Dead simple rate limit middleware for Go._
      9 
     10 - Simple API
     11 - "Store" approach for backend
     12 - Redis support (but not tied too)
     13 - Middlewares: HTTP, [FastHTTP][6] and [Gin][4]
     14 
     15 ## Installation
     16 
     17 Using [Go Modules](https://github.com/golang/go/wiki/Modules)
     18 
     19 ```bash
     20 $ go get github.com/ulule/limiter/v3@v3.11.2
     21 ```
     22 
     23 ## Usage
     24 
     25 In five steps:
     26 
     27 - Create a `limiter.Rate` instance _(the number of requests per period)_
     28 - Create a `limiter.Store` instance _(see [Redis](https://github.com/ulule/limiter/blob/master/drivers/store/redis/store.go) or [In-Memory](https://github.com/ulule/limiter/blob/master/drivers/store/memory/store.go))_
     29 - Create a `limiter.Limiter` instance that takes store and rate instances as arguments
     30 - Create a middleware instance using the middleware of your choice
     31 - Give the limiter instance to your middleware initializer
     32 
     33 **Example:**
     34 
     35 ```go
     36 // Create a rate with the given limit (number of requests) for the given
     37 // period (a time.Duration of your choice).
     38 import "github.com/ulule/limiter/v3"
     39 
     40 rate := limiter.Rate{
     41     Period: 1 * time.Hour,
     42     Limit:  1000,
     43 }
     44 
     45 // You can also use the simplified format "<limit>-<period>"", with the given
     46 // periods:
     47 //
     48 // * "S": second
     49 // * "M": minute
     50 // * "H": hour
     51 // * "D": day
     52 //
     53 // Examples:
     54 //
     55 // * 5 reqs/second: "5-S"
     56 // * 10 reqs/minute: "10-M"
     57 // * 1000 reqs/hour: "1000-H"
     58 // * 2000 reqs/day: "2000-D"
     59 //
     60 rate, err := limiter.NewRateFromFormatted("1000-H")
     61 if err != nil {
     62     panic(err)
     63 }
     64 
     65 // Then, create a store. Here, we use the bundled Redis store. Any store
     66 // compliant to limiter.Store interface will do the job. The defaults are
     67 // "limiter" as Redis key prefix and a maximum of 3 retries for the key under
     68 // race condition.
     69 import "github.com/ulule/limiter/v3/drivers/store/redis"
     70 
     71 store, err := redis.NewStore(client)
     72 if err != nil {
     73     panic(err)
     74 }
     75 
     76 // Alternatively, you can pass options to the store with the "WithOptions"
     77 // function. For example, for Redis store:
     78 import "github.com/ulule/limiter/v3/drivers/store/redis"
     79 
     80 store, err := redis.NewStoreWithOptions(pool, limiter.StoreOptions{
     81     Prefix:   "your_own_prefix",
     82 })
     83 if err != nil {
     84     panic(err)
     85 }
     86 
     87 // Or use a in-memory store with a goroutine which clears expired keys.
     88 import "github.com/ulule/limiter/v3/drivers/store/memory"
     89 
     90 store := memory.NewStore()
     91 
     92 // Then, create the limiter instance which takes the store and the rate as arguments.
     93 // Now, you can give this instance to any supported middleware.
     94 instance := limiter.New(store, rate)
     95 
     96 // Alternatively, you can pass options to the limiter instance with several options.
     97 instance := limiter.New(store, rate, limiter.WithClientIPHeader("True-Client-IP"), limiter.WithIPv6Mask(mask))
     98 
     99 // Finally, give the limiter instance to your middleware initializer.
    100 import "github.com/ulule/limiter/v3/drivers/middleware/stdlib"
    101 
    102 middleware := stdlib.NewMiddleware(instance)
    103 ```
    104 
    105 See middleware examples:
    106 
    107 - [HTTP](https://github.com/ulule/limiter-examples/tree/master/http/main.go)
    108 - [Gin](https://github.com/ulule/limiter-examples/tree/master/gin/main.go)
    109 - [Beego](https://github.com/ulule/limiter-examples/blob/master//beego/main.go)
    110 - [Chi](https://github.com/ulule/limiter-examples/tree/master/chi/main.go)
    111 - [Echo](https://github.com/ulule/limiter-examples/tree/master/echo/main.go)
    112 - [Fasthttp](https://github.com/ulule/limiter-examples/tree/master/fasthttp/main.go)
    113 
    114 ## How it works
    115 
    116 The ip address of the request is used as a key in the store.
    117 
    118 If the key does not exist in the store we set a default
    119 value with an expiration period.
    120 
    121 You will find two stores:
    122 
    123 - Redis: rely on [TTL](http://redis.io/commands/ttl) and incrementing the rate limit on each request.
    124 - In-Memory: rely on a fork of [go-cache](https://github.com/patrickmn/go-cache) with a goroutine to clear expired keys using a default interval.
    125 
    126 When the limit is reached, a `429` HTTP status code is sent.
    127 
    128 ## Limiter behind a reverse proxy
    129 
    130 ### Introduction
    131 
    132 If your limiter is behind a reverse proxy, it could be difficult to obtain the "real" client IP.
    133 
    134 Some reverse proxies, like AWS ALB, lets all header values through that it doesn't set itself.
    135 Like for example, `True-Client-IP` and `X-Real-IP`.
    136 Similarly, `X-Forwarded-For` is a list of comma-separated IPs that gets appended to by each traversed proxy.
    137 The idea is that the first IP _(added by the first proxy)_ is the true client IP. Each subsequent IP is another proxy along the path.
    138 
    139 An attacker can spoof either of those headers, which could be reported as a client IP.
    140 
    141 By default, limiter doesn't trust any of those headers: you have to explicitly enable them in order to use them.
    142 If you enable them, **you must always be aware** that any header added by any _(reverse)_ proxy not controlled
    143 by you **are completely unreliable.**
    144 
    145 ### X-Forwarded-For
    146 
    147 For example, if you make this request to your load balancer:
    148 ```bash
    149 curl -X POST https://example.com/login -H "X-Forwarded-For: 1.2.3.4, 11.22.33.44"
    150 ```
    151 
    152 And your server behind the load balancer obtain this:
    153 ```
    154 X-Forwarded-For: 1.2.3.4, 11.22.33.44, <actual client IP>
    155 ```
    156 
    157 That's mean you can't use `X-Forwarded-For` header, because it's **unreliable** and **untrustworthy**.
    158 So keep `TrustForwardHeader` disabled in your limiter option.
    159 
    160 However, if you have configured your reverse proxy to always remove/overwrite `X-Forwarded-For` and/or `X-Real-IP` headers
    161 so that if you execute this _(same)_ request:
    162 ```bash
    163 curl -X POST https://example.com/login -H "X-Forwarded-For: 1.2.3.4, 11.22.33.44"
    164 ```
    165 
    166 And your server behind the load balancer obtain this:
    167 ```
    168 X-Forwarded-For: <actual client IP>
    169 ```
    170 
    171 Then, you can enable `TrustForwardHeader` in your limiter option.
    172 
    173 ### Custom header
    174 
    175 Many CDN and Cloud providers add a custom header to define the client IP. Like for example, this non exhaustive list:
    176 
    177 * `Fastly-Client-IP` from Fastly
    178 * `CF-Connecting-IP` from Cloudflare
    179 * `X-Azure-ClientIP` from Azure
    180 
    181 You can use these headers using `ClientIPHeader` in your limiter option.
    182 
    183 ### None of the above
    184 
    185 If none of the above solution are working, please use a custom `KeyGetter` in your middleware.
    186 
    187 You can use this excellent article to help you define the best strategy depending on your network topology and your security need:
    188 https://adam-p.ca/blog/2022/03/x-forwarded-for/
    189 
    190 If you have any idea/suggestions on how we could simplify this steps, don't hesitate to raise an issue.
    191 We would like some feedback on how we could implement this steps in the Limiter API.
    192 
    193 Thank you.
    194 
    195 ## Why Yet Another Package
    196 
    197 You could ask us: why yet another rate limit package?
    198 
    199 Because existing packages did not suit our needs.
    200 
    201 We tried a lot of alternatives:
    202 
    203 1. [Throttled][1]. This package uses the generic cell-rate algorithm. To cite the
    204    documentation: _"The algorithm has been slightly modified from its usual form to
    205    support limiting with an additional quantity parameter, such as for limiting the
    206    number of bytes uploaded"_. It is brillant in term of algorithm but
    207    documentation is quite unclear at the moment, we don't need _burst_ feature for
    208    now, impossible to get a correct `After-Retry` (when limit exceeds, we can still
    209    make a few requests, because of the max burst) and it only supports `http.Handler`
    210    middleware (we use [Gin][4]). Currently, we only need to return `429`
    211    and `X-Ratelimit-*` headers for `n reqs/duration`.
    212 
    213 2. [Speedbump][3]. Good package but maybe too lightweight. No `Reset` support,
    214    only one middleware for [Gin][4] framework and too Redis-coupled. We rather
    215    prefer to use a "store" approach.
    216 
    217 3. [Tollbooth][5]. Good one too but does both too much and too little. It limits by
    218    remote IP, path, methods, custom headers and basic auth usernames... but does not
    219    provide any Redis support (only _in-memory_) and a ready-to-go middleware that sets
    220    `X-Ratelimit-*` headers. `tollbooth.LimitByRequest(limiter, r)` only returns an HTTP
    221    code.
    222 
    223 4. [ratelimit][2]. Probably the closer to our needs but, once again, too
    224    lightweight, no middleware available and not active (last commit was in August
    225    2014). Some parts of code (Redis) comes from this project. It should deserve much
    226    more love.
    227 
    228 There are other many packages on GitHub but most are either too lightweight, too
    229 old (only support old Go versions) or unmaintained. So that's why we decided to
    230 create yet another one.
    231 
    232 ## Contributing
    233 
    234 - Ping us on twitter:
    235   - [@oibafsellig](https://twitter.com/oibafsellig)
    236   - [@thoas](https://twitter.com/thoas)
    237   - [@novln\_](https://twitter.com/novln_)
    238 - Fork the [project](https://github.com/ulule/limiter)
    239 - Fix [bugs](https://github.com/ulule/limiter/issues)
    240 
    241 Don't hesitate ;)
    242 
    243 [1]: https://github.com/throttled/throttled
    244 [2]: https://github.com/r8k/ratelimit
    245 [3]: https://github.com/etcinit/speedbump
    246 [4]: https://github.com/gin-gonic/gin
    247 [5]: https://github.com/didip/tollbooth
    248 [6]: https://github.com/valyala/fasthttp
    249 [godoc-url]: https://pkg.go.dev/github.com/ulule/limiter/v3
    250 [godoc-img]: https://pkg.go.dev/badge/github.com/ulule/limiter/v3
    251 [license-img]: https://img.shields.io/badge/license-MIT-blue.svg
    252 [goreport-url]: https://goreportcard.com/report/github.com/ulule/limiter
    253 [goreport-img]: https://goreportcard.com/badge/github.com/ulule/limiter
    254 [circle-url]: https://circleci.com/gh/ulule/limiter/tree/master
    255 [circle-img]: https://circleci.com/gh/ulule/limiter.svg?style=shield&circle-token=baf62ec320dd871b3a4a7e67fa99530fbc877c99