]>
git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.auth-py/authtests.py
3 from __future__
import print_function
16 from pprint
import pprint
18 class AuthTest ( unittest
. TestCase
):
20 Setup auth required for the tests
26 _root_DS
= "63149 13 1 a59da3f5c1b97fcd5fa2b3b2b0ac91d38a60d33a"
28 # The default SOA for zones in the authoritative servers
29 _SOA
= "ns1.example.net. hostmaster.example.net. 1 3600 1800 1209600 300"
31 # The definitions of the zones on the authoritative servers, the key is the
32 # zonename and the value is the zonefile content. several strings are replaced:
33 # - {soa} => value of _SOA
34 # - {prefix} value of _PREFIX
37 example.org. 3600 IN SOA {soa}
38 example.org. 3600 IN NS ns1.example.org.
39 example.org. 3600 IN NS ns2.example.org.
40 ns1.example.org. 3600 IN A {prefix} .10
41 ns2.example.org. 3600 IN A {prefix} .11
47 Private-key-format: v1.2
48 Algorithm: 13 (ECDSAP256SHA256)
49 PrivateKey: Lt0v0Gol3pRUFM7fDdcy0IWN0O/MnEmVPA+VylL8Y4U=
53 _auth_cmd
= [ 'authbind' ,
58 _PREFIX
= os
. environ
[ 'PREFIX' ]
62 def createConfigDir ( cls
, confdir
):
64 shutil
. rmtree ( confdir
)
66 if e
. errno
!= errno
. ENOENT
:
68 os
. mkdir ( confdir
, 0o755 )
71 def generateAuthZone ( cls
, confdir
, zonename
, zonecontent
):
72 with
open ( os
. path
. join ( confdir
, ' %s .zone' % zonename
), 'w' ) as zonefile
:
73 zonefile
. write ( zonecontent
. format ( prefix
= cls
._ PREFIX
, soa
= cls
._ SOA
))
76 def generateAuthNamedConf ( cls
, confdir
, zones
):
77 with
open ( os
. path
. join ( confdir
, 'named.conf' ), 'w' ) as namedconf
:
82 for zonename
in zones
:
83 zone
= '.' if zonename
== 'ROOT' else zonename
89 };""" % ( zone
, zonename
))
92 def generateAuthConfig ( cls
, confdir
):
93 bind_dnssec_db
= os
. path
. join ( confdir
, 'bind-dnssec.sqlite3' )
95 with
open ( os
. path
. join ( confdir
, 'pdns.conf' ), 'w' ) as pdnsconf
:
97 module-dir=../regression-tests/modules
101 bind-config= {confdir} /named.conf
102 bind-dnssec-db= {bind_dnssec_db}
110 geoip-database-files=../modules/geoipbackend/regression-tests/GeoLiteCity.mmdb
111 edns-subnet-processing=yes
113 resolver= {prefix} .1:5301
115 distributor-threads=1""" . format ( confdir
= confdir
, prefix
= cls
._ PREFIX
,
116 bind_dnssec_db
= bind_dnssec_db
))
118 pdnsutilCmd
= [ os
. environ
[ 'PDNSUTIL' ],
119 '--config-dir= %s ' % confdir
,
123 print ( ' ' . join ( pdnsutilCmd
))
125 subprocess
. check_output ( pdnsutilCmd
, stderr
= subprocess
. STDOUT
)
126 except subprocess
. CalledProcessError
as e
:
127 raise AssertionError ( ' %s failed ( %d ): %s ' % ( pdnsutilCmd
, e
. returncode
, e
. output
))
130 def secureZone ( cls
, confdir
, zonename
, key
= None ):
131 zone
= '.' if zonename
== 'ROOT' else zonename
133 pdnsutilCmd
= [ os
. environ
[ 'PDNSUTIL' ],
134 '--config-dir= %s ' % confdir
,
138 keyfile
= os
. path
. join ( confdir
, 'dnssec.key' )
139 with
open ( keyfile
, 'w' ) as fdKeyfile
:
142 pdnsutilCmd
= [ os
. environ
[ 'PDNSUTIL' ],
143 '--config-dir= %s ' % confdir
,
150 print ( ' ' . join ( pdnsutilCmd
))
152 subprocess
. check_output ( pdnsutilCmd
, stderr
= subprocess
. STDOUT
)
153 except subprocess
. CalledProcessError
as e
:
154 raise AssertionError ( ' %s failed ( %d ): %s ' % ( pdnsutilCmd
, e
. returncode
, e
. output
))
157 def generateAllAuthConfig ( cls
, confdir
):
159 cls
. generateAuthConfig ( confdir
)
160 cls
. generateAuthNamedConf ( confdir
, cls
._ zones
. keys ())
162 for zonename
, zonecontent
in cls
._ zones
. items ():
163 cls
. generateAuthZone ( confdir
,
166 if cls
._ zone
_ keys
. get ( zonename
, None ):
167 cls
. secureZone ( confdir
, zonename
, cls
._ zone
_ keys
. get ( zonename
))
170 def startAuth ( cls
, confdir
, ipaddress
):
172 print ( "Launching pdns_server.." )
173 authcmd
= list ( cls
._ auth
_ cmd
)
174 authcmd
. append ( '--config-dir= %s ' % confdir
)
175 authcmd
. append ( '--local-address= %s ' % ipaddress
)
176 authcmd
. append ( '--local-port= %s ' % cls
._ authPort
)
177 authcmd
. append ( '--loglevel=9' )
178 authcmd
. append ( '--enable-lua-records' )
179 print ( ' ' . join ( authcmd
))
181 logFile
= os
. path
. join ( confdir
, 'pdns.log' )
182 with
open ( logFile
, 'w' ) as fdLog
:
183 cls
._ auths
[ ipaddress
] = subprocess
. Popen ( authcmd
, close_fds
= True ,
184 stdout
= fdLog
, stderr
= fdLog
,
189 if cls
._ auths
[ ipaddress
]. poll () is not None :
191 cls
._ auths
[ ipaddress
]. kill ()
193 if e
. errno
!= errno
. ESRCH
:
195 with
open ( logFile
, 'r' ) as fdLog
:
197 sys
. exit ( cls
._ auths
[ ipaddress
]. returncode
)
200 def setUpSockets ( cls
):
201 print ( "Setting up UDP socket.." )
202 cls
._ sock
= socket
. socket ( socket
. AF_INET
, socket
. SOCK_DGRAM
)
203 cls
._ sock
. settimeout ( 2.0 )
204 cls
._ sock
. connect (( cls
._ PREFIX
+ ".1" , cls
._ authPort
))
207 def startResponders ( cls
):
214 cls
. startResponders ()
216 confdir
= os
. path
. join ( 'configs' , cls
._ confdir
)
217 cls
. createConfigDir ( confdir
)
219 cls
. generateAllAuthConfig ( confdir
)
220 cls
. startAuth ( confdir
, cls
._ PREFIX
+ ".1" )
222 print ( "Launching tests.." )
225 def tearDownClass ( cls
):
227 cls
. tearDownResponders ()
230 def tearDownResponders ( cls
):
234 def tearDownClass ( cls
):
238 def tearDownAuth ( cls
):
239 if 'PDNSRECURSOR_FAST_TESTS' in os
. environ
:
244 for _
, auth
in cls
._ auths
. items ():
247 if auth
. poll () is None :
249 if auth
. poll () is None :
253 if e
. errno
!= errno
. ESRCH
:
257 def sendUDPQuery ( cls
, query
, timeout
= 2.0 , decode
= True , fwparams
= dict ()):
259 cls
._ sock
. settimeout ( timeout
)
262 cls
._ sock
. send ( query
. to_wire ())
263 data
= cls
._ sock
. recv ( 4096 )
264 except socket
. timeout
:
268 cls
._ sock
. settimeout ( None )
274 message
= dns
. message
. from_wire ( data
, ** fwparams
)
278 def sendTCPQuery ( cls
, query
, timeout
= 2.0 ):
279 sock
= socket
. socket ( socket
. AF_INET
, socket
. SOCK_STREAM
)
281 sock
. settimeout ( timeout
)
283 sock
. connect (( "127.0.0.1" , cls
._ recursorPort
))
286 wire
= query
. to_wire ()
287 sock
. send ( struct
. pack ( "!H" , len ( wire
)))
291 ( datalen
,) = struct
. unpack ( "!H" , data
)
292 data
= sock
. recv ( datalen
)
293 except socket
. timeout
as e
:
294 print ( "Timeout: %s " % ( str ( e
)))
296 except socket
. error
as e
:
297 print ( "Network error: %s " % ( str ( e
)))
304 message
= dns
. message
. from_wire ( data
)
309 def sendTCPQuery ( cls
, query
, timeout
= 2.0 ):
310 sock
= socket
. socket ( socket
. AF_INET
, socket
. SOCK_STREAM
)
312 sock
. settimeout ( timeout
)
314 sock
. connect (( "127.0.0.1" , cls
._ authPort
))
317 wire
= query
. to_wire ()
318 sock
. send ( struct
. pack ( "!H" , len ( wire
)))
322 ( datalen
,) = struct
. unpack ( "!H" , data
)
323 data
= sock
. recv ( datalen
)
324 except socket
. timeout
as e
:
325 print ( "Timeout: %s " % ( str ( e
)))
327 except socket
. error
as e
:
328 print ( "Network error: %s " % ( str ( e
)))
335 message
= dns
. message
. from_wire ( data
)
339 # This function is called before every tests
342 ## Functions for comparisons
343 def assertMessageHasFlags ( self
, msg
, flags
, ednsflags
=[]):
344 """Asserts that msg has all the flags from flags set
346 @param msg: the dns.message.Message to check
347 @param flags: a list of strings with flag mnemonics (like ['RD', 'RA'])
348 @param ednsflags: a list of strings with edns-flag mnemonics (like ['DO'])"""
350 if not isinstance ( msg
, dns
. message
. Message
):
351 raise TypeError ( "msg is not a dns.message.Message" )
353 if isinstance ( flags
, list ):
355 if not isinstance ( elem
, str ):
356 raise TypeError ( "flags is not a list of strings" )
358 raise TypeError ( "flags is not a list of strings" )
360 if isinstance ( ednsflags
, list ):
361 for elem
in ednsflags
:
362 if not isinstance ( elem
, str ):
363 raise TypeError ( "ednsflags is not a list of strings" )
365 raise TypeError ( "ednsflags is not a list of strings" )
367 msgFlags
= dns
. flags
. to_text ( msg
. flags
). split ()
368 missingFlags
= [ flag
for flag
in flags
if flag
not in msgFlags
]
370 msgEdnsFlags
= dns
. flags
. edns_to_text ( msg
. ednsflags
). split ()
371 missingEdnsFlags
= [ ednsflag
for ednsflag
in ednsflags
if ednsflag
not in msgEdnsFlags
]
373 if len ( missingFlags
) or len ( missingEdnsFlags
) or len ( msgFlags
) > len ( flags
):
374 raise AssertionError ( "Expected flags ' %s ' (EDNS: ' %s '), found ' %s ' (EDNS: ' %s ') in query %s " %
375 ( ' ' . join ( flags
), ' ' . join ( ednsflags
),
376 ' ' . join ( msgFlags
), ' ' . join ( msgEdnsFlags
),
379 def assertMessageIsAuthenticated ( self
, msg
):
380 """Asserts that the message has the AD bit set
382 @param msg: the dns.message.Message to check"""
384 if not isinstance ( msg
, dns
. message
. Message
):
385 raise TypeError ( "msg is not a dns.message.Message" )
387 msgFlags
= dns
. flags
. to_text ( msg
. flags
)
388 self
. assertTrue ( 'AD' in msgFlags
, "No AD flag found in the message for %s " % msg
. question
[ 0 ]. name
)
390 def assertRRsetInAnswer ( self
, msg
, rrset
):
391 """Asserts the rrset (without comparing TTL) exists in the
392 answer section of msg
394 @param msg: the dns.message.Message to check
395 @param rrset: a dns.rrset.RRset object"""
398 if not isinstance ( msg
, dns
. message
. Message
):
399 raise TypeError ( "msg is not a dns.message.Message" )
401 if not isinstance ( rrset
, dns
. rrset
. RRset
):
402 raise TypeError ( "rrset is not a dns.rrset.RRset" )
405 for ans
in msg
. answer
:
406 ret
+= " %s \n " % ans
. to_text ()
407 if ans
. match ( rrset
. name
, rrset
. rdclass
, rrset
. rdtype
, 0 , None ):
408 self
. assertEqual ( ans
, rrset
, "' %s ' != ' %s '" % ( ans
. to_text (), rrset
. to_text ()))
412 raise AssertionError ( "RRset not found in answer \n\n %s " % ret
)
414 def sortRRsets ( self
, rrsets
):
415 """Sorts RRsets in a more useful way than dnspython's default behaviour
417 @param rrsets: an array of dns.rrset.RRset objects"""
419 return sorted ( rrsets
, key
= lambda rrset
: ( rrset
. name
, rrset
. rdtype
))
421 def assertAnyRRsetInAnswer ( self
, msg
, rrsets
):
422 """Asserts that any of the supplied rrsets exists (without comparing TTL)
423 in the answer section of msg
425 @param msg: the dns.message.Message to check
426 @param rrsets: an array of dns.rrset.RRset object"""
428 if not isinstance ( msg
, dns
. message
. Message
):
429 raise TypeError ( "msg is not a dns.message.Message" )
433 if not isinstance ( rrset
, dns
. rrset
. RRset
):
434 raise TypeError ( "rrset is not a dns.rrset.RRset" )
435 for ans
in msg
. answer
:
436 if ans
. match ( rrset
. name
, rrset
. rdclass
, rrset
. rdtype
, 0 , None ):
441 raise AssertionError ( "RRset not found in answer \n %s " %
442 " \n " . join (([ ans
. to_text () for ans
in msg
. answer
])))
444 def assertMatchingRRSIGInAnswer ( self
, msg
, coveredRRset
, keys
= None ):
445 """Looks for coveredRRset in the answer section and if there is an RRSIG RRset
446 that covers that RRset. If keys is not None, this function will also try to
447 validate the RRset against the RRSIG
449 @param msg: The dns.message.Message to check
450 @param coveredRRset: The RRSet to check for
451 @param keys: a dictionary keyed by dns.name.Name with node or rdataset values to use for validation"""
453 if not isinstance ( msg
, dns
. message
. Message
):
454 raise TypeError ( "msg is not a dns.message.Message" )
456 if not isinstance ( coveredRRset
, dns
. rrset
. RRset
):
457 raise TypeError ( "coveredRRset is not a dns.rrset.RRset" )
463 for ans
in msg
. answer
:
464 ret
+= ans
. to_text () + " \n "
466 if ans
. match ( coveredRRset
. name
, coveredRRset
. rdclass
, coveredRRset
. rdtype
, 0 , None ):
468 if ans
. match ( coveredRRset
. name
, dns
. rdataclass
. IN
, dns
. rdatatype
. RRSIG
, coveredRRset
. rdtype
, None ):
470 if msgRRSet
and msgRRsigRRSet
:
474 raise AssertionError ( "RRset for ' %s ' not found in answer" % msg
. question
[ 0 ]. to_text ())
476 if not msgRRsigRRSet
:
477 raise AssertionError ( "No RRSIGs found in answer for %s : \n Full answer: \n %s " % ( msg
. question
[ 0 ]. to_text (), ret
))
481 dns
. dnssec
. validate ( msgRRSet
, msgRRsigRRSet
. to_rdataset (), keys
)
482 except dns
. dnssec
. ValidationFailure
as e
:
483 raise AssertionError ( "Signature validation failed for %s : \n %s " % ( msg
. question
[ 0 ]. to_text (), e
))
485 def assertNoRRSIGsInAnswer ( self
, msg
):
486 """Checks if there are _no_ RRSIGs in the answer section of msg"""
488 if not isinstance ( msg
, dns
. message
. Message
):
489 raise TypeError ( "msg is not a dns.message.Message" )
492 for ans
in msg
. answer
:
493 if ans
. rdtype
== dns
. rdatatype
. RRSIG
:
494 ret
+= ans
. name
. to_text () + " \n "
497 raise AssertionError ( "RRSIG found in answers for: \n %s " % ret
)
499 def assertAnswerEmpty ( self
, msg
):
500 self
. assertTrue ( len ( msg
. answer
) == 0 , "Data found in the the answer section for %s : \n %s " % ( msg
. question
[ 0 ]. to_text (), ' \n ' . join ([ i
. to_text () for i
in msg
. answer
])))
502 def assertAnswerNotEmpty ( self
, msg
):
503 self
. assertTrue ( len ( msg
. answer
) > 0 , "Answer is empty" )
505 def assertRcodeEqual ( self
, msg
, rcode
):
506 if not isinstance ( msg
, dns
. message
. Message
):
507 raise TypeError ( "msg is not a dns.message.Message but a %s " % type ( msg
))
509 if not isinstance ( rcode
, int ):
510 if isinstance ( rcode
, str ):
511 rcode
= dns
. rcode
. from_text ( rcode
)
513 raise TypeError ( "rcode is neither a str nor int" )
515 if msg
. rcode () != rcode
:
516 msgRcode
= dns
. rcode
._ by
_ value
[ msg
. rcode ()]
517 wantedRcode
= dns
. rcode
._ by
_ value
[ rcode
]
519 raise AssertionError ( "Rcode for %s is %s , expected %s ." % ( msg
. question
[ 0 ]. to_text (), msgRcode
, wantedRcode
))
521 def assertAuthorityHasSOA ( self
, msg
):
522 if not isinstance ( msg
, dns
. message
. Message
):
523 raise TypeError ( "msg is not a dns.message.Message but a %s " % type ( msg
))
526 for rrset
in msg
. authority
:
527 if rrset
. rdtype
== dns
. rdatatype
. SOA
:
532 raise AssertionError ( "No SOA record found in the authority section: \n %s " % msg
. to_text ())