]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Run all ECS tests also in hardened mode and add a specific hardened mode test
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Thu, 8 May 2025 12:05:39 +0000 (14:05 +0200)
committerOtto Moerbeek <otto.moerbeek@open-xchange.com>
Wed, 28 May 2025 11:21:23 +0000 (13:21 +0200)
regression-tests.recursor-dnssec/test_ECS.py

index e3934e3ab618c75bafd34fcd2e0e18ddab094d12..dde1dfe8ea8e7097bb290c28d43f7fe5bbeb9f41 100644 (file)
@@ -36,7 +36,6 @@ ecs-add-for=0.0.0.0/0
 
     def sendECSQuery(self, query, expected, expectedFirstTTL=None, scopeZeroResponse=None):
         res = self.sendUDPQuery(query)
-
         self.assertRcodeEqual(res, dns.rcode.NOERROR)
         self.assertRRsetInAnswer(res, expected)
         # this will break if you are not looking for the first RR, sorry!
@@ -141,7 +140,106 @@ forward-zones=ecs-echo.example=%s.21
         query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
         self.sendECSQuery(query, expected)
 
-class testIncomingNoECS(ECSTest):
+class NoECSHardenedTest(NoECSTest):
+    _confdir = 'NoECSHardened'
+
+    _config_template = """
+edns-subnet-harden=yes
+edns-subnet-allow-list=
+forward-zones=ecs-echo.example=%s.21
+    """ % (os.environ['PREFIX'])
+
+class NoECSInAnswerTest(ECSTest):
+    _confdir = 'NoECSInAnswer'
+
+    _wsPort = 8042
+    _wsTimeout = 2
+    _wsPassword = 'secretpassword'
+    _apiKey = 'secretapikey'
+    _config_template = """edns-subnet-allow-list=xecs-echo.example
+forward-zones=xecs-echo.example=%s.21
+webserver=yes
+webserver-port=%d
+webserver-address=127.0.0.1
+webserver-password=%s
+webserver-allow-from=127.0.0.1
+api-key=%s
+    """ % (os.environ['PREFIX'], _wsPort, _wsPassword, _apiKey)
+
+    def test1SendECS(self):
+        expected = dns.rrset.from_text('x'+ nameECS, ttlECS, dns.rdataclass.IN, 'TXT', 'X')
+        ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
+        query = dns.message.make_query('x' + nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
+        self.sendECSQuery(query, expected)
+        self.checkMetrics({
+            'ecs-missing': 0
+        })
+
+    def test2NoECS(self):
+        expected = dns.rrset.from_text('x' + nameECS, ttlECS, dns.rdataclass.IN, 'TXT', 'X')
+        query = dns.message.make_query('x' + nameECS, 'TXT')
+        self.sendECSQuery(query, expected)
+        self.checkMetrics({
+            'ecs-missing': 0
+        })
+
+    def test3RequireNoECS(self):
+        expected = dns.rrset.from_text('x' + nameECS, ttlECS, dns.rdataclass.IN, 'TXT', 'X')
+
+        ecso = clientsubnetoption.ClientSubnetOption('0.0.0.0', 0)
+        query = dns.message.make_query('x' + nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
+        self.sendECSQuery(query, expected)
+        self.checkMetrics({
+            'ecs-missing': 0
+        })
+
+class NoECSInAnswerHardenedTest(NoECSInAnswerTest):
+    _confdir = 'NoECSInAnswerHardened'
+
+    _wsPort = 8042
+    _wsTimeout = 2
+    _wsPassword = 'secretpassword'
+    _apiKey = 'secretapikey'
+    _config_template = """
+edns-subnet-harden=yes
+edns-subnet-allow-list=xecs-echo.example
+forward-zones=xecs-echo.example=%s.21
+webserver=yes
+webserver-port=%d
+webserver-address=127.0.0.1
+webserver-password=%s
+webserver-allow-from=127.0.0.1
+api-key=%s
+    """ % (os.environ['PREFIX'], _wsPort, _wsPassword, _apiKey)
+
+    # All test below have ecs-missing count to be 1, as they result is a no ecs scoped answer in the cache
+    def test1SendECS(self):
+        expected = dns.rrset.from_text('x'+ nameECS, ttlECS, dns.rdataclass.IN, 'TXT', 'X')
+        ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
+        query = dns.message.make_query('x' + nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
+        self.sendECSQuery(query, expected)
+        self.checkMetrics({
+            'ecs-missing': 1
+        })
+
+    def test2NoECS(self):
+        expected = dns.rrset.from_text('x' + nameECS, ttlECS, dns.rdataclass.IN, 'TXT', 'X')
+        query = dns.message.make_query('x' + nameECS, 'TXT')
+        self.sendECSQuery(query, expected)
+        self.checkMetrics({
+            'ecs-missing': 1
+        })
+
+    def test3RequireNoECS(self):
+        expected = dns.rrset.from_text('x' + nameECS, ttlECS, dns.rdataclass.IN, 'TXT', 'X')
+        ecso = clientsubnetoption.ClientSubnetOption('0.0.0.0', 0)
+        query = dns.message.make_query('x' + nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
+        self.sendECSQuery(query, expected)
+        self.checkMetrics({
+            'ecs-missing': 1
+        })
+
+class IncomingNoECSTest(ECSTest):
     _confdir = 'IncomingNoECS'
 
     _config_template = """edns-subnet-allow-list=
@@ -169,7 +267,17 @@ forward-zones=ecs-echo.example=%s.21
         query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
         self.sendECSQuery(query, expected, scopeZeroResponse=True)
 
-class testECSByName(ECSTest):
+class IncomingNoECSHardenedTest(IncomingNoECSTest):
+    _confdir = 'IncomingNoECSHardened'
+
+    _config_template = """
+edns-subnet-harden=yes
+edns-subnet-allow-list=
+use-incoming-edns-subnet=yes
+forward-zones=ecs-echo.example=%s.21
+    """ % (os.environ['PREFIX'])
+
+class ECSByNameTest(ECSTest):
     _confdir = 'ECSByName'
 
     _config_template = """edns-subnet-allow-list=ecs-echo.example.
@@ -234,8 +342,20 @@ ecs-ipv6-cache-bits=128
         query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
         self.sendECSQuery(query, expected)
 
-class testECSByNameSmaller(ECSTest):
-    _confdir = 'ECSByNameLarger'
+class ECSByNameLargerHardenedTest(ECSByNameLargerTest):
+    _confdir = 'ECSByNameLargerHardened'
+
+    _config_template = """
+edns-subnet-harden=yes
+edns-subnet-allow-list=ecs-echo.example.
+ecs-ipv4-bits=32
+forward-zones=ecs-echo.example=%s.21
+ecs-ipv4-cache-bits=32
+ecs-ipv6-cache-bits=128
+    """ % (os.environ['PREFIX'])
+
+class ECSByNameSmallerTest(ECSTest):
+    _confdir = 'ECSByNameSmaller'
 
     _config_template = """edns-subnet-allow-list=ecs-echo.example.
 ecs-ipv4-bits=16
@@ -261,8 +381,18 @@ forward-zones=ecs-echo.example=%s.21
         query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
         self.sendECSQuery(query, expected)
 
-class testIncomingECSByName(ECSTest):
-    _confdir = 'ECSIncomingByName'
+class ECSByNameSmallerHardenedTest(ECSByNameSmallerTest):
+    _confdir = 'ECSByNameSmallerHardened'
+
+    _config_template = """
+edns-subnet-harden=yes
+edns-subnet-allow-list=ecs-echo.example.
+ecs-ipv4-bits=16
+forward-zones=ecs-echo.example=%s.21
+    """ % (os.environ['PREFIX'])
+
+class IncomingECSByNameTest(ECSTest):
+    _confdir = 'IncomingECSByName'
 
     _config_template = """edns-subnet-allow-list=ecs-echo.example.
 use-incoming-edns-subnet=yes
@@ -301,8 +431,21 @@ ecs-ipv6-cache-bits=128
         query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
         self.sendECSQuery(query, expected, ttlECS)
 
-class testIncomingECSByNameLarger(ECSTest):
-    _confdir = 'ECSIncomingByNameLarger'
+class IncomingECSByNameHardenedTest(IncomingECSByNameTest):
+    _confdir = 'IncomingECSByNameHardened'
+
+    _config_template = """
+edns-subnet-harden=yes
+edns-subnet-allow-list=ecs-echo.example.
+use-incoming-edns-subnet=yes
+forward-zones=ecs-echo.example=%s.21
+ecs-scope-zero-address=2001:db8::42
+ecs-ipv4-cache-bits=32
+ecs-ipv6-cache-bits=128
+    """ % (os.environ['PREFIX'])
+
+class IncomingECSByNameLargerTest(ECSTest):
+    _confdir = 'IncomingECSByNameLarger'
 
     _config_template = """edns-subnet-allow-list=ecs-echo.example.
 use-incoming-edns-subnet=yes
@@ -333,8 +476,22 @@ ecs-ipv6-cache-bits=128
         query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
         self.sendECSQuery(query, expected, ttlECS)
 
-class testIncomingECSByNameSmaller(ECSTest):
-    _confdir = 'ECSIncomingByNameSmaller'
+class IncomingECSByNameLargerHardenedTest(IncomingECSByNameLargerTest):
+    _confdir = 'IncomingECSByNameLargerHardened'
+
+    _config_template = """
+edns-subnet-harden=yes
+edns-subnet-allow-list=ecs-echo.example.
+use-incoming-edns-subnet=yes
+ecs-ipv4-bits=32
+forward-zones=ecs-echo.example=%s.21
+ecs-scope-zero-address=192.168.0.1
+ecs-ipv4-cache-bits=32
+ecs-ipv6-cache-bits=128
+    """ % (os.environ['PREFIX'])
+
+class IncomingECSByNameSmallerTest(ECSTest):
+    _confdir = 'IncomingECSByNameSmaller'
 
     _config_template = """edns-subnet-allow-list=ecs-echo.example.
 use-incoming-edns-subnet=yes
@@ -363,6 +520,20 @@ ecs-ipv6-cache-bits=128
         query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
         self.sendECSQuery(query, expected, ttlECS)
 
+class IncomingECSByNameSmallerHardenedTest(IncomingECSByNameSmallerTest):
+    _confdir = 'IncomingECSByNameSmallerHardened'
+
+    _config_template = """
+edns-subnet-harden=yes
+edns-subnet-allow-list=ecs-echo.example.
+use-incoming-edns-subnet=yes
+ecs-ipv4-bits=16
+forward-zones=ecs-echo.example=%s.21
+ecs-scope-zero-address=192.168.0.1
+ecs-ipv4-cache-bits=32
+ecs-ipv6-cache-bits=128
+    """ % (os.environ['PREFIX'])
+
 @unittest.skipIf(not have_ipv6(), "No IPv6")
 class testIncomingECSByNameV6(ECSTest):
     _confdir = 'ECSIncomingByNameV6'
@@ -422,7 +593,22 @@ forward-zones=ecs-echo.example=%s.21
         query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
         self.sendECSQuery(query, expected)
 
-class testECSByIP(ECSTest):
+@unittest.skipIf(not have_ipv6(), "No IPv6")
+class IncomingECSByNameV6HardenedTest(IncomingECSByNameV6Test):
+    _confdir = 'IncomingECSByNameV6Hardened'
+
+    _config_template = """
+edns-subnet-harden=yes
+edns-subnet-allow-list=ecs-echo.example.
+use-incoming-edns-subnet=yes
+ecs-ipv6-bits=128
+ecs-ipv4-cache-bits=32
+ecs-ipv6-cache-bits=128
+query-local-address=::1
+forward-zones=ecs-echo.example=[::1]:53000
+    """
+
+class ECSByIPTest(ECSTest):
     _confdir = 'ECSByIP'
 
     _config_template = """edns-subnet-allow-list=%s.21
@@ -448,8 +634,17 @@ forward-zones=ecs-echo.example=%s.21
         query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
         self.sendECSQuery(query, expected)
 
-class testIncomingECSByIP(ECSTest):
-    _confdir = 'ECSIncomingByIP'
+class ECSByIPHardenedTest(ECSByIPTest):
+    _confdir = 'ECSByIPHardened'
+
+    _config_template = """
+edns-subnet-harden=yes
+edns-subnet-allow-list=%s.21
+forward-zones=ecs-echo.example=%s.21
+    """ % (os.environ['PREFIX'], os.environ['PREFIX'])
+
+class IncomingECSByIPTest(ECSTest):
+    _confdir = 'IncomingECSByIP'
 
     _config_template = """edns-subnet-allow-list=%s.21
 use-incoming-edns-subnet=yes
@@ -488,7 +683,20 @@ ecs-ipv6-cache-bits=128
 
         self.sendECSQuery(query, expected)
 
-class testECSIPMismatch(ECSTest):
+class IncomingECSByIPHardenedTest(IncomingECSByIPTest):
+    _confdir = 'IncomingECSByIPHardened'
+
+    _config_template = """
+edns-subnet-harden=yes
+edns-subnet-allow-list=%s.21
+use-incoming-edns-subnet=yes
+forward-zones=ecs-echo.example=%s.21
+ecs-scope-zero-address=::1
+ecs-ipv4-cache-bits=32
+ecs-ipv6-cache-bits=128
+    """ % (os.environ['PREFIX'], os.environ['PREFIX'])
+
+class ECSIPMismatchTest(ECSTest):
     _confdir = 'ECSIPMismatch'
 
     _config_template = """edns-subnet-allow-list=192.0.2.1
@@ -514,8 +722,17 @@ forward-zones=ecs-echo.example=%s.21
         query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
         self.sendECSQuery(query, expected)
 
-class testECSWithProxyProtocoldRecursorTest(ECSTest):
-    _confdir = 'ECSWithProxyProtocol'
+class ECSIPMismatchHardenedTest(ECSIPMismatchTest):
+    _confdir = 'ECSIPMismatchHardened'
+
+    _config_template = """
+edns-subnet-harden=yes
+edns-subnet-allow-list=192.0.2.1
+forward-zones=ecs-echo.example=%s.21
+    """ % (os.environ['PREFIX'])
+
+class ECSWithProxyProtocolRecursorTest(ECSTest):
+    _confdir = 'ECSWithProxyProtocolRecursor'
     _config_template = """
     ecs-add-for=2001:db8::1/128
     edns-subnet-allow-list=ecs-echo.example.
@@ -535,7 +752,18 @@ class testECSWithProxyProtocoldRecursorTest(ECSTest):
             self.assertRcodeEqual(res, dns.rcode.NOERROR)
             self.assertRRsetInAnswer(res, expected)
 
-class testTooLargeToAddZeroScope(RecursorTest):
+class ECSWithProxyProtocolRecursorHardenedTest(ECSWithProxyProtocolRecursorTest):
+    _confdir = 'ECSWithProxyProtocolRecursorHardened'
+    _config_template = """
+    edns-subnet-harden=yes
+    ecs-add-for=2001:db8::1/128
+    edns-subnet-allow-list=ecs-echo.example.
+    forward-zones=ecs-echo.example=%s.21
+    proxy-protocol-from=127.0.0.1/32
+    allow-from=2001:db8::1/128
+""" % (os.environ['PREFIX'])
+
+class TooLargeToAddZeroScopeTest(RecursorTest):
 
     _confdir = 'TooLargeToAddZeroScope'
     _config_template = """
@@ -637,6 +865,10 @@ class UDPECSResponder(DatagramProtocol):
             additional = dns.rrset.from_text('ns1.ecs-echo.example.', 15, dns.rdataclass.IN, 'A', os.environ['PREFIX'] + '.21')
             response.additional.append(additional)
 
+        elif request.question[0].name == dns.name.from_text('x' + nameECS):
+            answer = dns.rrset.from_text(request.question[0].name, ttlECS, dns.rdataclass.IN, 'TXT', 'X')
+            response.answer.append(answer)
+
         if ecso:
             response.use_edns(options = [ecso])