Skip to content

Commit c346f9d

Browse files
committed
Add basic IP-based rate-limit support, closes #59
1 parent 1c070b1 commit c346f9d

File tree

5 files changed

+103
-1
lines changed

5 files changed

+103
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Exoframe is a self-hosted tool that allows simple one-command deployments using
1717
- Deploy tokens (e.g. to deploy from CI)
1818
- Automated HTTPS setup via letsencrypt \*
1919
- Automated gzip compression \*
20+
- Rate-limit support \*
2021
- Simple access to the logs of deployments
2122
- Docker-compose support
2223
- Multiple deployment endpoints and multi-user support

docs/Advanced.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,30 @@ Here's a few examples of basic use cases:
2222
| `domain.com;PathStrip:/products/` | `service/` | Match exact path and strip off the path prior to forwarding the request |
2323

2424
For more info and options see the aforementioned [Traefik frontend matchers](https://docs.traefik.io/basics/#matchers) docs.
25+
26+
## Rate limiting
27+
28+
Exoframe allows you to enable basic IP-based rate-limiting integrated into Traefik.
29+
To do that, simply specify the following fields in the project config file:
30+
31+
```json
32+
{
33+
// adding this object will enable IP-based rate-limiting
34+
"rate-limit": {
35+
// time period to be considered for request limits
36+
// defaults to "1s" if not specified
37+
"period": "3s",
38+
// average request rate over given time period
39+
// defaults to 1 if not specified
40+
"average": 5,
41+
// maximal burst request rate over given time period
42+
// defaults to 5 if not specified
43+
"burst": 10
44+
}
45+
}
46+
```
47+
48+
This will define how many requests (`average`) over given time (`period`) can be performed from one IP address.
49+
For the example above - an average of 5 requests every 3 seconds is allowed with busts of up to 10 requests.
50+
51+
For more information, see [Traefik rate-limiting docs](https://docs.traefik.io/configuration/commons/#rate-limiting).

docs/Basics.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,16 @@ Config file has the following structure:
9898
"labels": {
9999
"my.custom.label": "value"
100100
},
101+
// rate-limit config
102+
// see "advanced topics" for more info
103+
"rate-limit": {
104+
// rate-limit time period
105+
"period": "1s",
106+
// request rate over given time period
107+
"average": 1,
108+
// max burst request rate over given time period
109+
"burst": 5,
110+
},
101111
// template to be used for project deployment
102112
// undefined by default, detected by server based on file structure
103113
"template": "my-template"

src/commands/config.js

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ exports.handler = async () => {
2323
labels: undefined,
2424
hostname: '',
2525
template: '',
26+
rateLimit: {
27+
period: '1s',
28+
average: 1,
29+
burst: 5,
30+
},
2631
};
2732
try {
2833
fs.statSync(configPath);
@@ -86,6 +91,36 @@ exports.handler = async () => {
8691
: '',
8792
filter,
8893
});
94+
prompts.push({
95+
type: 'confirm',
96+
name: 'enableRatelimit',
97+
message: 'Enable rate-limit? [optional]',
98+
default: !!defaultConfig.rateLimit,
99+
});
100+
prompts.push({
101+
type: 'input',
102+
name: 'ratelimitPeriod',
103+
message: 'Rate-limit period (in seconds)',
104+
default: defaultConfig.rateLimit ? defaultConfig.rateLimit.period.replace('s', '') : '1',
105+
filter: val => `${val.trim()}s`,
106+
when: ({enableRatelimit}) => enableRatelimit,
107+
});
108+
prompts.push({
109+
type: 'input',
110+
name: 'ratelimitAverage',
111+
message: 'Rate-limit average request rate',
112+
default: defaultConfig.rateLimit ? defaultConfig.rateLimit.average : '1',
113+
filter: val => Number(val.trim()),
114+
when: ({enableRatelimit}) => enableRatelimit,
115+
});
116+
prompts.push({
117+
type: 'input',
118+
name: 'ratelimitBurst',
119+
message: 'Rate-limit burst request rate',
120+
default: defaultConfig.rateLimit ? defaultConfig.rateLimit.burst : '5',
121+
filter: val => Number(val.trim()),
122+
when: ({enableRatelimit}) => enableRatelimit,
123+
});
89124
prompts.push({
90125
type: 'input',
91126
name: 'hostname',
@@ -108,7 +143,20 @@ exports.handler = async () => {
108143
filter,
109144
});
110145
// get values from user
111-
const {name, domain, project, env, labels, hostname, restart, template} = await inquirer.prompt(prompts);
146+
const {
147+
name,
148+
domain,
149+
project,
150+
env,
151+
labels,
152+
enableRatelimit,
153+
ratelimitPeriod,
154+
ratelimitAverage,
155+
ratelimitBurst,
156+
hostname,
157+
restart,
158+
template,
159+
} = await inquirer.prompt(prompts);
112160
// init config object
113161
const config = {name, restart};
114162
if (domain && domain.length) {
@@ -131,6 +179,13 @@ exports.handler = async () => {
131179
.map(pair => ({key: pair[0].trim(), value: pair[1].trim()}))
132180
.reduce((prev, obj) => Object.assign(prev, {[obj.key]: obj.value}), {});
133181
}
182+
if (enableRatelimit) {
183+
config.rateLimit = {
184+
period: ratelimitPeriod,
185+
average: ratelimitAverage,
186+
burst: ratelimitBurst,
187+
};
188+
}
134189
if (hostname && hostname.length) {
135190
config.hostname = hostname;
136191
}

test/config.test.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ const configData = {
2121
hostname: 'host',
2222
restart: 'no',
2323
template: 'static',
24+
enableRatelimit: true,
25+
ratelimitPeriod: 10,
26+
ratelimitAverage: 20,
27+
ratelimitBurst: 30,
2428
};
2529
const configPath = path.join(process.cwd(), 'exoframe.json');
2630

@@ -55,6 +59,11 @@ test('Should generate config file', done => {
5559
expect(cfg.labels.label).toEqual('1');
5660
expect(cfg.labels.other).toEqual('2');
5761
expect(cfg.template).toEqual(configData.template);
62+
expect(cfg.rateLimit).toEqual({
63+
period: configData.ratelimitPeriod,
64+
average: configData.ratelimitAverage,
65+
burst: configData.ratelimitBurst,
66+
});
5867
// restore inquirer
5968
inquirer.prompt.restore();
6069
// restore console

0 commit comments

Comments
 (0)