]> git.ipfire.org Git - thirdparty/pdns.git/blame - regression-tests.dnsdist/test_TLS.py
dnsdist: Wait for the API port to be available before running the tests
[thirdparty/pdns.git] / regression-tests.dnsdist / test_TLS.py
CommitLineData
a227f47d 1#!/usr/bin/env python
1d896c34 2import base64
a227f47d 3import dns
5f4156be
RG
4import socket
5import ssl
1d896c34 6import subprocess
d4d57f56 7import time
5f4156be 8import unittest
a227f47d
RG
9from dnsdisttests import DNSDistTest
10
5f4156be 11class TLSTests(object):
a227f47d 12
1d896c34
RG
13 def getServerCertificate(self):
14 conn = self.openTLSConnection(self._tlsServerPort, self._serverName, self._caCert)
75b536de
RG
15 cert = conn.getpeercert()
16 conn.close()
17 return cert
1d896c34 18
98222dfc
RG
19 def getTLSProvider(self):
20 return self.sendConsoleCommand("getBind(0):getEffectiveTLSProvider()").rstrip()
21
a227f47d
RG
22 def testTLSSimple(self):
23 """
24 TLS: Single query
25 """
26 name = 'single.tls.tests.powerdns.com.'
27 query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
28 response = dns.message.make_response(query)
29 rrset = dns.rrset.from_text(name,
30 3600,
31 dns.rdataclass.IN,
32 dns.rdatatype.A,
33 '127.0.0.1')
34 response.answer.append(rrset)
35
36 conn = self.openTLSConnection(self._tlsServerPort, self._serverName, self._caCert)
37
38 self.sendTCPQueryOverConnection(conn, query, response=response)
39 (receivedQuery, receivedResponse) = self.recvTCPResponseOverConnection(conn, useQueue=True)
40 self.assertTrue(receivedQuery)
41 self.assertTrue(receivedResponse)
42 receivedQuery.id = query.id
4bfebc93
CH
43 self.assertEqual(query, receivedQuery)
44 self.assertEqual(response, receivedResponse)
6db567d1 45
1d896c34
RG
46 # check the certificate
47 cert = self.getServerCertificate()
48 self.assertIn('subject', cert)
49 self.assertIn('serialNumber', cert)
50 self.assertIn('subjectAltName', cert)
51 subject = cert['subject']
52 altNames = cert['subjectAltName']
4bfebc93
CH
53 self.assertEqual(dict(subject[0])['commonName'], 'tls.tests.dnsdist.org')
54 self.assertEqual(dict(subject[1])['organizationalUnitName'], 'PowerDNS.com BV')
1d896c34
RG
55 names = []
56 for entry in altNames:
57 names.append(entry[1])
644eb242 58 self.assertEqual(names, ['tls.tests.dnsdist.org', 'powerdns.com', '127.0.0.1'])
1d896c34
RG
59 serialNumber = cert['serialNumber']
60
61 self.generateNewCertificateAndKey()
62 self.sendConsoleCommand("reloadAllCertificates()")
63
75b536de 64 conn.close()
1d896c34
RG
65 # open a new connection
66 conn = self.openTLSConnection(self._tlsServerPort, self._serverName, self._caCert)
67
68 self.sendTCPQueryOverConnection(conn, query, response=response)
69 (receivedQuery, receivedResponse) = self.recvTCPResponseOverConnection(conn, useQueue=True)
70 self.assertTrue(receivedQuery)
71 self.assertTrue(receivedResponse)
72 receivedQuery.id = query.id
4bfebc93
CH
73 self.assertEqual(query, receivedQuery)
74 self.assertEqual(response, receivedResponse)
1d896c34
RG
75
76 # check that the certificate is OK
77 cert = self.getServerCertificate()
78 self.assertIn('subject', cert)
79 self.assertIn('serialNumber', cert)
80 self.assertIn('subjectAltName', cert)
81 subject = cert['subject']
82 altNames = cert['subjectAltName']
4bfebc93
CH
83 self.assertEqual(dict(subject[0])['commonName'], 'tls.tests.dnsdist.org')
84 self.assertEqual(dict(subject[1])['organizationalUnitName'], 'PowerDNS.com BV')
1d896c34
RG
85 names = []
86 for entry in altNames:
87 names.append(entry[1])
644eb242 88 self.assertEqual(names, ['tls.tests.dnsdist.org', 'powerdns.com', '127.0.0.1'])
1d896c34
RG
89
90 # and that the serial is different
4bfebc93 91 self.assertNotEqual(serialNumber, cert['serialNumber'])
75b536de 92 conn.close()
1d896c34 93
6db567d1
RG
94 def testTLKA(self):
95 """
96 TLS: Several queries over the same connection
97 """
98 name = 'ka.tls.tests.powerdns.com.'
99 query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
100 response = dns.message.make_response(query)
101 rrset = dns.rrset.from_text(name,
102 3600,
103 dns.rdataclass.IN,
104 dns.rdatatype.A,
105 '127.0.0.1')
106 response.answer.append(rrset)
107
108 conn = self.openTLSConnection(self._tlsServerPort, self._serverName, self._caCert)
109
110 for idx in range(5):
111 self.sendTCPQueryOverConnection(conn, query, response=response)
112 (receivedQuery, receivedResponse) = self.recvTCPResponseOverConnection(conn, useQueue=True)
113 self.assertTrue(receivedQuery)
114 self.assertTrue(receivedResponse)
115 receivedQuery.id = query.id
4bfebc93
CH
116 self.assertEqual(query, receivedQuery)
117 self.assertEqual(response, receivedResponse)
6db567d1 118
75b536de
RG
119 conn.close()
120
6db567d1
RG
121 def testTLSPipelining(self):
122 """
123 TLS: Several queries over the same connection without waiting for the responses
124 """
125 name = 'pipelining.tls.tests.powerdns.com.'
126 query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
127 response = dns.message.make_response(query)
128 rrset = dns.rrset.from_text(name,
129 3600,
130 dns.rdataclass.IN,
131 dns.rdatatype.A,
132 '127.0.0.1')
133 response.answer.append(rrset)
134
135 conn = self.openTLSConnection(self._tlsServerPort, self._serverName, self._caCert)
136
137 for idx in range(100):
138 self.sendTCPQueryOverConnection(conn, query, response=response)
139
140 for idx in range(100):
141 (receivedQuery, receivedResponse) = self.recvTCPResponseOverConnection(conn, useQueue=True)
142 self.assertTrue(receivedQuery)
143 self.assertTrue(receivedResponse)
144 receivedQuery.id = query.id
4bfebc93
CH
145 self.assertEqual(query, receivedQuery)
146 self.assertEqual(response, receivedResponse)
046bac5c 147
75b536de
RG
148 conn.close()
149
046bac5c
RG
150 def testTLSSNIRouting(self):
151 """
152 TLS: SNI Routing
153 """
154 name = 'sni.tls.tests.powerdns.com.'
155 query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
156 query.flags &= ~dns.flags.RD
157 response = dns.message.make_response(query)
158 rrset = dns.rrset.from_text(name,
159 3600,
160 dns.rdataclass.IN,
161 dns.rdatatype.A,
162 '127.0.0.1')
163 response.answer.append(rrset)
164 expectedResponse = dns.message.make_response(query)
165 rrset = dns.rrset.from_text(name,
166 3600,
167 dns.rdataclass.IN,
168 dns.rdatatype.A,
169 '1.2.3.4')
170 expectedResponse.answer.append(rrset)
171
172 # this SNI should match so we should get a spoofed answer
173 conn = self.openTLSConnection(self._tlsServerPort, 'powerdns.com', self._caCert)
174
175 self.sendTCPQueryOverConnection(conn, query, response=None)
176 receivedResponse = self.recvTCPResponseOverConnection(conn, useQueue=False)
177 self.assertTrue(receivedResponse)
4bfebc93 178 self.assertEqual(expectedResponse, receivedResponse)
046bac5c 179
75b536de 180 conn.close()
046bac5c
RG
181 # this one should not
182 conn = self.openTLSConnection(self._tlsServerPort, self._serverName, self._caCert)
183
184 self.sendTCPQueryOverConnection(conn, query, response=response)
185 (receivedQuery, receivedResponse) = self.recvTCPResponseOverConnection(conn, useQueue=True)
186 self.assertTrue(receivedQuery)
187 self.assertTrue(receivedResponse)
188 receivedQuery.id = query.id
4bfebc93
CH
189 self.assertEqual(query, receivedQuery)
190 self.assertEqual(response, receivedResponse)
75b536de 191 conn.close()
d27309a9 192
5f4156be
RG
193 def testTLSSNIRoutingAfterResumption(self):
194 # we have more complicated tests about session resumption itself,
195 # but here we want to make sure the SNI is still present after resumption
196 """
197 TLS: SNI Routing after resumption
198 """
199 name = 'sni-resumed.tls.tests.powerdns.com.'
200 query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
201 query.flags &= ~dns.flags.RD
202 response = dns.message.make_response(query)
203 rrset = dns.rrset.from_text(name,
204 3600,
205 dns.rdataclass.IN,
206 dns.rdatatype.A,
207 '127.0.0.1')
208 response.answer.append(rrset)
209 expectedResponse = dns.message.make_response(query)
210 rrset = dns.rrset.from_text(name,
211 3600,
212 dns.rdataclass.IN,
213 dns.rdatatype.A,
214 '1.2.3.4')
215 expectedResponse.answer.append(rrset)
216
217 # this SNI should match so we should get a spoofed answer
218 sslctx = ssl.SSLContext(protocol=ssl.PROTOCOL_TLSv1_2)
219 sslctx.check_hostname = True
220 sslctx.verify_mode = ssl.CERT_REQUIRED
221 sslctx.load_verify_locations(self._caCert)
222
223 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
224 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
225 sock.settimeout(2.0)
226 sslsock = sslctx.wrap_socket(sock, server_hostname='powerdns.com')
227 sslsock.connect(("127.0.0.1", self._tlsServerPort))
228
229 self.sendTCPQueryOverConnection(sslsock, query, response=None)
230 receivedResponse = self.recvTCPResponseOverConnection(sslsock, useQueue=False)
231 self.assertTrue(receivedResponse)
4bfebc93 232 self.assertEqual(expectedResponse, receivedResponse)
5f4156be
RG
233 self.assertFalse(sslsock.session_reused)
234 session = sslsock.session
235
236 # this one should not (different SNI)
237 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
238 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
239 sock.settimeout(2.0)
240 sslsock = sslctx.wrap_socket(sock, server_hostname=self._serverName)
241 sslsock.connect(("127.0.0.1", self._tlsServerPort))
242
243 self.sendTCPQueryOverConnection(sslsock, query, response=response)
244 (receivedQuery, receivedResponse) = self.recvTCPResponseOverConnection(sslsock, useQueue=True)
245 self.assertTrue(receivedQuery)
246 self.assertTrue(receivedResponse)
247 receivedQuery.id = query.id
4bfebc93
CH
248 self.assertEqual(query, receivedQuery)
249 self.assertEqual(response, receivedResponse)
5f4156be
RG
250 self.assertFalse(sslsock.session_reused)
251
252 # and now we should be able to resume the session
253 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
254 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
255 sock.settimeout(2.0)
256 sslsock = sslctx.wrap_socket(sock, server_hostname='powerdns.com')
257 sslsock.session = session
258 sslsock.connect(("127.0.0.1", self._tlsServerPort))
259
260 self.sendTCPQueryOverConnection(sslsock, query, response=None)
261 receivedResponse = self.recvTCPResponseOverConnection(sslsock, useQueue=False)
262 self.assertTrue(receivedResponse)
4bfebc93 263 self.assertEqual(expectedResponse, receivedResponse)
5f4156be
RG
264 self.assertTrue(sslsock.session_reused)
265
266class TestOpenSSL(DNSDistTest, TLSTests):
267
b38aaeb5 268 _extraStartupSleep = 1
1d896c34
RG
269 _consoleKey = DNSDistTest.generateConsoleKey()
270 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
5f4156be
RG
271 _serverKey = 'server.key'
272 _serverCert = 'server.chain'
273 _serverName = 'tls.tests.dnsdist.org'
274 _caCert = 'ca.pem'
275 _tlsServerPort = 8453
276 _config_template = """
1d896c34
RG
277 setKey("%s")
278 controlSocket("127.0.0.1:%s")
279
5f4156be
RG
280 newServer{address="127.0.0.1:%s"}
281 addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl" })
282 addAction(SNIRule("powerdns.com"), SpoofAction("1.2.3.4"))
283 """
1d896c34 284 _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey']
5f4156be 285
98222dfc
RG
286 def testProvider(self):
287 self.assertEquals(self.getTLSProvider(), "openssl")
288
5f4156be
RG
289class TestGnuTLS(DNSDistTest, TLSTests):
290
1d896c34
RG
291 _consoleKey = DNSDistTest.generateConsoleKey()
292 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
5f4156be
RG
293 _serverKey = 'server.key'
294 _serverCert = 'server.chain'
295 _serverName = 'tls.tests.dnsdist.org'
296 _caCert = 'ca.pem'
297 _tlsServerPort = 8453
298 _config_template = """
1d896c34
RG
299 setKey("%s")
300 controlSocket("127.0.0.1:%s")
301
5f4156be
RG
302 newServer{address="127.0.0.1:%s"}
303 addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="gnutls" })
304 addAction(SNIRule("powerdns.com"), SpoofAction("1.2.3.4"))
305 """
1d896c34 306 _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey']
5f4156be 307
98222dfc
RG
308 def testProvider(self):
309 self.assertEquals(self.getTLSProvider(), "gnutls")
310
d27309a9 311class TestDOTWithCache(DNSDistTest):
d27309a9
RG
312 _serverKey = 'server.key'
313 _serverCert = 'server.chain'
314 _serverName = 'tls.tests.dnsdist.org'
315 _caCert = 'ca.pem'
316 _tlsServerPort = 8453
317 _config_template = """
318 newServer{address="127.0.0.1:%s"}
319
320 addTLSLocal("127.0.0.1:%s", "%s", "%s")
321
322 pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
323 getPool(""):setCache(pc)
324 """
325 _config_params = ['_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey']
326
327 def testDOTCacheLargeAnswer(self):
328 """
329 DOT with cache: Check that we can cache (and retrieve) large answers
330 """
331 numberOfQueries = 10
332 name = 'large.dot-with-cache.tests.powerdns.com.'
333 query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
334 query.id = 0
335 expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
336 expectedQuery.id = 0
337 response = dns.message.make_response(query)
338 # we prepare a large answer
339 content = ""
340 for i in range(44):
341 if len(content) > 0:
342 content = content + ', '
343 content = content + (str(i)*50)
344 # pad up to 4096
345 content = content + 'A'*40
346
347 rrset = dns.rrset.from_text(name,
348 3600,
349 dns.rdataclass.IN,
350 dns.rdatatype.TXT,
351 content)
352 response.answer.append(rrset)
4bfebc93 353 self.assertEqual(len(response.to_wire()), 4096)
d27309a9
RG
354
355 # first query to fill the cache
356 conn = self.openTLSConnection(self._tlsServerPort, self._serverName, self._caCert)
357 self.sendTCPQueryOverConnection(conn, query, response=response)
358 (receivedQuery, receivedResponse) = self.recvTCPResponseOverConnection(conn, useQueue=True)
359
360 self.assertTrue(receivedQuery)
361 self.assertTrue(receivedResponse)
362 receivedQuery.id = expectedQuery.id
4bfebc93 363 self.assertEqual(expectedQuery, receivedQuery)
d27309a9 364 self.checkQueryNoEDNS(expectedQuery, receivedQuery)
4bfebc93 365 self.assertEqual(response, receivedResponse)
75b536de 366 conn.close()
d27309a9
RG
367
368 for _ in range(numberOfQueries):
369 conn = self.openTLSConnection(self._tlsServerPort, self._serverName, self._caCert)
370 self.sendTCPQueryOverConnection(conn, query, response=None)
371 receivedResponse = self.recvTCPResponseOverConnection(conn, useQueue=False)
4bfebc93 372 self.assertEqual(receivedResponse, response)
75b536de 373 conn.close()
d4d57f56
RG
374
375class TestTLSFrontendLimits(DNSDistTest):
376
377 # this test suite uses a different responder port
378 # because it uses a different health check configuration
379 _testServerPort = 5395
380 _answerUnexpected = True
381
382 _serverKey = 'server.key'
383 _serverCert = 'server.chain'
384 _serverName = 'tls.tests.dnsdist.org'
385 _caCert = 'ca.pem'
386 _tlsServerPort = 8453
387
388 _skipListeningOnCL = True
389 _tcpIdleTimeout = 2
390 _maxTCPConnsPerTLSFrontend = 5
391 _config_template = """
392 newServer{address="127.0.0.1:%s"}
393 addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl", maxConcurrentTCPConnections=%d })
394 """
395 _config_params = ['_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_maxTCPConnsPerTLSFrontend']
d4d57f56
RG
396
397 def testTCPConnsPerTLSFrontend(self):
398 """
399 TLS Frontend Limits: Maximum number of conns per TLS frontend
400 """
401 name = 'maxconnspertlsfrontend.tls.tests.powerdns.com.'
402 query = dns.message.make_query(name, 'A', 'IN')
403 conns = []
404
405 for idx in range(self._maxTCPConnsPerTLSFrontend + 1):
406 try:
407 conns.append(self.openTLSConnection(self._tlsServerPort, self._serverName, self._caCert))
408 except:
409 conns.append(None)
410
411 count = 0
412 failed = 0
413 for conn in conns:
414 if not conn:
415 failed = failed + 1
416 continue
417
418 try:
419 self.sendTCPQueryOverConnection(conn, query)
420 response = self.recvTCPResponseOverConnection(conn)
421 if response:
422 count = count + 1
423 else:
424 failed = failed + 1
425 except:
426 failed = failed + 1
427
428 for conn in conns:
429 if conn:
430 conn.close()
431
432 # wait a bit to be sure that dnsdist closed the connections
433 # and decremented the counters on its side, otherwise subsequent
434 # connections will be dropped
435 time.sleep(1)
436
437 self.assertEqual(count, self._maxTCPConnsPerTLSFrontend)
438 self.assertEqual(failed, 1)
7d808ff4
RG
439
440class TestProtocols(DNSDistTest):
441 _serverKey = 'server.key'
442 _serverCert = 'server.chain'
443 _serverName = 'tls.tests.dnsdist.org'
444 _caCert = 'ca.pem'
445 _tlsServerPort = 8453
446
447 _config_template = """
448 function checkDOT(dq)
449 if dq:getProtocol() ~= "DNS over TLS" then
450 return DNSAction.Spoof, '1.2.3.4'
451 end
452 return DNSAction.None
453 end
454
455 addAction("protocols.tls.tests.powerdns.com.", LuaAction(checkDOT))
456 newServer{address="127.0.0.1:%s"}
457 addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl" })
458 """
459 _config_params = ['_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey']
460
461 def testProtocolDOT(self):
462 """
463 DoT: Test DNSQuestion.Protocol
464 """
465 name = 'protocols.tls.tests.powerdns.com.'
466 query = dns.message.make_query(name, 'A', 'IN')
467 response = dns.message.make_response(query)
468
469 conn = self.openTLSConnection(self._tlsServerPort, self._serverName, self._caCert)
470 self.sendTCPQueryOverConnection(conn, query, response=response)
471 (receivedQuery, receivedResponse) = self.recvTCPResponseOverConnection(conn, useQueue=True)
472 self.assertTrue(receivedQuery)
473 self.assertTrue(receivedResponse)
474 receivedQuery.id = query.id
475 self.assertEqual(query, receivedQuery)
476 self.assertEqual(response, receivedResponse)
75b536de 477 conn.close()
5ac11505
CHB
478
479class TestPKCSTLSCertificate(DNSDistTest, TLSTests):
480 _consoleKey = DNSDistTest.generateConsoleKey()
481 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
482 _serverCert = 'server.p12'
483 _pkcsPassphrase = 'passw0rd'
484 _serverName = 'tls.tests.dnsdist.org'
485 _caCert = 'ca.pem'
486 _tlsServerPort = 8453
487 _config_template = """
488 setKey("%s")
489 controlSocket("127.0.0.1:%s")
490 newServer{address="127.0.0.1:%s"}
491 cert=newTLSCertificate("%s", {password="%s"})
492 addTLSLocal("127.0.0.1:%s", cert, "", { provider="openssl" })
493 addAction(SNIRule("powerdns.com"), SpoofAction("1.2.3.4"))
494 """
495 _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_serverCert', '_pkcsPassphrase', '_tlsServerPort']