summaryrefslogtreecommitdiff
path: root/vendor/github.com/ulule/limiter/v3/README.md
blob: e0024fca401205fdc85a38f6f42636135691af6a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# Limiter

[![Documentation][godoc-img]][godoc-url]
![License][license-img]
[![Build Status][circle-img]][circle-url]
[![Go Report Card][goreport-img]][goreport-url]

_Dead simple rate limit middleware for Go._

- Simple API
- "Store" approach for backend
- Redis support (but not tied too)
- Middlewares: HTTP, [FastHTTP][6] and [Gin][4]

## Installation

Using [Go Modules](https://github.com/golang/go/wiki/Modules)

```bash
$ go get github.com/ulule/limiter/v3@v3.11.2
```

## Usage

In five steps:

- Create a `limiter.Rate` instance _(the number of requests per period)_
- 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))_
- Create a `limiter.Limiter` instance that takes store and rate instances as arguments
- Create a middleware instance using the middleware of your choice
- Give the limiter instance to your middleware initializer

**Example:**

```go
// Create a rate with the given limit (number of requests) for the given
// period (a time.Duration of your choice).
import "github.com/ulule/limiter/v3"

rate := limiter.Rate{
    Period: 1 * time.Hour,
    Limit:  1000,
}

// You can also use the simplified format "<limit>-<period>"", with the given
// periods:
//
// * "S": second
// * "M": minute
// * "H": hour
// * "D": day
//
// Examples:
//
// * 5 reqs/second: "5-S"
// * 10 reqs/minute: "10-M"
// * 1000 reqs/hour: "1000-H"
// * 2000 reqs/day: "2000-D"
//
rate, err := limiter.NewRateFromFormatted("1000-H")
if err != nil {
    panic(err)
}

// Then, create a store. Here, we use the bundled Redis store. Any store
// compliant to limiter.Store interface will do the job. The defaults are
// "limiter" as Redis key prefix and a maximum of 3 retries for the key under
// race condition.
import "github.com/ulule/limiter/v3/drivers/store/redis"

store, err := redis.NewStore(client)
if err != nil {
    panic(err)
}

// Alternatively, you can pass options to the store with the "WithOptions"
// function. For example, for Redis store:
import "github.com/ulule/limiter/v3/drivers/store/redis"

store, err := redis.NewStoreWithOptions(pool, limiter.StoreOptions{
    Prefix:   "your_own_prefix",
})
if err != nil {
    panic(err)
}

// Or use a in-memory store with a goroutine which clears expired keys.
import "github.com/ulule/limiter/v3/drivers/store/memory"

store := memory.NewStore()

// Then, create the limiter instance which takes the store and the rate as arguments.
// Now, you can give this instance to any supported middleware.
instance := limiter.New(store, rate)

// Alternatively, you can pass options to the limiter instance with several options.
instance := limiter.New(store, rate, limiter.WithClientIPHeader("True-Client-IP"), limiter.WithIPv6Mask(mask))

// Finally, give the limiter instance to your middleware initializer.
import "github.com/ulule/limiter/v3/drivers/middleware/stdlib"

middleware := stdlib.NewMiddleware(instance)
```

See middleware examples:

