-
Notifications
You must be signed in to change notification settings - Fork 0
/
core.clj
360 lines (316 loc) · 15.1 KB
/
core.clj
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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
(ns ring-firewall-middleware.core
(:require [ring-firewall-middleware.coordination :as coord]
[ring-firewall-middleware.cidr :as cidr])
(:import [java.util.concurrent Semaphore TimeUnit]
(java.util.concurrent.locks Lock ReentrantReadWriteLock)))
(defn default-forbidden-handler
"Provides a default ring response for users who didn't meet the firewall requirements."
([request]
{:status 403
:headers {"Content-Type" "text/plain"}
:body "Access denied"})
([request respond raise]
(respond (default-forbidden-handler request))))
(defn default-limited-handler
"Provides a default ring response for users who exceeded the imposed limit."
([request]
{:status 429
:headers {"Content-Type" "text/plain"}
:body "Limit exceeded"})
([request respond raise]
(respond (default-limited-handler request))))
(defn default-maintenance-handler
"Provides a default ring response for when the server is enforcing a maintenance mode."
([request]
{:status 503
:headers {"Content-Type" "text/plain"}
:body "Undergoing maintenance"})
([request respond raise]
(respond (default-maintenance-handler request))))
(defn wrap-allow-ips
"Protect a ring handler with source ip authentication. Your allow-list ranges must cover
any permitted clients as well as any intermediate proxy servers. The default allow-list
ranges cover the entire internal network space as defined by RFC 1918 and RFC 4193.
allow-list - cidr ranges collection that, if matched, will result in an allowed request. optionally
provide a ref type in which case it will be dereferenced before use.
deny-handler - a function of a ring request that returns a ring response in the event of a denied request.
"
([handler]
(wrap-allow-ips handler {}))
([handler {:keys [allow-list deny-handler]
:or {allow-list cidr/private-subnets
deny-handler default-forbidden-handler}}]
(fn allow-ips-handler
([request]
(let [client-chain (cidr/client-ip-chain request)]
(if (cidr/client-allowed? client-chain allow-list)
(handler request)
(deny-handler request))))
([request respond raise]
(let [client-chain (cidr/client-ip-chain request)]
(if (cidr/client-allowed? client-chain allow-list)
(handler request respond raise)
(deny-handler request respond raise)))))))
(defn wrap-deny-ips
"Protect a ring handler with source ip authentication. Your deny-list ranges must cover
any forbidden clients / proxy servers. The default deny-list ranges cover the entire
public network space.
deny-list - cidr ranges collection that, if matched, will result in a denied request. optionally
provide a ref type in which case it will be dereferenced before use.
deny-handler - a function of a ring request that returns a ring response in the event of a denied request.
"
([handler]
(wrap-deny-ips handler {}))
([handler {:keys [deny-list deny-handler]
:or {deny-list cidr/public-subnets
deny-handler default-forbidden-handler}}]
(fn deny-ips-handler
([request]
(let [client-chain (cidr/client-ip-chain request)]
(if-not (cidr/client-denied? client-chain deny-list)
(handler request)
(deny-handler request))))
([request respond raise]
(let [client-chain (cidr/client-ip-chain request)]
(if-not (cidr/client-denied? client-chain deny-list)
(handler request respond raise)
(deny-handler request respond raise)))))))
(defn wrap-concurrency-throttle
"Protect a ring handler against excessive concurrency. New requests
after the concurrency limit is already saturated will block until
a slot is available.
max-concurrent - the maximum number of requests to be handled concurrently
ident-fn - a function of a request returning an opaque identifier by which to identify the
semaphore. defaults to a global limit (shared by all clients) but you may set it to
ring-firewall-middleware.core/default-client-ident to implement a per-ip limit
instead or else write your own function to set it to some other group of clients
like those representing one (of many) tenants.
"
([handler]
(wrap-concurrency-throttle handler {}))
([handler {:keys [max-concurrent ident-fn]
:or {max-concurrent 1
ident-fn (constantly :world)}}]
(let [stripe (coord/weak-semaphore-factory max-concurrent)]
(fn concurrency-throttle-handler
([request]
(let [ident (ident-fn request)
semaphore ^Semaphore (stripe ident)]
(try
(.acquire semaphore)
(handler request)
(finally
(.release semaphore)))))
([request respond raise]
(let [ident (ident-fn request)
semaphore ^Semaphore (stripe ident)]
(.acquire semaphore)
(handler request
(fn [response]
(try
(respond response)
(finally
(.release semaphore))))
(fn [exception]
(try
(raise exception)
(finally
(.release semaphore)))))))))))
(defn wrap-concurrency-limit
"Protect a ring handler against excessive concurrency. New requests
after the concurrency limit is already saturated will receive a
denied response.
max-concurrent - the maximum number of requests to be handled concurrently
deny-handler - a function of a ring request that returns a ring response in the event of a denied request.
max-wait - the amount of time (in milliseconds) that a request should wait optimistically before
succeeding or returning with a denied response.
ident-fn - a function of a request returning an opaque identifier by which to identify the
semaphore. defaults to a global limit (shared by all clients) but you may set it to
ring-firewall-middleware.core/default-client-ident to implement a per-ip limit
instead or else write your own function to set it to some other group of clients
like those representing one (of many) tenants.
"
([handler]
(wrap-concurrency-limit handler {}))
([handler {:keys [max-concurrent deny-handler ident-fn max-wait]
:or {max-concurrent 1
deny-handler default-limited-handler
ident-fn (constantly :world)
max-wait 50}}]
(let [stripe (coord/weak-semaphore-factory max-concurrent)]
(fn concurrency-limit-handler
([request]
(let [ident (ident-fn request)
semaphore ^Semaphore (stripe ident)]
(if (.tryAcquire semaphore max-wait TimeUnit/MILLISECONDS)
(try (handler request)
(finally (.release semaphore)))
(deny-handler request))))
([request respond raise]
(let [ident (ident-fn request)
semaphore ^Semaphore (stripe ident)]
(if (.tryAcquire semaphore max-wait TimeUnit/MILLISECONDS)
(handler request
(fn [response]
(try
(respond response)
(finally
(.release semaphore))))
(fn [exception]
(try
(raise exception)
(finally
(.release semaphore)))))
(deny-handler request respond raise))))))))
(defn wrap-rate-throttle
"Protect a ring handler against excessive calls. New requests
that would exceed the rate limit will block until making
them would no longer exceed the rate limit.
max-requests - the maximum number of requests allowed within the time period.
period - the span of the sliding window (in milliseconds) over which requests are counted.
ident-fn - a function of a request returning an opaque identifier by which to identify the
rate limiter. defaults to a global limit (shared by all clients) but you may set it to
ring-firewall-middleware.core/default-client-ident to implement a per-ip limit
instead or else write your own function to set it to some other group of clients
like those representing one (of many) tenants.
"
([handler]
(wrap-rate-throttle handler {}))
([handler {:keys [max-requests period ident-fn]
:or {max-requests 100
period 60000
ident-fn (constantly :world)}}]
(let [striped (coord/weak-leaky-semaphore-factory max-requests period)]
(fn rate-throttle-handler
([request]
(let [ident (ident-fn request)
semaphore ^Semaphore (striped ident)]
(.acquire semaphore)
(handler request)))
([request respond raise]
(let [ident (ident-fn request)
semaphore ^Semaphore (striped ident)]
(.acquire semaphore)
(handler request respond raise)))))))
(defn wrap-rate-limit
"Protect a ring handler against excessive calls. New requests
that would exceed the rate limit will receive a denied response.
max-requests - the maximum number of requests allowed within the time period.
deny-handler - a function of a ring request that returns a ring response in the event of a denied request.
period - the span of the sliding window (in milliseconds) over which requests are counted.
max-wait - the amount of time (in milliseconds) that a request should wait optimistically before
succeeding or returning with a denied response.
ident-fn - a function of a request returning an opaque identifier by which to identify the
rate limiter. defaults to a global limit (shared by all clients) but you may set it to
ring-firewall-middleware.core/default-client-ident to implement a per-ip limit
instead or else write your own function to set it to some other group of clients
like those representing one (of many) tenants.
"
([handler]
(wrap-rate-limit handler {}))
([handler {:keys [max-requests period deny-handler ident-fn max-wait]
:or {max-requests 500
period 60000
ident-fn (constantly :world)
deny-handler default-limited-handler
max-wait 50}}]
(let [striped (coord/weak-leaky-semaphore-factory max-requests period)]
(fn rate-limit-handler
([request]
(let [ident (ident-fn request)
semaphore ^Semaphore (striped ident)]
(if (.tryAcquire semaphore max-wait TimeUnit/MILLISECONDS)
(handler request)
(deny-handler request))))
([request respond raise]
(let [ident (ident-fn request)
semaphore ^Semaphore (striped ident)]
(if (.tryAcquire semaphore max-wait TimeUnit/MILLISECONDS)
(handler request respond raise)
(deny-handler request respond raise))))))))
(defonce STATE (atom {}))
(defn new-state []
(let [lock (ReentrantReadWriteLock.)]
{:read (.readLock lock) :write (.writeLock lock)}))
(defn get-maintenance-state [ident]
(-> (swap! STATE update ident #(or % (new-state)))
(get ident)))
(defn wrap-maintenance-throttle
"Middleware that coordinates requests to establish a maintenance mode when
requested. When maintenance throttle is enabled any new requests will block
but in-flight requests will be given a chance to finish prior to maintenance
activities beginning.
ident-fn - a function of a request returning an opaque identifier by which to identify the
request group that may be flipped into maintenance mode. useful if applying
maintenance mode to one (of many) tenants at a time.
"
([handler]
(wrap-maintenance-throttle handler {}))
([handler {:keys [ident-fn]
:or {ident-fn (constantly :world)}}]
(fn maintenance-throttle-handler
([request]
(let [{:keys [^Lock read]} (get-maintenance-state (ident-fn request))]
(try
(.lock read)
(handler request)
(finally (.unlock read)))))
([request respond raise]
(let [{:keys [^Lock read]} (get-maintenance-state (ident-fn request))]
(.lock read)
(handler request
(fn [response]
(try (respond response)
(finally (.unlock read))))
(fn [exception]
(try (raise exception)
(finally (.unlock read))))))))))
(defn wrap-maintenance-limit
"Middleware that coordinates requests to establish a maintenance mode when
requested. When maintenance mode is enabled any new requests will be denied
but in-flight requests will be given a chance to finish prior to maintenance
activities beginning.
ident-fn - a function of a request returning an opaque identifier by which to identify the
request group that may be flipped into maintenance mode. useful if applying
maintenance mode to one (of many) tenants at a time.
deny-handler - a ring handler that should produce a response for requests that were denied due
to being in maintenance mode.
max-wait - the amount of time (in milliseconds) that a request should wait optimistically before
succeeding or returning with a denied response.
"
([handler]
(wrap-maintenance-limit handler {}))
([handler {:keys [ident-fn deny-handler max-wait]
:or {ident-fn (constantly :world)
deny-handler default-maintenance-handler
max-wait 50}}]
(fn maintenance-limit-handler
([request]
(let [{:keys [^Lock read]} (get-maintenance-state (ident-fn request))]
(if (.tryLock read max-wait TimeUnit/MILLISECONDS)
(try
(handler request)
(finally (.unlock read)))
(deny-handler request))))
([request respond raise]
(let [{:keys [^Lock read]} (get-maintenance-state (ident-fn request))]
(if (.tryLock read max-wait TimeUnit/MILLISECONDS)
(handler request
(fn [response]
(try (respond response)
(finally (.unlock read))))
(fn [exception]
(try (raise exception)
(finally (.unlock read)))))
(deny-handler request respond raise)))))))
(defmacro with-maintenance-mode
"Enables maintenance mode for the given identity and
executes body after all in-flight requests have
completed."
[ident & body]
`(let [^Lock lock# (:write (get-maintenance-state ~ident))]
(try
(.lock lock#)
~@body
(finally
(.unlock lock#)))))