]>
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
:
131 def secureZone ( cls
, confdir
, zonename
, key
= None ):
132 zone
= '.' if zonename
== 'ROOT' else zonename
134 pdnsutilCmd
= [ os
. environ
[ 'PDNSUTIL' ],
135 '--config-dir= %s ' % confdir
,
139 keyfile
= os
. path
. join ( confdir
, 'dnssec.key' )
140 with
open ( keyfile
, 'w' ) as fdKeyfile
:
143 pdnsutilCmd
= [ os
. environ
[ 'PDNSUTIL' ],
144 '--config-dir= %s ' % confdir
,
151 print ( ' ' . join ( pdnsutilCmd
))
153 subprocess
. check_output ( pdnsutilCmd
, stderr
= subprocess
. STDOUT
)
154 except subprocess
. CalledProcessError
as e
:
159 def generateAllAuthConfig ( cls
, confdir
):
161 cls
. generateAuthConfig ( confdir
)
162 cls
. generateAuthNamedConf ( confdir
, cls
._ zones
. keys ())
164 for zonename
, zonecontent
in cls
._ zones
. items ():
165 cls
. generateAuthZone ( confdir
,
168 if cls
._ zone
_ keys
. get ( zonename
, None ):
169 cls
. secureZone ( confdir
, zonename
, cls
._ zone
_ keys
. get ( zonename
))
172 def startAuth ( cls
, confdir
, ipaddress
):
174 print ( "Launching pdns_server.." )
175 authcmd
= list ( cls
._ auth
_ cmd
)
176 authcmd
. append ( '--config-dir= %s ' % confdir
)
177 authcmd
. append ( '--local-address= %s ' % ipaddress
)
178 authcmd
. append ( '--local-port= %s ' % cls
._ authPort
)
179 authcmd
. append ( '--loglevel=9' )
180 authcmd
. append ( '--enable-lua-record' )
181 print ( ' ' . join ( authcmd
))
183 logFile
= os
. path
. join ( confdir
, 'pdns.log' )
184 with
open ( logFile
, 'w' ) as fdLog
:
185 cls
._ auths
[ ipaddress
] = subprocess
. Popen ( authcmd
, close_fds
= True ,
186 stdout
= fdLog
, stderr
= fdLog
,
191 if cls
._ auths
[ ipaddress
]. poll () is not None :
193 cls
._ auths
[ ipaddress
]. kill ()
195 if e
. errno
!= errno
. ESRCH
:
197 with
open ( logFile
, 'r' ) as fdLog
:
199 sys
. exit ( cls
._ auths
[ ipaddress
]. returncode
)
202 def setUpSockets ( cls
):
203 print ( "Setting up UDP socket.." )
204 cls
._ sock
= socket
. socket ( socket
. AF_INET
, socket
. SOCK_DGRAM
)
205 cls
._ sock
. settimeout ( 2.0 )
206 cls
._ sock
. connect (( cls
._ PREFIX
+ ".1" , cls
._ authPort
))
209 def startResponders ( cls
):
216 cls
. startResponders ()
218 confdir
= os
. path
. join ( 'configs' , cls
._ confdir
)
219 cls
. createConfigDir ( confdir
)
221 cls
. generateAllAuthConfig ( confdir
)
222 cls
. startAuth ( confdir
, cls
._ PREFIX
+ ".1" )
224 print ( "Launching tests.." )
227 def tearDownClass ( cls
):
229 cls
. tearDownResponders ()
232 def tearDownResponders ( cls
):
236 def tearDownClass ( cls
):
240 def tearDownAuth ( cls
):
241 if 'PDNSRECURSOR_FAST_TESTS' in os
. environ
:
246 for _
, auth
in cls
._ auths
. items ():
249 if auth
. poll () is None :
251 if auth
. poll () is None :
255 if e
. errno
!= errno
. ESRCH
:
259 def sendUDPQuery ( cls
, query
, timeout
= 2.0 , decode
= True , fwparams
= dict ()):
261 cls
._ sock
. settimeout ( timeout
)
264 cls
._ sock
. send ( query
. to_wire ())
265 data
= cls
._ sock
. recv ( 4096 )
266 except socket
. timeout
:
270 cls
._ sock
. settimeout ( None )
276 message
= dns
. message
. from_wire ( data
, ** fwparams
)
280 def sendTCPQuery ( cls
, query
, timeout
= 2.0 ):
281 sock
= socket
. socket ( socket
. AF_INET
, socket
. SOCK_STREAM
)
283 sock
. settimeout ( timeout
)
285 sock
. connect (( "127.0.0.1" , cls
._ recursorPort
))
288 wire
= query
. to_wire ()
289 sock
. send ( struct
. pack ( "!H" , len ( wire
)))
293 ( datalen
,) = struct
. unpack ( "!H" , data
)
294 data
= sock
. recv ( datalen
)
295 except socket
. timeout
as e
:
296 print ( "Timeout: %s " % ( str ( e
)))
298 except socket
. error
as e
:
299 print ( "Network error: %s " % ( str ( e
)))
306 message
= dns
. message
. from_wire ( data
)
311 def sendTCPQuery ( cls
, query
, timeout
= 2.0 ):
312 sock
= socket
. socket ( socket
. AF_INET
, socket
. SOCK_STREAM
)
314 sock
. settimeout ( timeout
)
316 sock
. connect (( "127.0.0.1" , cls
._ authPort
))
319 wire
= query
. to_wire ()
320 sock
. send ( struct
. pack ( "!H" , len ( wire
)))
324 ( datalen
,) = struct
. unpack ( "!H" , data
)
325 data
= sock
. recv ( datalen
)
326 except socket
. timeout
as e
:
327 print ( "Timeout: %s " % ( str ( e
)))
329 except socket
. error
as e
:
330 print ( "Network error: %s " % ( str ( e
)))
337 message
= dns
. message
. from_wire ( data
)
341 # This function is called before every tests
344 ## Functions for comparisons
345 def assertMessageHasFlags ( self
, msg
, flags
, ednsflags
=[]):
346 """Asserts that msg has all the flags from flags set
348 @param msg: the dns.message.Message to check
349 @param flags: a list of strings with flag mnemonics (like ['RD', 'RA'])
350 @param ednsflags: a list of strings with edns-flag mnemonics (like ['DO'])"""
352 if not isinstance ( msg
, dns
. message
. Message
):
353 raise TypeError ( "msg is not a dns.message.Message" )
355 if isinstance ( flags
, list ):
357 if not isinstance ( elem
, str ):
358 raise TypeError ( "flags is not a list of strings" )
360 raise TypeError ( "flags is not a list of strings" )
362 if isinstance ( ednsflags
, list ):
363 for elem
in ednsflags
:
364 if not isinstance ( elem
, str ):
365 raise TypeError ( "ednsflags is not a list of strings" )
367 raise TypeError ( "ednsflags is not a list of strings" )
369 msgFlags
= dns
. flags
. to_text ( msg
. flags
). split ()
370 missingFlags
= [ flag
for flag
in flags
if flag
not in msgFlags
]
372 msgEdnsFlags
= dns
. flags
. edns_to_text ( msg
. ednsflags
). split ()
373 missingEdnsFlags
= [ ednsflag
for ednsflag
in ednsflags
if ednsflag
not in msgEdnsFlags
]
375 if len ( missingFlags
) or len ( missingEdnsFlags
) or len ( msgFlags
) > len ( flags
):
376 raise AssertionError ( "Expected flags ' %s ' (EDNS: ' %s '), found ' %s ' (EDNS: ' %s ') in query %s " %
377 ( ' ' . join ( flags
), ' ' . join ( ednsflags
),
378 ' ' . join ( msgFlags
), ' ' . join ( msgEdnsFlags
),
381 def assertMessageIsAuthenticated ( self
, msg
):
382 """Asserts that the message has the AD bit set
384 @param msg: the dns.message.Message to check"""
386 if not isinstance ( msg
, dns
. message
. Message
):
387 raise TypeError ( "msg is not a dns.message.Message" )
389 msgFlags
= dns
. flags
. to_text ( msg
. flags
)
390 self
. assertTrue ( 'AD' in msgFlags
, "No AD flag found in the message for %s " % msg
. question
[ 0 ]. name
)
392 def assertRRsetInAnswer ( self
, msg
, rrset
):
393 """Asserts the rrset (without comparing TTL) exists in the
394 answer section of msg
396 @param msg: the dns.message.Message to check
397 @param rrset: a dns.rrset.RRset object"""
400 if not isinstance ( msg
, dns
. message
. Message
):
401 raise TypeError ( "msg is not a dns.message.Message" )
403 if not isinstance ( rrset
, dns
. rrset
. RRset
):
404 raise TypeError ( "rrset is not a dns.rrset.RRset" )
407 for ans
in msg
. answer
:
408 ret
+= " %s \n " % ans
. to_text ()
409 if ans
. match ( rrset
. name
, rrset
. rdclass
, rrset
. rdtype
, 0 , None ):
410 self
. assertEqual ( ans
, rrset
, "' %s ' != ' %s '" % ( ans
. to_text (), rrset
. to_text ()))
414 raise AssertionError ( "RRset not found in answer \n\n %s " % ret
)
416 def sortRRsets ( self
, rrsets
):
417 """Sorts RRsets in a more useful way than dnspython's default behaviour
419 @param rrsets: an array of dns.rrset.RRset objects"""
421 return sorted ( rrsets
, key
= lambda rrset
: ( rrset
. name
, rrset
. rdtype
))
423 def assertAnyRRsetInAnswer ( self
, msg
, rrsets
):
424 """Asserts that any of the supplied rrsets exists (without comparing TTL)
425 in the answer section of msg
427 @param msg: the dns.message.Message to check
428 @param rrsets: an array of dns.rrset.RRset object"""
430 if not isinstance ( msg
, dns
. message
. Message
):
431 raise TypeError ( "msg is not a dns.message.Message" )
435 if not isinstance ( rrset
, dns
. rrset
. RRset
):
436 raise TypeError ( "rrset is not a dns.rrset.RRset" )
437 for ans
in msg
. answer
:
438 if ans
. match ( rrset
. name
, rrset
. rdclass
, rrset
. rdtype
, 0 , None ):
443 raise AssertionError ( "RRset not found in answer \n %s " %
444 " \n " . join (([ ans
. to_text () for ans
in msg
. answer
])))
446 def assertMatchingRRSIGInAnswer ( self
, msg
, coveredRRset
, keys
= None ):
447 """Looks for coveredRRset in the answer section and if there is an RRSIG RRset
448 that covers that RRset. If keys is not None, this function will also try to
449 validate the RRset against the RRSIG
451 @param msg: The dns.message.Message to check
452 @param coveredRRset: The RRSet to check for
453 @param keys: a dictionary keyed by dns.name.Name with node or rdataset values to use for validation"""
455 if not isinstance ( msg
, dns
. message
. Message
):
456 raise TypeError ( "msg is not a dns.message.Message" )
458 if not isinstance ( coveredRRset
, dns
. rrset
. RRset
):
459 raise TypeError ( "coveredRRset is not a dns.rrset.RRset" )
465 for ans
in msg
. answer
:
466 ret
+= ans
. to_text () + " \n "
468 if ans
. match ( coveredRRset
. name
, coveredRRset
. rdclass
, coveredRRset
. rdtype
, 0 , None ):
470 if ans
. match ( coveredRRset
. name
, dns
. rdataclass
. IN
, dns
. rdatatype
. RRSIG
, coveredRRset
. rdtype
, None ):
472 if msgRRSet
and msgRRsigRRSet
:
476 raise AssertionError ( "RRset for ' %s ' not found in answer" % msg
. question
[ 0 ]. to_text ())
478 if not msgRRsigRRSet
:
479 raise AssertionError ( "No RRSIGs found in answer for %s : \n Full answer: \n %s " % ( msg
. question
[ 0 ]. to_text (), ret
))
483 dns
. dnssec
. validate ( msgRRSet
, msgRRsigRRSet
. to_rdataset (), keys
)
484 except dns
. dnssec
. ValidationFailure
as e
:
485 raise AssertionError ( "Signature validation failed for %s : \n %s " % ( msg
. question
[ 0 ]. to_text (), e
))
487 def assertNoRRSIGsInAnswer ( self
, msg
):
488 """Checks if there are _no_ RRSIGs in the answer section of msg"""
490 if not isinstance ( msg
, dns
. message
. Message
):
491 raise TypeError ( "msg is not a dns.message.Message" )
494 for ans
in msg
. answer
:
495 if ans
. rdtype
== dns
. rdatatype
. RRSIG
:
496 ret
+= ans
. name
. to_text () + " \n "
499 raise AssertionError ( "RRSIG found in answers for: \n %s " % ret
)
501 def assertAnswerEmpty ( self
, msg
):
502 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
])))
504 def assertAnswerNotEmpty ( self
, msg
):
505 self
. assertTrue ( len ( msg
. answer
) > 0 , "Answer is empty" )
507 def assertRcodeEqual ( self
, msg
, rcode
):
508 if not isinstance ( msg
, dns
. message
. Message
):
509 raise TypeError ( "msg is not a dns.message.Message but a %s " % type ( msg
))
511 if not isinstance ( rcode
, int ):
512 if isinstance ( rcode
, str ):
513 rcode
= dns
. rcode
. from_text ( rcode
)
515 raise TypeError ( "rcode is neither a str nor int" )
517 if msg
. rcode () != rcode
:
518 msgRcode
= dns
. rcode
._ by
_ value
[ msg
. rcode ()]
519 wantedRcode
= dns
. rcode
._ by
_ value
[ rcode
]
521 raise AssertionError ( "Rcode for %s is %s , expected %s ." % ( msg
. question
[ 0 ]. to_text (), msgRcode
, wantedRcode
))
523 def assertAuthorityHasSOA ( self
, msg
):
524 if not isinstance ( msg
, dns
. message
. Message
):
525 raise TypeError ( "msg is not a dns.message.Message but a %s " % type ( msg
))
528 for rrset
in msg
. authority
:
529 if rrset
. rdtype
== dns
. rdatatype
. SOA
:
534 raise AssertionError ( "No SOA record found in the authority section: \n %s " % msg
. to_text ())