- [HTTP](https://github.com/ulule/limiter-examples/tree/master/http/main.go)
- [Gin](https://github.com/ulule/limiter-examples/tree/master/gin/main.go)
- [Beego](https://github.com/ulule/limiter-examples/blob/master//beego/main.go)
- [Chi](https://github.com/ulule/limiter-examples/tree/master/chi/main.go)
- [Echo](https://github.com/ulule/limiter-examples/tree/master/echo/main.go)
- [Fasthttp](https://github.com/ulule/limiter-examples/tree/master/fasthttp/main.go)

## How it works

The ip address of the request is used as a key in the store.

If the key does not exist in the store we set a default
value with an expiration period.

You will find two stores:

- Redis: rely on [TTL](http://redis.io/commands/ttl) and incrementing the rate limit on each request.
- 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.

When the limit is reached, a `429` HTTP status code is sent.

## Limiter behind a reverse proxy

### Introduction

If your limiter is behind a reverse proxy, it could be difficult to obtain the "real" client IP.

Some reverse proxies, like AWS ALB, lets all header values through that it doesn't set itself.
Like for example, `True-Client-IP` and `X-Real-IP`.
Similarly, `X-Forwarded-For` is a list of comma-separated IPs that gets appended to by each traversed proxy.
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.

An attacker can spoof either of those headers, which could be reported as a client IP.

By default, limiter doesn't trust any of those headers: you have to explicitly enable them in order to use them.
If you enable them, **you must always be aware** that any header added by any _(reverse)_ proxy not controlled
by you **are completely unreliable.**

### X-Forwarded-For

For example, if you make this request to your load balancer:
```bash
curl -X POST https://example.com/login -H "X-Forwarded-For: 1.2.3.4, 11.22.33.44"
```

And your server behind the load balancer obtain this:
```
X-Forwarded-For: 1.2.3.4, 11.22.33.44, <actual client IP>
```

That's mean you can't use `X-Forwarded-For` header, because it's **unreliable** and **untrustworthy**.
So keep `TrustForwardHeader` disabled in your limiter option.

However, if you have configured your reverse proxy to always remove/overwrite `X-Forwarded-For` and/or `X-Real-IP` headers
so that if you execute this _(same)_ request:
```bash
curl -X POST https://example.com/login -H "X-Forwarded-For: 1.2.3.4, 11.22.33.44"
```

And your server behind the load balancer obtain this:
```
X-Forwarded-For: <actual client IP>
```

Then, you can enable `TrustForwardHeader` in your limiter option.

### Custom header

Many CDN and Cloud providers add a custom header to define the client IP. Like for example, this non exhaustive list:

* `Fastly-Client-IP` from Fastly
* `CF-Connecting-IP` from Cloudflare
* `X-Azure-ClientIP` from Azure

You can use these headers using `ClientIPHeader` in your limiter option.

### None of the above

If none of the above solution are working, please use a custom `KeyGetter` in your middleware.

You can use this excellent article to help you define the best strategy depending on your network topology and your security need:
https://adam-p.ca/blog/2022/03/x-forwarded-for/

If you have any idea/suggestions on how we could simplify this steps, don't hesitate to raise an issue.
We would like some feedback on how we could implement this steps in the Limiter API.

Thank you.

## Why Yet Another Package

You could ask us: why yet another rate limit package?

Because existing packages did not suit our needs.

We tried a lot of alternatives:

1. [Throttled][1]. This package uses the generic cell-rate algorithm. To cite the
   documentation: _"The algorithm has been slightly modified from its usual form to
   support limiting with an additional quantity parameter, such as for limiting the
   number of bytes uploaded"_. It is brillant in term of algorithm but
   documentation is quite unclear at the moment, we don't need _burst_ feature for
   now, impossible to get a correct `After-Retry` (when limit exceeds, we can still
   make a few requests, because of the max burst) and it only supports `http.Handler`
   middleware (we use [Gin][4]). Currently, we only need to return `429`
   and `X-Ratelimit-*` headers for `n reqs/duration`.

2. [Speedbump][3]. Good package but maybe too lightweight. No `Reset` support,
   only one middleware for [Gin][4] framework and too Redis-coupled. We rather
   prefer to use a "store" approach.

3. [Tollbooth][5]. Good one too but does both too much and too little. It limits by
   remote IP, path, methods, custom headers and basic auth usernames... but does not
   provide any Redis support (only _in-memory_) and a ready-to-go middleware that sets
   `X-Ratelimit-*` headers. `tollbooth.LimitByRequest(limiter, r)` only returns an HTTP
   code.

4. [ratelimit][2]. Probably the closer to our needs but, once again, too
   lightweight, no middleware available and not active (last commit was in August
   2014). Some parts of code (Redis) comes from this project. It should deserve much
   more love.

There are other many packages on GitHub but most are either too lightweight, too
old (only support old Go versions) or unmaintained. So that's why we decided to
create yet another one.

## Contributing

- Ping us on twitter:
  - [@oibafsellig](https://twitter.com/oibafsellig)
  - [@thoas](https://twitter.com/thoas)
  - [@novln\_](https://twitter.com/novln_)
- Fork the [project](https://github.com/ulule/limiter)
- Fix [bugs](https://github.com/ulule/limiter/issues)

Don't hesitate ;)

[1]: https://github.com/throttled/throttled
[2]: https://github.com/r8k/ratelimit
[3]: https://github.com/etcinit/speedbump
[4]: https://github.com/gin-gonic/gin
[5]: https://github.com/didip/tollbooth
[6]: https://github.com/valyala/fasthttp
[godoc-url]: https://pkg.go.dev/github.com/ulule/limiter/v3
[godoc-img]: https://pkg.go.dev/badge/github.com/ulule/limiter/v3
[license-img]: https://img.shields.io/badge/license-MIT-blue.svg
[goreport-url]: https://goreportcard.com/report/github.com/ulule/limiter
[goreport-img]: https://goreportcard.com/badge/github.com/ulule/limiter
[circle-url]: https://circleci.com/gh/ulule/limiter/tree/master
[circle-img]: https://circleci.com/gh/ulule/limiter.svg?style=shield&circle-token=baf62ec320dd871b3a4a7e67fa99530fbc877c99