Skip to content
This repository was archived by the owner on Apr 14, 2025. It is now read-only.

Commit 4d4aada

Browse files
committed
Version 0.1.5
- Adds support for a "dont block" mode. Fixes #16 - Adds injecting headers by default. Fixes #10 - Makes cache size configurable. Fixes #2 - Adds support for an IP whitelist. Fixes #15 - Makes statsd flush timer configurable. Fixes #14
1 parent e6c0d2a commit 4d4aada

File tree

4 files changed

+80
-36
lines changed

4 files changed

+80
-36
lines changed

README.md

+23-10
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ init_by_lua_block {
3333
statsd_host = os.getenv("STATSD_HOST") or nil,
3434
statsd_port = tonumber(os.getenv("STATSD_PORT")) or 8125,
3535
statsd_max_buffer_count = tonumber(os.getenv("STATSD_MAX_BUFFER_COUNT")) or 100,
36+
statsd_flush_timer = tonumber(os.getenv("STATSD_FLUSH_TIMER")) or 5,
37+
dont_block = tonumber(os.getenv("DONT_BLOCK")) or 0,
38+
whitelist = {},
3639
})
3740
}
3841
@@ -101,8 +104,9 @@ violations for your environment.
101104
--
102105
-- Optional parameters:
103106
-- url - The base URL to iprepd (defaults to "http://localhost:8080/")
104-
-- cache_ttl - The iprepd response cache ttl in seconds (defaults to 30)
105107
-- timeout - The timeout for making requests to iprepd in milliseconds (defaults to 10)
108+
-- cache_ttl - The iprepd response cache ttl in seconds (defaults to 30)
109+
-- cache_buffer_count - Max number of entries allowed in the cache. (defaults to 200)
106110
-- cache_errors - Enables (1) or disables (0) caching errors. Caching errors is a good
107111
-- idea in production, as it can reduce the average additional latency
108112
-- caused by this module if anything goes wrong with the underlying
@@ -111,17 +115,24 @@ violations for your environment.
111115
-- statsd_port - Port of statsd collector. (defaults to 8125)
112116
-- statsd_max_buffer_count - Max number of metrics in buffer before metrics should be submitted
113117
-- to statsd (defaults to 100)
118+
-- statsd_flush_timer - Interval for attempting to flush the stats in seconds. (defaults to 5)
119+
-- dont_block - Enables (1) or disables (0) not blocking within nginx by returning a 403. (defaults to disabled)
120+
-- whitelist - List of whitelisted IP's and IP CIDR's. (defaults to empty)
114121
--
115122
client = require("resty.iprepd").new({
116-
url = "http://127.0.0.1:8080",
117123
api_key = os.getenv("IPREPD_API_KEY"),
118124
threshold = 50,
119-
cache_ttl = 30,
125+
url = "http://127.0.0.1:8080",
120126
timeout = 10,
127+
cache_ttl = 30,
128+
cache_buffer_count = 1000,
121129
cache_errors = 1,
122130
statsd_host = "127.0.0.1",
123131
statsd_port = 8125,
124132
statsd_max_buffer_count = 100,
133+
statsd_flush_timer = 10,
134+
dont_block = 0,
135+
whitelist = {"127.0.0.1", "10.10.10.0/24", "192.168.0.0/16"}
125136
})
126137
```
127138

@@ -137,7 +148,7 @@ $ make run_dev
137148

138149
Then you will be able to hit this proxy with: `curl http://localhost:80`
139150

140-
### Environment Variables
151+
### Environment Variables for Dev
141152

142153
#### Note:
143154

