]> git.ipfire.org Git - thirdparty/pdns.git/blame - regression-tests.recursor-dnssec/test_RPZ.py
dnsdist doc typo fix
[thirdparty/pdns.git] / regression-tests.recursor-dnssec / test_RPZ.py
CommitLineData
f9017ec1 1import dns
22cf3506 2import json
f9017ec1 3import os
22cf3506 4import requests
f9017ec1
RG
5import socket
6import struct
7import sys
8import threading
9import time
10
11from recursortests import RecursorTest
12
13class RPZServer(object):
14
15 def __init__(self, port):
16 self._currentSerial = 0
17 self._targetSerial = 1
18 self._serverPort = port
19 listener = threading.Thread(name='RPZ Listener', target=self._listener, args=[])
20 listener.setDaemon(True)
21 listener.start()
22
23 def getCurrentSerial(self):
24 return self._currentSerial
25
26 def moveToSerial(self, newSerial):
27 if newSerial == self._currentSerial:
28 return False
29
30 if newSerial != self._currentSerial + 1:
31 raise AssertionError("Asking the RPZ server to server serial %d, already serving %d" % (newSerial, self._currentSerial))
32 self._targetSerial = newSerial
33 return True
34
35 def _getAnswer(self, message):
36
37 response = dns.message.make_response(message)
38 records = []
39
40 if message.question[0].rdtype == dns.rdatatype.AXFR:
41 if self._currentSerial != 0:
42 print('Received an AXFR query but IXFR expected because the current serial is %d' % (self._currentSerial))
43 return (None, self._currentSerial)
44
45 newSerial = self._targetSerial
46 records = [
47 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
48 dns.rrset.from_text('a.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1'),
49 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial)
50 ]
51
52 elif message.question[0].rdtype == dns.rdatatype.IXFR:
53 oldSerial = message.authority[0][0].serial
54
55 if oldSerial != self._currentSerial:
56 print('Received an IXFR query with an unexpected serial %d, expected %d' % (oldSerial, self._currentSerial))
57 return (None, self._currentSerial)
58
59 newSerial = self._targetSerial
60 if newSerial == 2:
61 records = [
62 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
63 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % oldSerial),
64 # no deletion
65 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
66 dns.rrset.from_text('b.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1'),
67 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial)
68 ]
69 elif newSerial == 3:
70 records = [
71 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
72 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % oldSerial),
73 dns.rrset.from_text('a.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1'),
74 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
75 # no addition
76 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial)
77 ]
78 elif newSerial == 4:
79 records = [
80 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
81 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % oldSerial),
82 dns.rrset.from_text('b.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1'),
83 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
84 dns.rrset.from_text('c.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1'),
85 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial)
86 ]
22cf3506
RG
87 elif newSerial == 5:
88 # this one is a bit special, we are answering with a full AXFR
89 records = [
90 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
91 dns.rrset.from_text('d.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1'),
8340237f
RG
92 dns.rrset.from_text('tc.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.CNAME, 'rpz-tcp-only.'),
93 dns.rrset.from_text('drop.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.CNAME, 'rpz-drop.'),
22cf3506
RG
94 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial)
95 ]
96 elif newSerial == 6:
97 # back to IXFR
98 records = [
99 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
100 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % oldSerial),
101 dns.rrset.from_text('d.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1'),
8340237f
RG
102 dns.rrset.from_text('tc.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.CNAME, 'rpz-tcp-only.'),
103 dns.rrset.from_text('drop.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.CNAME, 'rpz-drop.'),
22cf3506 104 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
6da513b2
RG
105 dns.rrset.from_text('e.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1', '192.0.2.2'),
106 dns.rrset.from_text('e.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.MX, '10 mx.example.'),
107 dns.rrset.from_text('f.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.CNAME, 'e.example.'),
108 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial)
109 ]
110 elif newSerial == 7:
111 records = [
112 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
113 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % oldSerial),
114 dns.rrset.from_text('e.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1', '192.0.2.2'),
115 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
116 dns.rrset.from_text('e.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.2'),
d13c4d18
RG
117 dns.rrset.from_text('tc.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.CNAME, 'rpz-tcp-only.'),
118 dns.rrset.from_text('drop.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.CNAME, 'rpz-drop.'),
22cf3506
RG
119 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial)
120 ]
98b33176
RG
121 elif newSerial == 8:
122 # this one is a bit special too, we are answering with a full AXFR and the new zone is empty
123 records = [
124 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
125 dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial)
126 ]
f9017ec1
RG
127
128 response.answer = records
129 return (newSerial, response)
130
131 def _connectionHandler(self, conn):
132 data = None
133 while True:
134 data = conn.recv(2)
135 if not data:
136 break
137 (datalen,) = struct.unpack("!H", data)
138 data = conn.recv(datalen)
139 if not data:
140 break
141
142 message = dns.message.from_wire(data)
143 if len(message.question) != 1:
144 print('Invalid RPZ query, qdcount is %d' % (len(message.question)))
145 break
146 if not message.question[0].rdtype in [dns.rdatatype.AXFR, dns.rdatatype.IXFR]:
147 print('Invalid RPZ query, qtype is %d' % (message.question.rdtype))
148 break
149 (serial, answer) = self._getAnswer(message)
150 if not answer:
151 print('Unable to get a response for %s %d' % (message.question[0].name, message.question[0].rdtype))
152 break
153
154 wire = answer.to_wire()
155 conn.send(struct.pack("!H", len(wire)))
156 conn.send(wire)
157 self._currentSerial = serial
158 break
159
160 conn.close()
161
162 def _listener(self):
163 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
164 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
165 try:
166 sock.bind(("127.0.0.1", self._serverPort))
167 except socket.error as e:
168 print("Error binding in the RPZ listener: %s" % str(e))
169 sys.exit(1)
170
171 sock.listen(100)
172 while True:
173 try:
174 (conn, _) = sock.accept()
175 thread = threading.Thread(name='RPZ Connection Handler',
176 target=self._connectionHandler,
177 args=[conn])
178 thread.setDaemon(True)
179 thread.start()
180
181 except socket.error as e:
182 print('Error in RPZ socket: %s' % str(e))
183 sock.close()
184
f9017ec1 185class RPZRecursorTest(RecursorTest):
22cf3506
RG
186 _wsPort = 8042
187 _wsTimeout = 2
188 _wsPassword = 'secretpassword'
189 _apiKey = 'secretapikey'
f9017ec1 190 _confdir = 'RPZ'
d19bcbf0
RG
191 _lua_dns_script_file = """
192
193 function prerpz(dq)
194 -- disable the RPZ policy named 'zone.rpz' for AD=1 queries
195 if dq:getDH():getAD() then
196 dq:discardPolicy('zone.rpz.')
197 end
198 return false
199 end
200 """
201
f9017ec1 202 _config_template = """
22cf3506
RG
203auth-zones=example=configs/%s/example.zone
204webserver=yes
205webserver-port=%d
206webserver-address=127.0.0.1
207webserver-password=%s
208api-key=%s
98b33176 209log-rpz-changes=yes
22cf3506 210""" % (_confdir, _wsPort, _wsPassword, _apiKey)
f9017ec1
RG
211
212 @classmethod
213 def setUpClass(cls):
214
215 cls.setUpSockets()
216 cls.startResponders()
217
218 confdir = os.path.join('configs', cls._confdir)
219 cls.createConfigDir(confdir)
220
221 cls.generateRecursorConfig(confdir)
222 cls.startRecursor(confdir, cls._recursorPort)
223
224 @classmethod
225 def tearDownClass(cls):
226 cls.tearDownRecursor()
227
d19bcbf0 228 def checkBlocked(self, name, shouldBeBlocked=True, adQuery=False):
f9017ec1
RG
229 query = dns.message.make_query(name, 'A', want_dnssec=True)
230 query.flags |= dns.flags.CD
d19bcbf0
RG
231 if adQuery:
232 query.flags |= dns.flags.AD
f9017ec1 233
d13c4d18
RG
234 for method in ("sendUDPQuery", "sendTCPQuery"):
235 sender = getattr(self, method)
236 res = sender(query)
237 self.assertRcodeEqual(res, dns.rcode.NOERROR)
238 if shouldBeBlocked:
239 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.1')
240 else:
241 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
242
243 self.assertRRsetInAnswer(res, expected)
f9017ec1 244
d19bcbf0
RG
245 def checkNotBlocked(self, name, adQuery=False):
246 self.checkBlocked(name, False, adQuery)
f9017ec1 247
6da513b2
RG
248 def checkCustom(self, qname, qtype, expected):
249 query = dns.message.make_query(qname, qtype, want_dnssec=True)
250 query.flags |= dns.flags.CD
d13c4d18
RG
251 for method in ("sendUDPQuery", "sendTCPQuery"):
252 sender = getattr(self, method)
253 res = sender(query)
254 self.assertRcodeEqual(res, dns.rcode.NOERROR)
255 self.assertRRsetInAnswer(res, expected)
6da513b2
RG
256
257 def checkNoData(self, qname, qtype):
d13c4d18
RG
258 query = dns.message.make_query(qname, qtype, want_dnssec=True)
259 query.flags |= dns.flags.CD
260 for method in ("sendUDPQuery", "sendTCPQuery"):
261 sender = getattr(self, method)
262 res = sender(query)
263 self.assertRcodeEqual(res, dns.rcode.NOERROR)
264 self.assertEqual(len(res.answer), 0)
265
98b33176 266 def checkNXD(self, qname, qtype='A'):
d122dac0
RG
267 query = dns.message.make_query(qname, qtype, want_dnssec=True)
268 query.flags |= dns.flags.CD
269 for method in ("sendUDPQuery", "sendTCPQuery"):
270 sender = getattr(self, method)
271 res = sender(query)
272 self.assertRcodeEqual(res, dns.rcode.NXDOMAIN)
273 self.assertEqual(len(res.answer), 0)
274 self.assertEqual(len(res.authority), 1)
275
d13c4d18 276 def checkTruncated(self, qname, qtype='A'):
6da513b2
RG
277 query = dns.message.make_query(qname, qtype, want_dnssec=True)
278 query.flags |= dns.flags.CD
279 res = self.sendUDPQuery(query)
d13c4d18
RG
280 self.assertRcodeEqual(res, dns.rcode.NOERROR)
281 self.assertMessageHasFlags(res, ['QR', 'RA', 'RD', 'CD', 'TC'])
282 self.assertEqual(len(res.answer), 0)
283 self.assertEqual(len(res.authority), 0)
284 self.assertEqual(len(res.additional), 0)
6da513b2 285
d13c4d18
RG
286 res = self.sendTCPQuery(query)
287 self.assertRcodeEqual(res, dns.rcode.NXDOMAIN)
288 self.assertMessageHasFlags(res, ['QR', 'RA', 'RD', 'CD'])
6da513b2 289 self.assertEqual(len(res.answer), 0)
d13c4d18
RG
290 self.assertEqual(len(res.authority), 1)
291 self.assertEqual(len(res.additional), 0)
292
293 def checkDropped(self, qname, qtype='A'):
294 query = dns.message.make_query(qname, qtype, want_dnssec=True)
295 query.flags |= dns.flags.CD
296 for method in ("sendUDPQuery", "sendTCPQuery"):
297 sender = getattr(self, method)
298 res = sender(query)
299 self.assertEqual(res, None)
6da513b2 300
d122dac0
RG
301 def checkRPZStats(self, serial, recordsCount, fullXFRCount, totalXFRCount):
302 headers = {'x-api-key': self._apiKey}
303 url = 'http://127.0.0.1:' + str(self._wsPort) + '/api/v1/servers/localhost/rpzstatistics'
304 r = requests.get(url, headers=headers, timeout=self._wsTimeout)
305 self.assertTrue(r)
306 self.assertEquals(r.status_code, 200)
307 self.assertTrue(r.json())
308 content = r.json()
309 self.assertIn('zone.rpz.', content)
310 zone = content['zone.rpz.']
311 for key in ['last_update', 'records', 'serial', 'transfers_failed', 'transfers_full', 'transfers_success']:
312 self.assertIn(key, zone)
313
314 self.assertEquals(zone['serial'], serial)
315 self.assertEquals(zone['records'], recordsCount)
316 self.assertEquals(zone['transfers_full'], fullXFRCount)
317 self.assertEquals(zone['transfers_success'], totalXFRCount)
318
319rpzServerPort = 4250
320rpzServer = RPZServer(rpzServerPort)
321
322class RPZXFRRecursorTest(RPZRecursorTest):
323 """
324 This test makes sure that we correctly update RPZ zones via AXFR then IXFR
325 """
326
327 global rpzServerPort
328 _lua_config_file = """
329 -- The first server is a bogus one, to test that we correctly fail over to the second one
330 rpzMaster({'127.0.0.1:9999', '127.0.0.1:%d'}, 'zone.rpz.', { refresh=1 })
331 """ % (rpzServerPort)
332 _confdir = 'RPZXFR'
333 _wsPort = 8042
334 _wsTimeout = 2
335 _wsPassword = 'secretpassword'
336 _apiKey = 'secretapikey'
337 _config_template = """
338auth-zones=example=configs/%s/example.zone
339webserver=yes
340webserver-port=%d
341webserver-address=127.0.0.1
342webserver-password=%s
343api-key=%s
344""" % (_confdir, _wsPort, _wsPassword, _apiKey)
345 _xfrDone = 0
346
347 @classmethod
348 def generateRecursorConfig(cls, confdir):
349 authzonepath = os.path.join(confdir, 'example.zone')
350 with open(authzonepath, 'w') as authzone:
351 authzone.write("""$ORIGIN example.
352@ 3600 IN SOA {soa}
353a 3600 IN A 192.0.2.42
354b 3600 IN A 192.0.2.42
355c 3600 IN A 192.0.2.42
356d 3600 IN A 192.0.2.42
357e 3600 IN A 192.0.2.42
358""".format(soa=cls._SOA))
359 super(RPZRecursorTest, cls).generateRecursorConfig(confdir)
360
f9017ec1
RG
361 def waitUntilCorrectSerialIsLoaded(self, serial, timeout=5):
362 global rpzServer
363
364 rpzServer.moveToSerial(serial)
365
366 attempts = 0
367 while attempts < timeout:
368 currentSerial = rpzServer.getCurrentSerial()
369 if currentSerial > serial:
370 raise AssertionError("Expected serial %d, got %d" % (serial, currentSerial))
371 if currentSerial == serial:
22cf3506 372 self._xfrDone = self._xfrDone + 1
f9017ec1
RG
373 return
374
375 attempts = attempts + 1
376 time.sleep(1)
377
378 raise AssertionError("Waited %d seconds for the serial to be updated to %d but the serial is still %d" % (timeout, serial, currentSerial))
379
380 def testRPZ(self):
381 # first zone, only a should be blocked
382 self.waitUntilCorrectSerialIsLoaded(1)
22cf3506 383 self.checkRPZStats(1, 1, 1, self._xfrDone)
f9017ec1
RG
384 self.checkBlocked('a.example.')
385 self.checkNotBlocked('b.example.')
386 self.checkNotBlocked('c.example.')
387
388 # second zone, a and b should be blocked
389 self.waitUntilCorrectSerialIsLoaded(2)
22cf3506 390 self.checkRPZStats(2, 2, 1, self._xfrDone)
f9017ec1
RG
391 self.checkBlocked('a.example.')
392 self.checkBlocked('b.example.')
393 self.checkNotBlocked('c.example.')
394
395 # third zone, only b should be blocked
396 self.waitUntilCorrectSerialIsLoaded(3)
22cf3506 397 self.checkRPZStats(3, 1, 1, self._xfrDone)
f9017ec1
RG
398 self.checkNotBlocked('a.example.')
399 self.checkBlocked('b.example.')
400 self.checkNotBlocked('c.example.')
401
402 # fourth zone, only c should be blocked
403 self.waitUntilCorrectSerialIsLoaded(4)
22cf3506 404 self.checkRPZStats(4, 1, 1, self._xfrDone)
f9017ec1
RG
405 self.checkNotBlocked('a.example.')
406 self.checkNotBlocked('b.example.')
407 self.checkBlocked('c.example.')
22cf3506
RG
408
409 # fifth zone, we should get a full AXFR this time, and only d should be blocked
410 self.waitUntilCorrectSerialIsLoaded(5)
8340237f 411 self.checkRPZStats(5, 3, 2, self._xfrDone)
22cf3506
RG
412 self.checkNotBlocked('a.example.')
413 self.checkNotBlocked('b.example.')
414 self.checkNotBlocked('c.example.')
415 self.checkBlocked('d.example.')
416
6da513b2 417 # sixth zone, only e should be blocked, f is a local data record
22cf3506 418 self.waitUntilCorrectSerialIsLoaded(6)
6da513b2
RG
419 self.checkRPZStats(6, 2, 2, self._xfrDone)
420 self.checkNotBlocked('a.example.')
421 self.checkNotBlocked('b.example.')
422 self.checkNotBlocked('c.example.')
423 self.checkNotBlocked('d.example.')
424 self.checkCustom('e.example.', 'A', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.1', '192.0.2.2'))
425 self.checkCustom('e.example.', 'MX', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'MX', '10 mx.example.'))
426 self.checkNoData('e.example.', 'AAAA')
427 self.checkCustom('f.example.', 'A', dns.rrset.from_text('f.example.', 0, dns.rdataclass.IN, 'CNAME', 'e.example.'))
428
429 # seventh zone, e should only have one A
430 self.waitUntilCorrectSerialIsLoaded(7)
d13c4d18 431 self.checkRPZStats(7, 4, 2, self._xfrDone)
22cf3506
RG
432 self.checkNotBlocked('a.example.')
433 self.checkNotBlocked('b.example.')
434 self.checkNotBlocked('c.example.')
435 self.checkNotBlocked('d.example.')
6da513b2
RG
436 self.checkCustom('e.example.', 'A', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.2'))
437 self.checkCustom('e.example.', 'MX', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'MX', '10 mx.example.'))
438 self.checkNoData('e.example.', 'AAAA')
439 self.checkCustom('f.example.', 'A', dns.rrset.from_text('f.example.', 0, dns.rdataclass.IN, 'CNAME', 'e.example.'))
d19bcbf0
RG
440 # check that the policy is disabled for AD=1 queries
441 self.checkNotBlocked('e.example.', True)
d13c4d18
RG
442 # check non-custom policies
443 self.checkTruncated('tc.example.')
444 self.checkDropped('drop.example.')
d122dac0 445
98b33176
RG
446 # eighth zone, all entries should be gone
447 self.waitUntilCorrectSerialIsLoaded(8)
448 self.checkRPZStats(8, 0, 3, self._xfrDone)
449 self.checkNotBlocked('a.example.')
450 self.checkNotBlocked('b.example.')
451 self.checkNotBlocked('c.example.')
452 self.checkNotBlocked('d.example.')
453 self.checkNotBlocked('e.example.')
454 self.checkNXD('f.example.')
455 self.checkNXD('tc.example.')
456 self.checkNXD('drop.example.')
457
d122dac0
RG
458class RPZFileRecursorTest(RPZRecursorTest):
459 """
460 This test makes sure that we correctly load RPZ zones from a file
461 """
462
463 _confdir = 'RPZFile'
464 _wsPort = 8042
465 _wsTimeout = 2
466 _wsPassword = 'secretpassword'
467 _apiKey = 'secretapikey'
468 _lua_config_file = """
469 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz." })
470 """ % (_confdir)
471 _config_template = """
472auth-zones=example=configs/%s/example.zone
473webserver=yes
474webserver-port=%d
475webserver-address=127.0.0.1
476webserver-password=%s
477api-key=%s
478""" % (_confdir, _wsPort, _wsPassword, _apiKey)
479
480 @classmethod
481 def generateRecursorConfig(cls, confdir):
482 authzonepath = os.path.join(confdir, 'example.zone')
483 with open(authzonepath, 'w') as authzone:
484 authzone.write("""$ORIGIN example.
485@ 3600 IN SOA {soa}
486a 3600 IN A 192.0.2.42
487b 3600 IN A 192.0.2.42
488c 3600 IN A 192.0.2.42
489d 3600 IN A 192.0.2.42
490e 3600 IN A 192.0.2.42
491z 3600 IN A 192.0.2.42
492""".format(soa=cls._SOA))
493
494 rpzFilePath = os.path.join(confdir, 'zone.rpz')
495 with open(rpzFilePath, 'w') as rpzZone:
496 rpzZone.write("""$ORIGIN zone.rpz.
497@ 3600 IN SOA {soa}
498a.example.zone.rpz. 60 IN A 192.0.2.42
499a.example.zone.rpz. 60 IN A 192.0.2.43
500a.example.zone.rpz. 60 IN TXT "some text"
501drop.example.zone.rpz. 60 IN CNAME rpz-drop.
502z.example.zone.rpz. 60 IN A 192.0.2.1
503tc.example.zone.rpz. 60 IN CNAME rpz-tcp-only.
504""".format(soa=cls._SOA))
505 super(RPZFileRecursorTest, cls).generateRecursorConfig(confdir)
506
507 def testRPZ(self):
508 self.checkCustom('a.example.', 'A', dns.rrset.from_text('a.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.42', '192.0.2.43'))
509 self.checkCustom('a.example.', 'TXT', dns.rrset.from_text('a.example.', 0, dns.rdataclass.IN, 'TXT', '"some text"'))
510 self.checkBlocked('z.example.')
511 self.checkNotBlocked('b.example.')
512 self.checkNotBlocked('c.example.')
513 self.checkNotBlocked('d.example.')
514 self.checkNotBlocked('e.example.')
515 # check that the policy is disabled for AD=1 queries
516 self.checkNotBlocked('z.example.', True)
517 # check non-custom policies
518 self.checkTruncated('tc.example.')
519 self.checkDropped('drop.example.')
520
521class RPZFileDefaultPolRecursorTest(RPZRecursorTest):
522 """
523 This test makes sure that we correctly load RPZ zones from a file with a default policy
524 """
525
526 _confdir = 'RPZFileDefaultPolicy'
527 _wsPort = 8042
528 _wsTimeout = 2
529 _wsPassword = 'secretpassword'
530 _apiKey = 'secretapikey'
531 _lua_config_file = """
532 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz.", defpol=Policy.NoAction })
533 """ % (_confdir)
534 _config_template = """
535auth-zones=example=configs/%s/example.zone
536webserver=yes
537webserver-port=%d
538webserver-address=127.0.0.1
539webserver-password=%s
540api-key=%s
541""" % (_confdir, _wsPort, _wsPassword, _apiKey)
542
543 @classmethod
544 def generateRecursorConfig(cls, confdir):
545 authzonepath = os.path.join(confdir, 'example.zone')
546 with open(authzonepath, 'w') as authzone:
547 authzone.write("""$ORIGIN example.
548@ 3600 IN SOA {soa}
549a 3600 IN A 192.0.2.42
550b 3600 IN A 192.0.2.42
551c 3600 IN A 192.0.2.42
552d 3600 IN A 192.0.2.42
553drop 3600 IN A 192.0.2.42
554e 3600 IN A 192.0.2.42
555z 3600 IN A 192.0.2.42
556""".format(soa=cls._SOA))
557
558 rpzFilePath = os.path.join(confdir, 'zone.rpz')
559 with open(rpzFilePath, 'w') as rpzZone:
560 rpzZone.write("""$ORIGIN zone.rpz.
561@ 3600 IN SOA {soa}
562a.example.zone.rpz. 60 IN A 192.0.2.42
563drop.example.zone.rpz. 60 IN CNAME rpz-drop.
564z.example.zone.rpz. 60 IN A 192.0.2.1
565tc.example.zone.rpz. 60 IN CNAME rpz-tcp-only.
566""".format(soa=cls._SOA))
567 super(RPZFileDefaultPolRecursorTest, cls).generateRecursorConfig(confdir)
568
569 def testRPZ(self):
570 # local data entries are overridden by default
571 self.checkCustom('a.example.', 'A', dns.rrset.from_text('a.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.42'))
572 self.checkNoData('a.example.', 'TXT')
573 # will not be blocked because the default policy overrides local data entries by default
574 self.checkNotBlocked('z.example.')
575 self.checkNotBlocked('b.example.')
576 self.checkNotBlocked('c.example.')
577 self.checkNotBlocked('d.example.')
578 self.checkNotBlocked('e.example.')
579 # check non-local policies, they should be overridden by the default policy
580 self.checkNXD('tc.example.', 'A')
581 self.checkNotBlocked('drop.example.')
582
583class RPZFileDefaultPolNotOverrideLocalRecursorTest(RPZRecursorTest):
584 """
585 This test makes sure that we correctly load RPZ zones from a file with a default policy, not overriding local data entries
586 """
587
588 _confdir = 'RPZFileDefaultPolicyNotOverrideLocal'
589 _wsPort = 8042
590 _wsTimeout = 2
591 _wsPassword = 'secretpassword'
592 _apiKey = 'secretapikey'
593 _lua_config_file = """
594 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz.", defpol=Policy.NoAction, defpolOverrideLocalData=false })
595 """ % (_confdir)
596 _config_template = """
597auth-zones=example=configs/%s/example.zone
598webserver=yes
599webserver-port=%d
600webserver-address=127.0.0.1
601webserver-password=%s
602api-key=%s
603""" % (_confdir, _wsPort, _wsPassword, _apiKey)
604
605 @classmethod
606 def generateRecursorConfig(cls, confdir):
607 authzonepath = os.path.join(confdir, 'example.zone')
608 with open(authzonepath, 'w') as authzone:
609 authzone.write("""$ORIGIN example.
610@ 3600 IN SOA {soa}
611a 3600 IN A 192.0.2.42
612b 3600 IN A 192.0.2.42
613c 3600 IN A 192.0.2.42
614d 3600 IN A 192.0.2.42
615drop 3600 IN A 192.0.2.42
616e 3600 IN A 192.0.2.42
617z 3600 IN A 192.0.2.42
618""".format(soa=cls._SOA))
619
620 rpzFilePath = os.path.join(confdir, 'zone.rpz')
621 with open(rpzFilePath, 'w') as rpzZone:
622 rpzZone.write("""$ORIGIN zone.rpz.
623@ 3600 IN SOA {soa}
624a.example.zone.rpz. 60 IN A 192.0.2.42
625a.example.zone.rpz. 60 IN A 192.0.2.43
626a.example.zone.rpz. 60 IN TXT "some text"
627drop.example.zone.rpz. 60 IN CNAME rpz-drop.
628z.example.zone.rpz. 60 IN A 192.0.2.1
629tc.example.zone.rpz. 60 IN CNAME rpz-tcp-only.
630""".format(soa=cls._SOA))
631 super(RPZFileDefaultPolNotOverrideLocalRecursorTest, cls).generateRecursorConfig(confdir)
632
633 def testRPZ(self):
634 # local data entries will not be overridden by the default polic
635 self.checkCustom('a.example.', 'A', dns.rrset.from_text('a.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.42', '192.0.2.43'))
636 self.checkCustom('a.example.', 'TXT', dns.rrset.from_text('a.example.', 0, dns.rdataclass.IN, 'TXT', '"some text"'))
637 # will be blocked because the default policy does not override local data entries
638 self.checkBlocked('z.example.')
639 self.checkNotBlocked('b.example.')
640 self.checkNotBlocked('c.example.')
641 self.checkNotBlocked('d.example.')
642 self.checkNotBlocked('e.example.')
643 # check non-local policies, they should be overridden by the default policy
644 self.checkNXD('tc.example.', 'A')
645 self.checkNotBlocked('drop.example.')