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

Commit 8550b47

Browse files
authored
Merge pull request #17 from mozilla-services/ajvb/0.1.5
Version 0.1.5
2 parents e6c0d2a + 806033e commit 8550b47

File tree

4 files changed

+92
-36
lines changed

4 files changed

+92
-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

+64-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,82 @@ 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+
ngx.req.set_header('X-Foxsec-IP-Reputation-Below-Threshold', 'false')
67+
ngx.req.set_header('X-Foxsec-Block', 'false')
68+
if self.whitelist then
69+
if iputils.ip_in_cidrs(ip, self.whitelist) then
70+
return
71+
end
72+
end
6073

61-
-- Get reputation for ip
74+
local reputation = self:get_reputation(ip)
75+
if reputation then
76+
ngx.req.set_header('X-Foxsec-IP-Reputation', tostring(reputation))
77+
if reputation <= self.threshold then
78+
ngx.req.set_header('X-Foxsec-IP-Reputation-Below-Threshold', 'true')
79+
ngx.req.set_header('X-Foxsec-Block', 'true')
80+
if self.statsd then
81+
self.statsd.incr("iprepd.status.below_threshold")
82+
end
83+
84+
if self.dont_block == 1 then
85+
ngx.log(ngx.ERR, ip .. ' is below threshold with a reputation of ' .. reputation)
86+
else
87+
ngx.log(ngx.ERR, ip .. ' rejected with a reputation of ' .. reputation)
88+
if self.statsd then
89+
self.statsd.incr("iprepd.status.rejected")
90+
end
91+
ngx.exit(ngx.HTTP_FORBIDDEN)
92+
end
93+
else
94+
if self.statsd then
95+
self.statsd.incr("iprepd.status.accepted")
96+
end
97+
end
98+
99+
return
100+
end
101+
102+
if self.statsd then
103+
self.statsd.incr("iprepd.status.accepted")
104+
end
105+
end
106+
107+
function _M.get_reputation(self, ip)
62108
local reputation = self.cache:get(ip)
109+
63110
if not reputation then
111+
local httpc = http.new()
112+
httpc:set_timeout(self.timeout)
64113
local resp, err = httpc:request_uri(self.url .. '/' .. ip, {
65114
method = "GET",
66115
headers = self.api_key_hdr,
@@ -70,7 +119,7 @@ function _M.check(self, ip)
70119
self.statsd.incr("iprepd.err.timeout")
71120
end
72121
ngx.log(ngx.ERR, 'Error with request to iprepd: ' .. err)
73-
return
122+
return nil
74123
end
75124

76125
-- If the IP was found
@@ -85,26 +134,17 @@ function _M.check(self, ip)
85134
self.cache:set(ip, 100, self.cache_ttl)
86135
else
87136
ngx.log(ngx.ERR, 'iprepd responded with a ' .. resp.status .. ' http status code')
137+
if self.statsd then
138+
self.statsd.incr("iprepd.err." .. resp.status)
139+
end
88140
if self.cache_errors == 1 then
89141
ngx.log(ngx.ERR, 'cache_errors is enabled, setting reputation of ' .. ip .. ' to 100 within the cache')
90142
self.cache:set(ip, 100, self.cache_ttl)
91143
end
92144
end
93145
end
94146

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
147+
return reputation
108148
end
109149

110150
function _M.flush_stats(self)
@@ -120,7 +160,7 @@ function _M.async_flush_stats(premature, self)
120160
end
121161

122162
function _M.config_flush_timer(self)
123-
ngx.timer.every(5, self.async_flush_stats, self)
163+
ngx.timer.every(self.statsd_flush_timer, self.async_flush_stats, self)
124164
end
125165

126166
return _M

0 commit comments

Comments
 (0)