]>
Commit | Line | Data |
---|---|---|
9d71a0cf | 1 | #!/usr/bin/env python |
c4c72a2c RG |
2 | import base64 |
3 | import copy | |
9d71a0cf RG |
4 | import dns |
5 | import requests | |
6 | import ssl | |
7 | import threading | |
8 | import time | |
9 | ||
630eb526 | 10 | from dnsdisttests import DNSDistTest, pickAvailablePort |
9d71a0cf RG |
11 | |
12 | class OutgoingDOHTests(object): | |
13 | ||
14 | _webTimeout = 2.0 | |
630eb526 | 15 | _webServerPort = pickAvailablePort() |
9d71a0cf RG |
16 | _webServerBasicAuthPassword = 'secret' |
17 | _webServerAPIKey = 'apisecret' | |
7b8a394a RG |
18 | _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM=' |
19 | _webServerAPIKeyHashed = '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso=' | |
9d71a0cf RG |
20 | |
21 | def checkOnlyDOHResponderHit(self, numberOfDOHQueries=1): | |
22 | self.assertNotIn('UDP Responder', self._responsesCounter) | |
23 | self.assertNotIn('TCP Responder', self._responsesCounter) | |
24 | self.assertNotIn('TLS Responder', self._responsesCounter) | |
c4c72a2c | 25 | self.assertEqual(self._responsesCounter['DoH Connection Handler'], numberOfDOHQueries) |
9d71a0cf RG |
26 | |
27 | def getServerStat(self, key): | |
28 | headers = {'x-api-key': self._webServerAPIKey} | |
29 | url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost' | |
30 | r = requests.get(url, headers=headers, timeout=self._webTimeout) | |
31 | self.assertTrue(r) | |
32 | self.assertEqual(r.status_code, 200) | |
33 | self.assertTrue(r.json()) | |
34 | content = r.json() | |
35 | self.assertTrue(len(content['servers']), 1) | |
36 | server = content['servers'][0] | |
37 | self.assertIn(key, server) | |
38 | return server[key] | |
39 | ||
40 | def testUDP(self): | |
41 | """ | |
42 | Outgoing DOH: UDP query is sent via DOH | |
43 | """ | |
44 | name = 'udp.outgoing-doh.test.powerdns.com.' | |
45 | query = dns.message.make_query(name, 'A', 'IN') | |
46 | expectedResponse = dns.message.make_response(query) | |
47 | rrset = dns.rrset.from_text(name, | |
48 | 60, | |
49 | dns.rdataclass.IN, | |
50 | dns.rdatatype.A, | |
51 | '127.0.0.1') | |
52 | expectedResponse.answer.append(rrset) | |
53 | ||
249f1981 RG |
54 | connsBefore = self.getServerStat('tcpReusedConnections') |
55 | ||
9d71a0cf RG |
56 | numberOfUDPQueries = 10 |
57 | for _ in range(numberOfUDPQueries): | |
58 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse) | |
59 | self.assertEqual(query, receivedQuery) | |
60 | self.assertEqual(receivedResponse, expectedResponse) | |
61 | ||
62 | # there was one TCP query in testTCP (below, but before in alphabetical order) | |
63 | numberOfQueries = numberOfUDPQueries + 1 | |
64 | self.checkOnlyDOHResponderHit(numberOfUDPQueries) | |
65 | ||
66 | self.assertEqual(self.getServerStat('tcpNewConnections'), 1) | |
249f1981 | 67 | self.assertEqual(self.getServerStat('tcpReusedConnections'), connsBefore + numberOfQueries - 1) |
9d71a0cf RG |
68 | self.assertEqual(self.getServerStat('tlsResumptions'), 0) |
69 | ||
70 | def testTCP(self): | |
71 | """ | |
72 | Outgoing DOH: TCP query is sent via DOH | |
73 | """ | |
74 | name = 'tcp.outgoing-doh.test.powerdns.com.' | |
75 | query = dns.message.make_query(name, 'A', 'IN') | |
76 | expectedResponse = dns.message.make_response(query) | |
77 | rrset = dns.rrset.from_text(name, | |
78 | 60, | |
79 | dns.rdataclass.IN, | |
80 | dns.rdatatype.A, | |
81 | '127.0.0.1') | |
82 | expectedResponse.answer.append(rrset) | |
83 | ||
249f1981 RG |
84 | connsBefore = self.getServerStat('tcpReusedConnections') |
85 | ||
9d71a0cf RG |
86 | (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse) |
87 | self.assertEqual(query, receivedQuery) | |
88 | self.assertEqual(receivedResponse, expectedResponse) | |
89 | self.checkOnlyDOHResponderHit() | |
90 | self.assertEqual(self.getServerStat('tcpNewConnections'), 1) | |
249f1981 | 91 | self.assertEqual(self.getServerStat('tcpReusedConnections'), connsBefore) |
9d71a0cf RG |
92 | self.assertEqual(self.getServerStat('tlsResumptions'), 0) |
93 | ||
249f1981 RG |
94 | def testUDPCache(self): |
95 | """ | |
96 | Outgoing DOH: UDP query is sent via DOH, should be cached | |
97 | """ | |
98 | name = 'udp.cached.outgoing-doh.test.powerdns.com.' | |
99 | query = dns.message.make_query(name, 'A', 'IN') | |
100 | expectedResponse = dns.message.make_response(query) | |
101 | rrset = dns.rrset.from_text(name, | |
102 | 60, | |
103 | dns.rdataclass.IN, | |
104 | dns.rdatatype.A, | |
105 | '127.0.0.1') | |
106 | expectedResponse.answer.append(rrset) | |
107 | ||
108 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse) | |
109 | self.assertEqual(query, receivedQuery) | |
110 | self.assertEqual(receivedResponse, expectedResponse) | |
111 | ||
112 | numberOfUDPQueries = 10 | |
113 | for _ in range(numberOfUDPQueries): | |
114 | (_, receivedResponse) = self.sendUDPQuery(query, useQueue=False, response=None) | |
115 | self.assertEqual(receivedResponse, expectedResponse) | |
116 | ||
117 | def testTCPCache(self): | |
118 | """ | |
119 | Outgoing DOH: TCP query is sent via DOH, should be cached | |
120 | """ | |
121 | name = 'tcp.cached.outgoing-doh.test.powerdns.com.' | |
122 | query = dns.message.make_query(name, 'A', 'IN') | |
123 | expectedResponse = dns.message.make_response(query) | |
124 | rrset = dns.rrset.from_text(name, | |
125 | 60, | |
126 | dns.rdataclass.IN, | |
127 | dns.rdatatype.A, | |
128 | '127.0.0.1') | |
129 | expectedResponse.answer.append(rrset) | |
130 | ||
131 | (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse) | |
132 | self.assertEqual(query, receivedQuery) | |
133 | self.assertEqual(receivedResponse, expectedResponse) | |
134 | ||
135 | numberOfTCPQueries = 10 | |
136 | for _ in range(numberOfTCPQueries): | |
137 | (_, receivedResponse) = self.sendTCPQuery(query, useQueue=False, response=None) | |
138 | self.assertEqual(receivedResponse, expectedResponse) | |
139 | ||
c4c72a2c RG |
140 | def testZHealthChecks(self): |
141 | # this test has to run last, as it will mess up the TCP connection counter, | |
142 | # hence the 'Z' in the name | |
143 | self.sendConsoleCommand("getServer(0):setAuto()") | |
144 | time.sleep(2) | |
145 | status = self.sendConsoleCommand("if getServer(0):isUp() then return 'up' else return 'down' end").strip("\n") | |
146 | self.assertEqual(status, 'up') | |
147 | ||
9d71a0cf RG |
148 | class BrokenOutgoingDOHTests(object): |
149 | ||
150 | _webTimeout = 2.0 | |
630eb526 | 151 | _webServerPort = pickAvailablePort() |
9d71a0cf RG |
152 | _webServerBasicAuthPassword = 'secret' |
153 | _webServerAPIKey = 'apisecret' | |
7b8a394a RG |
154 | _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM=' |
155 | _webServerAPIKeyHashed = '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso=' | |
9d71a0cf RG |
156 | |
157 | def checkNoResponderHit(self): | |
158 | self.assertNotIn('UDP Responder', self._responsesCounter) | |
159 | self.assertNotIn('TCP Responder', self._responsesCounter) | |
160 | self.assertNotIn('TLS Responder', self._responsesCounter) | |
161 | self.assertNotIn('DOH Responder', self._responsesCounter) | |
162 | ||
163 | def testUDP(self): | |
164 | """ | |
165 | Outgoing DOH (broken): UDP query is sent via DOH | |
166 | """ | |
167 | name = 'udp.broken-outgoing-doh.test.powerdns.com.' | |
168 | query = dns.message.make_query(name, 'A', 'IN') | |
169 | ||
170 | (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) | |
171 | self.assertEqual(receivedResponse, None) | |
172 | self.checkNoResponderHit() | |
173 | ||
174 | def testTCP(self): | |
175 | """ | |
176 | Outgoing DOH (broken): TCP query is sent via DOH | |
177 | """ | |
178 | name = 'tcp.broken-outgoing-doh.test.powerdns.com.' | |
179 | query = dns.message.make_query(name, 'A', 'IN') | |
180 | expectedResponse = dns.message.make_response(query) | |
181 | rrset = dns.rrset.from_text(name, | |
182 | 60, | |
183 | dns.rdataclass.IN, | |
184 | dns.rdatatype.A, | |
185 | '127.0.0.1') | |
186 | expectedResponse.answer.append(rrset) | |
187 | ||
188 | (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) | |
189 | self.assertEqual(receivedResponse, None) | |
190 | self.checkNoResponderHit() | |
191 | ||
192 | class OutgoingDOHBrokenResponsesTests(object): | |
193 | ||
194 | _webTimeout = 2.0 | |
630eb526 | 195 | _webServerPort = pickAvailablePort() |
9d71a0cf RG |
196 | _webServerBasicAuthPassword = 'secret' |
197 | _webServerAPIKey = 'apisecret' | |
7b8a394a RG |
198 | _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM=' |
199 | _webServerAPIKeyHashed = '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso=' | |
9d71a0cf RG |
200 | |
201 | def testUDP(self): | |
202 | """ | |
203 | Outgoing DOH (broken responses): UDP query is sent via DOH | |
204 | """ | |
205 | name = '500-status.broken-responses.outgoing-doh.test.powerdns.com.' | |
206 | query = dns.message.make_query(name, 'A', 'IN') | |
207 | ||
208 | (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) | |
9d71a0cf RG |
209 | self.assertEqual(receivedResponse, None) |
210 | ||
211 | name = 'invalid-dns-payload.broken-responses.outgoing-doh.test.powerdns.com.' | |
212 | query = dns.message.make_query(name, 'A', 'IN') | |
213 | ||
214 | (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) | |
215 | self.assertEqual(receivedResponse, None) | |
216 | ||
217 | name = 'closing-connection-id.broken-responses.outgoing-doh.test.powerdns.com.' | |
218 | query = dns.message.make_query(name, 'A', 'IN') | |
219 | ||
220 | (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) | |
221 | self.assertEqual(receivedResponse, None) | |
222 | ||
f05cd66c RG |
223 | # but a valid response should be successful |
224 | name = 'valid.broken-responses.outgoing-doh.test.powerdns.com.' | |
225 | query = dns.message.make_query(name, 'A', 'IN') | |
226 | response = dns.message.make_response(query) | |
227 | ||
228 | (_, receivedResponse) = self.sendUDPQuery(query, response) | |
229 | # we can't check the received query because the responder does not populate the queue.. | |
230 | # self.assertEqual(query, receivedQuery) | |
231 | self.assertEqual(response, receivedResponse) | |
232 | ||
233 | def testTCP(self): | |
234 | """ | |
235 | Outgoing DOH (broken responses): TCP query is sent via DOH | |
236 | """ | |
237 | name = '500-status.broken-responses.outgoing-doh.test.powerdns.com.' | |
238 | query = dns.message.make_query(name, 'A', 'IN') | |
239 | ||
240 | (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) | |
241 | self.assertEqual(receivedResponse, None) | |
242 | ||
243 | name = 'invalid-dns-payload.broken-responses.outgoing-doh.test.powerdns.com.' | |
244 | query = dns.message.make_query(name, 'A', 'IN') | |
245 | ||
246 | (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) | |
247 | self.assertEqual(receivedResponse, None) | |
248 | ||
249 | name = 'closing-connection-id.broken-responses.outgoing-doh.test.powerdns.com.' | |
250 | query = dns.message.make_query(name, 'A', 'IN') | |
251 | ||
252 | (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) | |
253 | self.assertEqual(receivedResponse, None) | |
254 | ||
255 | # but a valid response should be successful | |
256 | name = 'valid.broken-responses.outgoing-doh.test.powerdns.com.' | |
257 | query = dns.message.make_query(name, 'A', 'IN') | |
258 | response = dns.message.make_response(query) | |
259 | ||
260 | (_, receivedResponse) = self.sendTCPQuery(query, response) | |
261 | # we can't check the received query because the responder does not populate the queue.. | |
262 | #self.assertEqual(query, receivedQuery) | |
263 | self.assertEqual(response, receivedResponse) | |
264 | ||
9d71a0cf | 265 | class TestOutgoingDOHOpenSSL(DNSDistTest, OutgoingDOHTests): |
630eb526 | 266 | _tlsBackendPort = pickAvailablePort() |
c4c72a2c RG |
267 | _tlsProvider = 'openssl' |
268 | _consoleKey = DNSDistTest.generateConsoleKey() | |
269 | _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii') | |
270 | _config_params = ['_consoleKeyB64', '_consolePort', '_tlsBackendPort', '_tlsProvider', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed'] | |
9d71a0cf | 271 | _config_template = """ |
c4c72a2c RG |
272 | setKey("%s") |
273 | controlSocket("127.0.0.1:%d") | |
9d71a0cf | 274 | setMaxTCPClientThreads(1) |
c4c72a2c | 275 | newServer{address="127.0.0.1:%s", tls='%s', validateCertificates=true, caStore='ca.pem', subjectName='powerdns.com', dohPath='/dns-query', pool={'', 'cache'}}:setUp() |
9d71a0cf RG |
276 | webserver("127.0.0.1:%s") |
277 | setWebserverConfig({password="%s", apiKey="%s"}) | |
249f1981 RG |
278 | |
279 | pc = newPacketCache(100) | |
280 | getPool('cache'):setCache(pc) | |
281 | smn = newSuffixMatchNode() | |
282 | smn:add('cached.outgoing-doh.test.powerdns.com.') | |
283 | addAction(SuffixMatchNodeRule(smn), PoolAction('cache')) | |
9d71a0cf RG |
284 | """ |
285 | ||
bff62869 RG |
286 | @staticmethod |
287 | def sniCallback(sslSocket, sni, sslContext): | |
288 | assert(sni == 'powerdns.com') | |
289 | return None | |
290 | ||
9d71a0cf RG |
291 | @classmethod |
292 | def startResponders(cls): | |
293 | tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) | |
294 | tlsContext.set_alpn_protocols(["h2"]) | |
295 | tlsContext.load_cert_chain('server.chain', 'server.key') | |
bff62869 RG |
296 | # requires Python 3.7+ |
297 | if hasattr(tlsContext, 'sni_callback'): | |
298 | tlsContext.sni_callback = cls.sniCallback | |
9d71a0cf RG |
299 | |
300 | print("Launching DOH responder..") | |
301 | cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext]) | |
630eb526 | 302 | cls._DOHResponder.daemon = True |
9d71a0cf RG |
303 | cls._DOHResponder.start() |
304 | ||
305 | class TestOutgoingDOHGnuTLS(DNSDistTest, OutgoingDOHTests): | |
630eb526 | 306 | _tlsBackendPort = pickAvailablePort() |
c4c72a2c RG |
307 | _tlsProvider = 'gnutls' |
308 | _consoleKey = DNSDistTest.generateConsoleKey() | |
309 | _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii') | |
310 | _config_params = ['_consoleKeyB64', '_consolePort', '_tlsBackendPort', '_tlsProvider', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed'] | |
9d71a0cf | 311 | _config_template = """ |
c4c72a2c RG |
312 | setKey("%s") |
313 | controlSocket("127.0.0.1:%d") | |
9d71a0cf | 314 | setMaxTCPClientThreads(1) |
c4c72a2c | 315 | newServer{address="127.0.0.1:%s", tls='%s', validateCertificates=true, caStore='ca.pem', subjectName='powerdns.com', dohPath='/dns-query', pool={'', 'cache'}}:setUp() |
9d71a0cf RG |
316 | webserver("127.0.0.1:%s") |
317 | setWebserverConfig({password="%s", apiKey="%s"}) | |
249f1981 RG |
318 | |
319 | pc = newPacketCache(100) | |
320 | getPool('cache'):setCache(pc) | |
321 | smn = newSuffixMatchNode() | |
322 | smn:add('cached.outgoing-doh.test.powerdns.com.') | |
323 | addAction(SuffixMatchNodeRule(smn), PoolAction('cache')) | |
9d71a0cf RG |
324 | """ |
325 | ||
326 | @classmethod | |
327 | def startResponders(cls): | |
328 | tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) | |
329 | tlsContext.load_cert_chain('server.chain', 'server.key') | |
330 | tlsContext.keylog_filename = "/tmp/keys" | |
331 | ||
332 | print("Launching DOH responder..") | |
333 | cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext]) | |
630eb526 | 334 | cls._DOHResponder.daemon = True |
9d71a0cf RG |
335 | cls._DOHResponder.start() |
336 | ||
337 | class TestOutgoingDOHOpenSSLWrongCertName(DNSDistTest, BrokenOutgoingDOHTests): | |
630eb526 | 338 | _tlsBackendPort = pickAvailablePort() |
7b8a394a | 339 | _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed'] |
9d71a0cf RG |
340 | _config_template = """ |
341 | setMaxTCPClientThreads(1) | |
342 | newServer{address="127.0.0.1:%s", tls='openssl', validateCertificates=true, caStore='ca.pem', subjectName='not-powerdns.com', dohPath='/dns-query'}:setUp() | |
343 | webserver("127.0.0.1:%s") | |
344 | setWebserverConfig({password="%s", apiKey="%s"}) | |
345 | """ | |
346 | ||
347 | @classmethod | |
348 | def startResponders(cls): | |
349 | tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) | |
350 | tlsContext.load_cert_chain('server.chain', 'server.key') | |
351 | ||
352 | print("Launching DOH responder..") | |
353 | cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext]) | |
630eb526 | 354 | cls._DOHResponder.daemon = True |
9d71a0cf RG |
355 | cls._DOHResponder.start() |
356 | ||
357 | class TestOutgoingDOHGnuTLSWrongCertName(DNSDistTest, BrokenOutgoingDOHTests): | |
630eb526 | 358 | _tlsBackendPort = pickAvailablePort() |
7b8a394a | 359 | _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed'] |
9d71a0cf RG |
360 | _config_template = """ |
361 | setMaxTCPClientThreads(1) | |
362 | newServer{address="127.0.0.1:%s", tls='gnutls', validateCertificates=true, caStore='ca.pem', subjectName='not-powerdns.com', dohPath='/dns-query'}:setUp() | |
363 | webserver("127.0.0.1:%s") | |
364 | setWebserverConfig({password="%s", apiKey="%s"}) | |
365 | """ | |
366 | ||
367 | @classmethod | |
368 | def startResponders(cls): | |
369 | tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) | |
370 | tlsContext.load_cert_chain('server.chain', 'server.key') | |
371 | ||
372 | print("Launching DOH responder..") | |
373 | cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext]) | |
630eb526 | 374 | cls._DOHResponder.daemon = True |
9d71a0cf RG |
375 | cls._DOHResponder.start() |
376 | ||
377 | class TestOutgoingDOHOpenSSLWrongCertNameButNoCheck(DNSDistTest, OutgoingDOHTests): | |
630eb526 | 378 | _tlsBackendPort = pickAvailablePort() |
c4c72a2c RG |
379 | _tlsProvider = 'openssl' |
380 | _consoleKey = DNSDistTest.generateConsoleKey() | |
381 | _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii') | |
382 | _config_params = ['_consoleKeyB64', '_consolePort', '_tlsBackendPort', '_tlsProvider', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed'] | |
9d71a0cf | 383 | _config_template = """ |
c4c72a2c RG |
384 | setKey("%s") |
385 | controlSocket("127.0.0.1:%d") | |
9d71a0cf | 386 | setMaxTCPClientThreads(1) |
c4c72a2c | 387 | newServer{address="127.0.0.1:%s", tls='%s', validateCertificates=false, caStore='ca.pem', subjectName='not-powerdns.com', dohPath='/dns-query', pool={'', 'cache'}}:setUp() |
9d71a0cf RG |
388 | webserver("127.0.0.1:%s") |
389 | setWebserverConfig({password="%s", apiKey="%s"}) | |
249f1981 RG |
390 | |
391 | pc = newPacketCache(100) | |
392 | getPool('cache'):setCache(pc) | |
393 | smn = newSuffixMatchNode() | |
394 | smn:add('cached.outgoing-doh.test.powerdns.com.') | |
395 | addAction(SuffixMatchNodeRule(smn), PoolAction('cache')) | |
9d71a0cf RG |
396 | """ |
397 | ||
398 | @classmethod | |
399 | def startResponders(cls): | |
400 | tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) | |
401 | tlsContext.load_cert_chain('server.chain', 'server.key') | |
402 | ||
403 | print("Launching DOH responder..") | |
404 | cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext]) | |
630eb526 | 405 | cls._DOHResponder.daemon = True |
9d71a0cf RG |
406 | cls._DOHResponder.start() |
407 | ||
408 | class TestOutgoingDOHGnuTLSWrongCertNameButNoCheck(DNSDistTest, OutgoingDOHTests): | |
630eb526 | 409 | _tlsBackendPort = pickAvailablePort() |
c4c72a2c RG |
410 | _tlsProvider = 'gnutls' |
411 | _consoleKey = DNSDistTest.generateConsoleKey() | |
412 | _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii') | |
413 | _config_params = ['_consoleKeyB64', '_consolePort', '_tlsBackendPort', '_tlsProvider', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed'] | |
9d71a0cf | 414 | _config_template = """ |
c4c72a2c RG |
415 | setKey("%s") |
416 | controlSocket("127.0.0.1:%d") | |
9d71a0cf | 417 | setMaxTCPClientThreads(1) |
c4c72a2c | 418 | newServer{address="127.0.0.1:%s", tls='%s', validateCertificates=false, caStore='ca.pem', subjectName='not-powerdns.com', dohPath='/dns-query', pool={'', 'cache'}}:setUp() |
9d71a0cf RG |
419 | webserver("127.0.0.1:%s") |
420 | setWebserverConfig({password="%s", apiKey="%s"}) | |
249f1981 RG |
421 | |
422 | pc = newPacketCache(100) | |
423 | getPool('cache'):setCache(pc) | |
424 | smn = newSuffixMatchNode() | |
425 | smn:add('cached.outgoing-doh.test.powerdns.com.') | |
426 | addAction(SuffixMatchNodeRule(smn), PoolAction('cache')) | |
9d71a0cf RG |
427 | """ |
428 | ||
429 | @classmethod | |
430 | def startResponders(cls): | |
431 | tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) | |
432 | tlsContext.load_cert_chain('server.chain', 'server.key') | |
433 | ||
434 | print("Launching DOH responder..") | |
435 | cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext]) | |
630eb526 | 436 | cls._DOHResponder.daemon = True |
9d71a0cf RG |
437 | cls._DOHResponder.start() |
438 | ||
439 | class TestOutgoingDOHBrokenResponsesOpenSSL(DNSDistTest, OutgoingDOHBrokenResponsesTests): | |
630eb526 | 440 | _tlsBackendPort = pickAvailablePort() |
7b8a394a | 441 | _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed'] |
9d71a0cf RG |
442 | _config_template = """ |
443 | setMaxTCPClientThreads(1) | |
249f1981 | 444 | newServer{address="127.0.0.1:%s", tls='openssl', validateCertificates=true, caStore='ca.pem', subjectName='powerdns.com', dohPath='/dns-query', pool={'', 'cache'}}:setUp() |
9d71a0cf RG |
445 | webserver("127.0.0.1:%s") |
446 | setWebserverConfig({password="%s", apiKey="%s"}) | |
249f1981 RG |
447 | |
448 | pc = newPacketCache(100) | |
449 | getPool('cache'):setCache(pc) | |
450 | smn = newSuffixMatchNode() | |
451 | smn:add('cached.outgoing-doh.test.powerdns.com.') | |
452 | addAction(SuffixMatchNodeRule(smn), PoolAction('cache')) | |
9d71a0cf RG |
453 | """ |
454 | ||
c4c72a2c | 455 | def callback(request, headers, fromQueue, toQueue): |
9d71a0cf RG |
456 | |
457 | if str(request.question[0].name) == '500-status.broken-responses.outgoing-doh.test.powerdns.com.': | |
458 | print("returning 500") | |
459 | return 500, b'Server error' | |
460 | ||
461 | if str(request.question[0].name) == 'invalid-dns-payload.broken-responses.outgoing-doh.test.powerdns.com.': | |
462 | return 200, b'not DNS' | |
463 | ||
464 | if str(request.question[0].name) == 'closing-connection-id.broken-responses.outgoing-doh.test.powerdns.com.': | |
465 | return 200, None | |
466 | ||
467 | print("Returning default for %s" % (request.question[0].name)) | |
468 | return 200, dns.message.make_response(request).to_wire() | |
469 | ||
470 | @classmethod | |
471 | def startResponders(cls): | |
472 | tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) | |
473 | tlsContext.set_alpn_protocols(["h2"]) | |
474 | tlsContext.load_cert_chain('server.chain', 'server.key') | |
475 | ||
476 | print("Launching DOH responder..") | |
477 | cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.callback, tlsContext]) | |
630eb526 | 478 | cls._DOHResponder.daemon = True |
9d71a0cf RG |
479 | cls._DOHResponder.start() |
480 | ||
481 | class TestOutgoingDOHBrokenResponsesGnuTLS(DNSDistTest, OutgoingDOHBrokenResponsesTests): | |
630eb526 | 482 | _tlsBackendPort = pickAvailablePort() |
7b8a394a | 483 | _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed'] |
9d71a0cf RG |
484 | _config_template = """ |
485 | setMaxTCPClientThreads(1) | |
486 | newServer{address="127.0.0.1:%s", tls='gnutls', validateCertificates=true, caStore='ca.pem', subjectName='powerdns.com', dohPath='/dns-query'}:setUp() | |
487 | webserver("127.0.0.1:%s") | |
488 | setWebserverConfig({password="%s", apiKey="%s"}) | |
489 | """ | |
f05cd66c | 490 | _verboseMode = True |
9d71a0cf | 491 | |
c4c72a2c | 492 | def callback(request, headers, fromQueue, toQueue): |
9d71a0cf RG |
493 | |
494 | if str(request.question[0].name) == '500-status.broken-responses.outgoing-doh.test.powerdns.com.': | |
495 | print("returning 500") | |
496 | return 500, b'Server error' | |
497 | ||
498 | if str(request.question[0].name) == 'invalid-dns-payload.broken-responses.outgoing-doh.test.powerdns.com.': | |
499 | return 200, b'not DNS' | |
500 | ||
501 | if str(request.question[0].name) == 'closing-connection-id.broken-responses.outgoing-doh.test.powerdns.com.': | |
502 | return 200, None | |
503 | ||
504 | print("Returning default for %s" % (request.question[0].name)) | |
505 | return 200, dns.message.make_response(request).to_wire() | |
506 | ||
507 | @classmethod | |
508 | def startResponders(cls): | |
509 | tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) | |
510 | tlsContext.set_alpn_protocols(["h2"]) | |
511 | tlsContext.load_cert_chain('server.chain', 'server.key') | |
512 | ||
513 | print("Launching DOH responder..") | |
514 | cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.callback, tlsContext]) | |
630eb526 | 515 | cls._DOHResponder.daemon = True |
9d71a0cf | 516 | cls._DOHResponder.start() |
0e6892c6 RG |
517 | |
518 | class TestOutgoingDOHProxyProtocol(DNSDistTest): | |
519 | ||
630eb526 | 520 | _tlsBackendPort = pickAvailablePort() |
0e6892c6 RG |
521 | _config_params = ['_tlsBackendPort'] |
522 | _config_template = """ | |
523 | setMaxTCPClientThreads(1) | |
524 | newServer{address="127.0.0.1:%s", tls='gnutls', validateCertificates=true, caStore='ca.pem', subjectName='powerdns.com', dohPath='/dns-query', useProxyProtocol=true}:setUp() | |
525 | """ | |
526 | _verboseMode = True | |
527 | ||
528 | @classmethod | |
529 | def startResponders(cls): | |
530 | tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) | |
531 | tlsContext.set_alpn_protocols(["h2"]) | |
532 | tlsContext.load_cert_chain('server.chain', 'server.key') | |
533 | ||
534 | print("Launching DOH woth Proxy Protocol responder..") | |
535 | cls._DOHResponder = threading.Thread(name='DOH with Proxy Protocol Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext, True]) | |
630eb526 | 536 | cls._DOHResponder.daemon = True |
0e6892c6 RG |
537 | cls._DOHResponder.start() |
538 | ||
539 | def testPP(self): | |
540 | """ | |
541 | Outgoing DOH with Proxy Protocol | |
542 | """ | |
543 | name = 'proxy-protocol.outgoing-doh.test.powerdns.com.' | |
544 | query = dns.message.make_query(name, 'A', 'IN') | |
545 | expectedResponse = dns.message.make_response(query) | |
546 | rrset = dns.rrset.from_text(name, | |
547 | 60, | |
548 | dns.rdataclass.IN, | |
549 | dns.rdatatype.A, | |
550 | '127.0.0.1') | |
551 | expectedResponse.answer.append(rrset) | |
552 | ||
553 | (receivedProxyPayload, receivedResponse) = self.sendUDPQuery(query, expectedResponse) | |
554 | receivedQuery = self._fromResponderQueue.get(True, 1.0) | |
555 | self.assertEqual(query, receivedQuery) | |
556 | self.assertEqual(receivedResponse, expectedResponse) | |
557 | self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', False) | |
558 | ||
559 | (receivedProxyPayload, receivedResponse) = self.sendTCPQuery(query, expectedResponse) | |
560 | receivedQuery = self._fromResponderQueue.get(True, 1.0) | |
561 | self.assertEqual(query, receivedQuery) | |
562 | self.assertEqual(receivedResponse, expectedResponse) | |
563 | self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True) | |
c4c72a2c RG |
564 | |
565 | class TestOutgoingDOHXForwarded(DNSDistTest): | |
630eb526 | 566 | _tlsBackendPort = pickAvailablePort() |
c4c72a2c RG |
567 | _config_params = ['_tlsBackendPort'] |
568 | _config_template = """ | |
569 | setMaxTCPClientThreads(1) | |
570 | newServer{address="127.0.0.1:%s", tls='gnutls', validateCertificates=true, caStore='ca.pem', subjectName='powerdns.com', dohPath='/dns-query', addXForwardedHeaders=true} | |
571 | """ | |
572 | _verboseMode = True | |
573 | ||
574 | def callback(request, headersList, fromQueue, toQueue): | |
575 | ||
576 | if str(request.question[0].name) == 'a.root-servers.net.': | |
577 | # do not check headers on health-check queries | |
578 | return 200, dns.message.make_response(request).to_wire() | |
579 | ||
580 | headers = {} | |
581 | if headersList: | |
582 | for k,v in headersList: | |
583 | headers[k] = v | |
584 | ||
585 | if not b'x-forwarded-for' in headers: | |
586 | print("missing X-Forwarded-For") | |
587 | return 406, b'Missing X-Forwarded-For header' | |
588 | if not b'x-forwarded-port' in headers: | |
589 | print("missing X-Forwarded-Port") | |
590 | return 406, b'Missing X-Forwarded-Port header' | |
591 | if not b'x-forwarded-proto' in headers: | |
592 | print("missing X-Forwarded-Proto") | |
593 | return 406, b'Missing X-Forwarded-Proto header' | |
594 | ||
595 | toQueue.put(request, True, 1.0) | |
596 | response = fromQueue.get(True, 1.0) | |
597 | if response: | |
598 | response = copy.copy(response) | |
599 | response.id = request.id | |
600 | ||
601 | return 200, response.to_wire() | |
602 | ||
603 | @classmethod | |
604 | def startResponders(cls): | |
605 | tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) | |
606 | tlsContext.set_alpn_protocols(["h2"]) | |
607 | tlsContext.load_cert_chain('server.chain', 'server.key') | |
608 | ||
609 | print("Launching DOH responder..") | |
610 | cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.callback, tlsContext]) | |
630eb526 | 611 | cls._DOHResponder.daemon = True |
c4c72a2c RG |
612 | cls._DOHResponder.start() |
613 | ||
614 | def testXForwarded(self): | |
615 | """ | |
616 | Outgoing DOH: X-Forwarded | |
617 | """ | |
618 | name = 'x-forwarded-for.outgoing-doh.test.powerdns.com.' | |
619 | query = dns.message.make_query(name, 'A', 'IN') | |
620 | expectedResponse = dns.message.make_response(query) | |
621 | rrset = dns.rrset.from_text(name, | |
622 | 60, | |
623 | dns.rdataclass.IN, | |
624 | dns.rdatatype.A, | |
625 | '127.0.0.1') | |
626 | expectedResponse.answer.append(rrset) | |
627 | ||
628 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse) | |
629 | self.assertEqual(query, receivedQuery) | |
630 | self.assertEqual(receivedResponse, expectedResponse) | |
631 | ||
632 | (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse) | |
633 | self.assertEqual(query, receivedQuery) | |
634 | self.assertEqual(receivedResponse, expectedResponse) |