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