]>
git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.auth-py/authtests.py
15 from pprint
import pprint
17 class AuthTest ( unittest
. TestCase
):
19 Setup auth required for the tests
25 _root_DS
= "63149 13 1 a59da3f5c1b97fcd5fa2b3b2b0ac91d38a60d33a"
27 # The default SOA for zones in the authoritative servers
28 _SOA
= "ns1.example.net. hostmaster.example.net. 1 3600 1800 1209600 300"
30 # The definitions of the zones on the authoritative servers, the key is the
31 # zonename and the value is the zonefile content. several strings are replaced:
32 # - {soa} => value of _SOA
33 # - {prefix} value of _PREFIX
36 example.org. 3600 IN SOA {soa}
37 example.org. 3600 IN NS ns1.example.org.
38 example.org. 3600 IN NS ns2.example.org.
39 ns1.example.org. 3600 IN A {prefix} .10
40 ns2.example.org. 3600 IN A {prefix} .11
46 Private-key-format: v1.2
47 Algorithm: 13 (ECDSAP256SHA256)
48 PrivateKey: Lt0v0Gol3pRUFM7fDdcy0IWN0O/MnEmVPA+VylL8Y4U=
52 _auth_cmd
= [ 'authbind' ,
57 _PREFIX
= os
. environ
[ 'PREFIX' ]
61 def createConfigDir ( cls
, confdir
):
63 shutil
. rmtree ( confdir
)
65 if e
. errno
!= errno
. ENOENT
:
67 os
. mkdir ( confdir
, 0755 )
70 def generateAuthZone ( cls
, confdir
, zonename
, zonecontent
):
71 with
open ( os
. path
. join ( confdir
, ' %s .zone' % zonename
), 'w' ) as zonefile
:
72 zonefile
. write ( zonecontent
. format ( prefix
= cls
._ PREFIX
, soa
= cls
._ SOA
))
75 def generateAuthNamedConf ( cls
, confdir
, zones
):
76 with
open ( os
. path
. join ( confdir
, 'named.conf' ), 'w' ) as namedconf
:
81 for zonename
in zones
:
82 zone
= '.' if zonename
== 'ROOT' else zonename
88 };""" % ( zone
, zonename
))
91 def generateAuthConfig ( cls
, confdir
):
92 bind_dnssec_db
= os
. path
. join ( confdir
, 'bind-dnssec.sqlite3' )
94 with
open ( os
. path
. join ( confdir
, 'pdns.conf' ), 'w' ) as pdnsconf
:
96 module-dir=../regression-tests/modules
100 bind-config= {confdir} /named.conf
101 bind-dnssec-db= {bind_dnssec_db}
109 geoip-database-files=../modules/geoipbackend/regression-tests/GeoLiteCity.mmdb
110 edns-subnet-processing=yes
111 distributor-threads=1""" . format ( confdir
= confdir
,
112 bind_dnssec_db
= bind_dnssec_db
))
114 pdnsutilCmd
= [ os
. environ
[ 'PDNSUTIL' ],
115 '--config-dir= %s ' % confdir
,
119 print ' ' . join ( pdnsutilCmd
)
121 subprocess
. check_output ( pdnsutilCmd
, stderr
= subprocess
. STDOUT
)
122 except subprocess
. CalledProcessError
as e
:
127 def secureZone ( cls
, confdir
, zonename
, key
= None ):
128 zone
= '.' if zonename
== 'ROOT' else zonename
130 pdnsutilCmd
= [ os
. environ
[ 'PDNSUTIL' ],
131 '--config-dir= %s ' % confdir
,
135 keyfile
= os
. path
. join ( confdir
, 'dnssec.key' )
136 with
open ( keyfile
, 'w' ) as fdKeyfile
:
139 pdnsutilCmd
= [ os
. environ
[ 'PDNSUTIL' ],
140 '--config-dir= %s ' % confdir
,
147 print ' ' . join ( pdnsutilCmd
)
149 subprocess
. check_output ( pdnsutilCmd
, stderr
= subprocess
. STDOUT
)
150 except subprocess
. CalledProcessError
as e
:
155 def generateAllAuthConfig ( cls
, confdir
):
157 cls
. generateAuthConfig ( confdir
)
158 cls
. generateAuthNamedConf ( confdir
, cls
._ zones
. keys ())
160 for zonename
, zonecontent
in cls
._ zones
. items ():
161 cls
. generateAuthZone ( confdir
,
164 if cls
._ zone
_ keys
. get ( zonename
, None ):
165 cls
. secureZone ( confdir
, zonename
, cls
._ zone
_ keys
. get ( zonename
))
168 def startAuth ( cls
, confdir
, ipaddress
):
170 print ( "Launching pdns_server.." )
171 authcmd
= list ( cls
._ auth
_ cmd
)
172 authcmd
. append ( '--config-dir= %s ' % confdir
)
173 authcmd
. append ( '--local-address= %s ' % ipaddress
)
174 authcmd
. append ( '--local-port= %s ' % cls
._ authPort
)
175 authcmd
. append ( '--loglevel=9' )
176 authcmd
. append ( '--enable-lua-record' )
177 print ( ' ' . join ( authcmd
))
179 logFile
= os
. path
. join ( confdir
, 'pdns.log' )
180 with
open ( logFile
, 'w' ) as fdLog
:
181 cls
._ auths
[ ipaddress
] = subprocess
. Popen ( authcmd
, close_fds
= True ,
182 stdout
= fdLog
, stderr
= fdLog
,
187 if cls
._ auths
[ ipaddress
]. poll () is not None :
189 cls
._ auths
[ ipaddress
]. kill ()
191 if e
. errno
!= errno
. ESRCH
:
193 with
open ( logFile
, 'r' ) as fdLog
:
195 sys
. exit ( cls
._ auths
[ ipaddress
]. returncode
)
198 def setUpSockets ( cls
):
199 print ( "Setting up UDP socket.." )
200 cls
._ sock
= socket
. socket ( socket
. AF_INET
, socket
. SOCK_DGRAM
)
201 cls
._ sock
. settimeout ( 2.0 )
202 cls
._ sock
. connect (( cls
._ PREFIX
+ ".1" , cls
._ authPort
))
205 def startResponders ( cls
):
212 cls
. startResponders ()
214 confdir
= os
. path
. join ( 'configs' , cls
._ confdir
)
215 cls
. createConfigDir ( confdir
)
217 cls
. generateAllAuthConfig ( confdir
)
218 cls
. startAuth ( confdir
, cls
._ PREFIX
+ ".1" )
220 print ( "Launching tests.." )
223 def tearDownClass ( cls
):
225 cls
. tearDownResponders ()
228 def tearDownResponders ( cls
):
232 def tearDownClass ( cls
):
236 def tearDownAuth ( cls
):
237 if 'PDNSRECURSOR_FAST_TESTS' in os
. environ
:
242 for _
, auth
in cls
._ auths
. items ():
245 if auth
. poll () is None :
247 if auth
. poll () is None :
251 if e
. errno
!= errno
. ESRCH
:
255 def sendUDPQuery ( cls
, query
, timeout
= 2.0 , decode
= True , fwparams
= dict ()):
257 cls
._ sock
. settimeout ( timeout
)
260 cls
._ sock
. send ( query
. to_wire ())
261 data
= cls
._ sock
. recv ( 4096 )
262 except socket
. timeout
:
266 cls
._ sock
. settimeout ( None )
272 message
= dns
. message
. from_wire ( data
, ** fwparams
)
276 def sendTCPQuery ( cls
, query
, timeout
= 2.0 ):
277 sock
= socket
. socket ( socket
. AF_INET
, socket
. SOCK_STREAM
)
279 sock
. settimeout ( timeout
)
281 sock
. connect (( "127.0.0.1" , cls
._ recursorPort
))
284 wire
= query
. to_wire ()
285 sock
. send ( struct
. pack ( "!H" , len ( wire
)))
289 ( datalen
,) = struct
. unpack ( "!H" , data
)
290 data
= sock
. recv ( datalen
)
291 except socket
. timeout
as e
:
292 print ( "Timeout: %s " % ( str ( e
)))
294 except socket
. error
as e
:
295 print ( "Network error: %s " % ( str ( e
)))
302 message
= dns
. message
. from_wire ( data
)
307 def sendTCPQuery ( cls
, query
, timeout
= 2.0 ):
308 sock
= socket
. socket ( socket
. AF_INET
, socket
. SOCK_STREAM
)
310 sock
. settimeout ( timeout
)
312 sock
. connect (( "127.0.0.1" , cls
._ authPort
))
315 wire
= query
. to_wire ()
316 sock
. send ( struct
. pack ( "!H" , len ( wire
)))
320 ( datalen
,) = struct
. unpack ( "!H" , data
)
321 data
= sock
. recv ( datalen
)
322 except socket
. timeout
as e
:
323 print ( "Timeout: %s " % ( str ( e
)))
325 except socket
. error
as e
:
326 print ( "Network error: %s " % ( str ( e
)))
333 message
= dns
. message
. from_wire ( data
)
337 # This function is called before every tests
340 ## Functions for comparisons
341 def assertMessageHasFlags ( self
, msg
, flags
, ednsflags
=[]):
342 """Asserts that msg has all the flags from flags set
344 @param msg: the dns.message.Message to check
345 @param flags: a list of strings with flag mnemonics (like ['RD', 'RA'])
346 @param ednsflags: a list of strings with edns-flag mnemonics (like ['DO'])"""
348 if not isinstance ( msg
, dns
. message
. Message
):
349 raise TypeError ( "msg is not a dns.message.Message" )
351 if isinstance ( flags
, list ):
353 if not isinstance ( elem
, str ):
354 raise TypeError ( "flags is not a list of strings" )
356 raise TypeError ( "flags is not a list of strings" )
358 if isinstance ( ednsflags
, list ):
359 for elem
in ednsflags
:
360 if not isinstance ( elem
, str ):
361 raise TypeError ( "ednsflags is not a list of strings" )
363 raise TypeError ( "ednsflags is not a list of strings" )
365 msgFlags
= dns
. flags
. to_text ( msg
. flags
). split ()
366 missingFlags
= [ flag
for flag
in flags
if flag
not in msgFlags
]
368 msgEdnsFlags
= dns
. flags
. edns_to_text ( msg
. ednsflags
). split ()
369 missingEdnsFlags
= [ ednsflag
for ednsflag
in ednsflags
if ednsflag
not in msgEdnsFlags
]
371 if len ( missingFlags
) or len ( missingEdnsFlags
) or len ( msgFlags
) > len ( flags
):
372 raise AssertionError ( "Expected flags ' %s ' (EDNS: ' %s '), found ' %s ' (EDNS: ' %s ') in query %s " %
373 ( ' ' . join ( flags
), ' ' . join ( ednsflags
),
374 ' ' . join ( msgFlags
), ' ' . join ( msgEdnsFlags
),
377 def assertMessageIsAuthenticated ( self
, msg
):
378 """Asserts that the message has the AD bit set
380 @param msg: the dns.message.Message to check"""
382 if not isinstance ( msg
, dns
. message
. Message
):
383 raise TypeError ( "msg is not a dns.message.Message" )
385 msgFlags
= dns
. flags
. to_text ( msg
. flags
)
386 self
. assertTrue ( 'AD' in msgFlags
, "No AD flag found in the message for %s " % msg
. question
[ 0 ]. name
)
388 def assertRRsetInAnswer ( self
, msg
, rrset
):
389 """Asserts the rrset (without comparing TTL) exists in the
390 answer section of msg
392 @param msg: the dns.message.Message to check
393 @param rrset: a dns.rrset.RRset object"""
396 if not isinstance ( msg
, dns
. message
. Message
):
397 raise TypeError ( "msg is not a dns.message.Message" )
399 if not isinstance ( rrset
, dns
. rrset
. RRset
):
400 raise TypeError ( "rrset is not a dns.rrset.RRset" )
403 for ans
in msg
. answer
:
404 ret
+= " %s \n " % ans
. to_text ()
405 if ans
. match ( rrset
. name
, rrset
. rdclass
, rrset
. rdtype
, 0 , None ):
406 self
. assertEqual ( ans
, rrset
, "' %s ' != ' %s '" % ( ans
. to_text (), rrset
. to_text ()))
410 raise AssertionError ( "RRset not found in answer \n\n %s " % ret
)
412 def assertAnyRRsetInAnswer ( self
, msg
, rrsets
):
413 """Asserts that any of the supplied rrsets exists (without comparing TTL)
414 in the answer section of msg
416 @param msg: the dns.message.Message to check
417 @param rrsets: an array of dns.rrset.RRset object"""
419 if not isinstance ( msg
, dns
. message
. Message
):
420 raise TypeError ( "msg is not a dns.message.Message" )
424 if not isinstance ( rrset
, dns
. rrset
. RRset
):
425 raise TypeError ( "rrset is not a dns.rrset.RRset" )
426 for ans
in msg
. answer
:
427 if ans
. match ( rrset
. name
, rrset
. rdclass
, rrset
. rdtype
, 0 , None ):
432 raise AssertionError ( "RRset not found in answer \n %s " %
433 " \n " . join (([ ans
. to_text () for ans
in msg
. answer
])))
435 def assertMatchingRRSIGInAnswer ( self
, msg
, coveredRRset
, keys
= None ):
436 """Looks for coveredRRset in the answer section and if there is an RRSIG RRset
437 that covers that RRset. If keys is not None, this function will also try to
438 validate the RRset against the RRSIG
440 @param msg: The dns.message.Message to check
441 @param coveredRRset: The RRSet to check for
442 @param keys: a dictionary keyed by dns.name.Name with node or rdataset values to use for validation"""
444 if not isinstance ( msg
, dns
. message
. Message
):
445 raise TypeError ( "msg is not a dns.message.Message" )
447 if not isinstance ( coveredRRset
, dns
. rrset
. RRset
):
448 raise TypeError ( "coveredRRset is not a dns.rrset.RRset" )
454 for ans
in msg
. answer
:
455 ret
+= ans
. to_text () + " \n "
457 if ans
. match ( coveredRRset
. name
, coveredRRset
. rdclass
, coveredRRset
. rdtype
, 0 , None ):
459 if ans
. match ( coveredRRset
. name
, dns
. rdataclass
. IN
, dns
. rdatatype
. RRSIG
, coveredRRset
. rdtype
, None ):
461 if msgRRSet
and msgRRsigRRSet
:
465 raise AssertionError ( "RRset for ' %s ' not found in answer" % msg
. question
[ 0 ]. to_text ())
467 if not msgRRsigRRSet
:
468 raise AssertionError ( "No RRSIGs found in answer for %s : \n Full answer: \n %s " % ( msg
. question
[ 0 ]. to_text (), ret
))
472 dns
. dnssec
. validate ( msgRRSet
, msgRRsigRRSet
. to_rdataset (), keys
)
473 except dns
. dnssec
. ValidationFailure
as e
:
474 raise AssertionError ( "Signature validation failed for %s : \n %s " % ( msg
. question
[ 0 ]. to_text (), e
))
476 def assertNoRRSIGsInAnswer ( self
, msg
):
477 """Checks if there are _no_ RRSIGs in the answer section of msg"""
479 if not isinstance ( msg
, dns
. message
. Message
):
480 raise TypeError ( "msg is not a dns.message.Message" )
483 for ans
in msg
. answer
:
484 if ans
. rdtype
== dns
. rdatatype
. RRSIG
:
485 ret
+= ans
. name
. to_text () + " \n "
488 raise AssertionError ( "RRSIG found in answers for: \n %s " % ret
)
490 def assertAnswerEmpty ( self
, msg
):
491 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
])))
493 def assertAnswerNotEmpty ( self
, msg
):
494 self
. assertTrue ( len ( msg
. answer
) > 0 , "Answer is empty" )
496 def assertRcodeEqual ( self
, msg
, rcode
):
497 if not isinstance ( msg
, dns
. message
. Message
):
498 raise TypeError ( "msg is not a dns.message.Message but a %s " % type ( msg
))
500 if not isinstance ( rcode
, int ):
501 if isinstance ( rcode
, str ):
502 rcode
= dns
. rcode
. from_text ( rcode
)
504 raise TypeError ( "rcode is neither a str nor int" )
506 if msg
. rcode () != rcode
:
507 msgRcode
= dns
. rcode
._ by
_ value
[ msg
. rcode ()]
508 wantedRcode
= dns
. rcode
._ by
_ value
[ rcode
]
510 raise AssertionError ( "Rcode for %s is %s , expected %s ." % ( msg
. question
[ 0 ]. to_text (), msgRcode
, wantedRcode
))
512 def assertAuthorityHasSOA ( self
, msg
):
513 if not isinstance ( msg
, dns
. message
. Message
):
514 raise TypeError ( "msg is not a dns.message.Message but a %s " % type ( msg
))
517 for rrset
in msg
. authority
:
518 if rrset
. rdtype
== dns
. rdatatype
. SOA
:
523 raise AssertionError ( "No SOA record found in the authority section: \n %s " % msg
. to_text ())