@@ -153,10 +164,12 @@ IPREPD_REPUTATION_THRESHOLD=50 # iprepd reputation threshold, block all IP's wi
153164
#
154165
# optional
155166
#
156-
IPREPD_TIMEOUT=10 # iprepd client timeout in milliseconds (default is 10ms)
157-
IPREPD_CACHE_TTL=60 # iprepd response cache ttl in seconds (default is 30s)
158-
IPREPD_CACHE_ERRORS=1 # enables caching iprepd non-200 responses (1 enables, 0 disables, default is 0)
159-
STATSD_HOST=127.0.0.1 # statsd host, setting this will also enable statsd metrics collection.
160-
STATSD_PORT=8125 # statsd port (default is 8125)
161-
STATSD_MAX_BUFFER_COUNT=200 # statsd max number of buffer items before submitting (default is 100)
167+
IPREPD_TIMEOUT=10
168+
IPREPD_CACHE_TTL=30
169+
IPREPD_CACHE_ERRORS=0
170+
STATSD_HOST=127.0.0.1
171+
STATSD_PORT=8125
172+
STATSD_MAX_BUFFER_COUNT=200
173+
STATSD_FLUSH_TIMER=2
174+
DONT_BLOCK=0
162175
```

dist.ini

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
name = iprepd-nginx
22
abstract = iprepd openresty module
33
author = AJ Bahnken (ajvb)
4-
version = 0.1.4
4+
version = 0.1.5
55
is_original = yes
66
license = mozilla2
77
lib_dir = lib
88
doc_dir = lib
99
repo_link = https://github.com/mozilla-services/iprepd-nginx
1010
main_module = lib/resty/iprepd.lua
11-
requires = openresty/lua-resty-lrucache, pintsized/lua-resty-http
11+
requires = openresty/lua-resty-lrucache, pintsized/lua-resty-http, hamishforbes/lua-resty-iputils

etc/conf.d/server.conf

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ init_by_lua_block {
99
statsd_host = os.getenv("STATSD_HOST") or nil,
1010
statsd_port = tonumber(os.getenv("STATSD_PORT")) or 8125,
1111
statsd_max_buffer_count = tonumber(os.getenv("STATSD_MAX_BUFFER_COUNT")) or 100,
12+
statsd_flush_timer = tonumber(os.getenv("STATSD_FLUSH_TIMER")) or 5,
13+
dont_block = tonumber(os.getenv("DONT_BLOCK")) or 0,
14+
whitelist = {},
1215
})
1316
}
1417

lib/resty/iprepd.lua

+52-24
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
local cjson = require('cjson')
22
local http = require('resty.http')
3+
local iputils = require('resty.iputils')
34
local lrucache = require('resty.lrucache')
45
local statsd = require('resty.statsd')
56

@@ -18,13 +19,12 @@ function _M.new(options)
1819
iprepd_url = iprepd_url:sub(1, -2)
1920
end
2021

21-
local cache_ttl = options.cache_ttl or 30
22+
local cache_buffer_count = options.cache_buffer_count or 200
2223

2324
local iprepd_threshold = options.threshold or fatal_error('Need to pass in a threshold')
2425
local iprepd_api_key = options.api_key or fatal_error('Need to pass in an api_key')
2526

26-
-- TODO: Make configurable?
27-
local cache, err = lrucache.new(200)
27+
local cache, err = lrucache.new(cache_buffer_count)
2828
if not cache then
2929
fatal_error('failed to create the cache: ' .. (err or 'unknown'))
3030
end
@@ -34,33 +34,73 @@ function _M.new(options)
3434
statsd_client = statsd
3535
end
3636

37+
local whitelist = nil
38+
local whitelist_list = options.whitelist or nil
39+
if whitelist_list then
40+
whitelist = iputils.parse_cidrs(whitelist_list)
41+
end
42+
3743
local self = {
3844
url = iprepd_url,
45+
timeout = options.timeout or 10,
3946
threshold = iprepd_threshold,
4047
api_key_hdr = {
4148
['Authorization'] = 'APIKey ' .. iprepd_api_key,
4249
},
43-
cache_ttl = cache_ttl,
44-
timeout = options.timeout or 10,
4550
cache = cache,
51+
cache_ttl = options.cache_ttl or 30,
4652
cache_errors = options.cache_errors or 0,
4753
statsd = statsd_client,
4854
statsd_host = options.statsd_host,
4955
statsd_port = options.statsd_port or 8125,
5056
statsd_max_buffer_count = options.statsd_max_buffer_count or 100,
57+
statsd_flush_timer = options.statsd_flush_timer or 5,
58+
dont_block = options.dont_block or 0,
59+
whitelist = whitelist,
5160
}
5261

5362
return setmetatable(self, mt)
5463
end
5564

5665
function _M.check(self, ip)
57-
local httpc = http.new()
58-
-- set timeout in ms
59-
httpc:set_timeout(self.timeout)
66+
if self.whitelist then
67+
if iputils.ip_in_cidrs(ip, self.whitelist) then
68+
return
69+
end
70+
end
71+
72+
local reputation = self:get_reputation(ip)
73+
if reputation then
74+
ngx.req.set_header('X-Foxsec-IP-Reputation', tostring(reputation))
75+
if reputation <= self.threshold then
76+
ngx.req.set_header('X-Foxsec-IP-Reputation-Below-Threshold', 'true')
77+
ngx.req.set_header('X-Foxsec-Block', 'true')
78+
if self.statsd then
79+
self.statsd.incr("iprepd.status.rejected")
80+
end
6081

61-
-- Get reputation for ip
82+
if self.dont_block == 1 then
83+
ngx.log(ngx.ERR, '[logonly] ' .. ip .. ' rejected with a reputation of ' .. reputation)
84+
else
85+
ngx.log(ngx.ERR, ip .. ' rejected with a reputation of ' .. reputation)
86+
ngx.exit(ngx.HTTP_FORBIDDEN)
87+
end
88+
end
89+
end
90+
91+
ngx.req.set_header('X-Foxsec-IP-Reputation-Below-Threshold', 'false')
92+
ngx.req.set_header('X-Foxsec-Block', 'false')
93+
if self.statsd then
94+
self.statsd.incr("iprepd.status.accepted")
95+
end
96+
end
97+
98+
function _M.get_reputation(self, ip)
6299
local reputation = self.cache:get(ip)
100+
63101
if not reputation then
102+
local httpc = http.new()
103+
httpc:set_timeout(self.timeout)
64104
local resp, err = httpc:request_uri(self.url .. '/' .. ip, {
65105
method = "GET",
66106
headers = self.api_key_hdr,
@@ -70,7 +110,7 @@ function _M.check(self, ip)
70110
self.statsd.incr("iprepd.err.timeout")
71111
end
72112
ngx.log(ngx.ERR, 'Error with request to iprepd: ' .. err)
73-
return
113+
return nil
74114
end
75115

76116
-- If the IP was found
@@ -92,19 +132,7 @@ function _M.check(self, ip)
92132
end
93133
end
94134

95-
-- check reputation against threshold
96-
if reputation and reputation <= self.threshold then
97-
-- return 403 and log rejections
98-
ngx.log(ngx.ERR, ip .. ' rejected with a reputation of ' .. reputation)
99-
if self.statsd then
100-
self.statsd.incr("iprepd.status.rejected")
101-
end
102-
ngx.exit(ngx.HTTP_FORBIDDEN)
103-
else
104-
if self.statsd then
105-
self.statsd.incr("iprepd.status.accepted")
106-
end
107-
end
135+
return reputation
108136
end
109137

110138
function _M.flush_stats(self)
@@ -120,7 +148,7 @@ function _M.async_flush_stats(premature, self)
120148
end
121149

122150
function _M.config_flush_timer(self)
123-
ngx.timer.every(5, self.async_flush_stats, self)
151+
ngx.timer.every(self.statsd_flush_timer, self.async_flush_stats, self)
124152
end
125153

126154
return _M

0 commit comments

Comments
 (0)