From 1fa892ea5a6db061d415a3ebe2b94105aaba57fd Mon Sep 17 00:00:00 2001 From: hamalq Date: Tue, 14 Jul 2020 21:05:05 +0000 Subject: [PATCH] Fix multi messages AXFR with TSIG TSIG sign for multi messages AXFR was using the wrong dnspython signing function which does not support sequence messages in response which cause the AXFR error below WARNING -- Some TSIG could not be validated Change-Id: I7ce84ac9cf5bc5f6fca47dc79c51fe0becf96ac6 Closes-Bug: 1886685 --- designate/mdns/handler.py | 44 +++++++++---- designate/tests/test_mdns/test_handler.py | 78 +++++++++++++++++++++++ 2 files changed, 108 insertions(+), 14 deletions(-) diff --git a/designate/mdns/handler.py b/designate/mdns/handler.py index 315b4c2fc..5db9f593c 100644 --- a/designate/mdns/handler.py +++ b/designate/mdns/handler.py @@ -239,6 +239,10 @@ class RequestHandler(xfr.XFRMixin): records.insert(0, soa_records[0]) records.append(soa_records[0]) + # Handle multi message response with tsig + multi_messages = False + multi_messages_context = None + # Render the results, yielding a packet after each TooBig exception. renderer = None while records: @@ -260,6 +264,9 @@ class RequestHandler(xfr.XFRMixin): renderer.add_rrset(dns.renderer.ANSWER, rrset) break except dns.exception.TooBig: + # The response will span multiple messages since one + # message is not enough + multi_messages = True if renderer.counts[dns.renderer.ANSWER] == 0: # We've received a TooBig from the first attempted # RRSet in this packet. Log a warning and abort the @@ -280,11 +287,16 @@ class RequestHandler(xfr.XFRMixin): ) return - yield self._finalize_packet(renderer, request) + renderer, multi_messages_context = self._finalize_packet( + renderer, request, multi_messages, + multi_messages_context) + yield renderer renderer = None if renderer: - yield self._finalize_packet(renderer, request) + renderer, multi_messages_context = self._finalize_packet( + renderer, request, multi_messages, multi_messages_context) + yield renderer return def _handle_record_query(self, request): @@ -396,22 +408,26 @@ class RequestHandler(xfr.XFRMixin): recordset.name, ttl, dns.rdataclass.IN, recordset.type, rdata) @staticmethod - def _finalize_packet(renderer, request): + def _finalize_packet(renderer, request, multi_messages=False, + multi_messages_context=None): renderer.write_header() if request.had_tsig: # Make the space we reserved for TSIG available for use renderer.max_size += TSIG_RRSIZE - renderer.add_tsig( - request.keyname, - request.keyring[request.keyname], - request.fudge, - request.original_id, - request.tsig_error, - request.other_data, - request.mac, - request.keyalgorithm - ) - return renderer + if multi_messages: + # The first message context will be None then the + # context for the prev message is used for the next + multi_messages_context = renderer.add_multi_tsig( + multi_messages_context, request.keyname, + request.keyring[request.keyname], request.fudge, + request.original_id, request.tsig_error, + request.other_data, request.mac, request.keyalgorithm) + else: + renderer.add_tsig(request.keyname, + request.keyring[request.keyname], request.fudge, + request.original_id, request.tsig_error, + request.other_data, request.mac, request.keyalgorithm) + return renderer, multi_messages_context @staticmethod def _get_max_message_size(had_tsig): diff --git a/designate/tests/test_mdns/test_handler.py b/designate/tests/test_mdns/test_handler.py index 56725505b..4ca68e008 100644 --- a/designate/tests/test_mdns/test_handler.py +++ b/designate/tests/test_mdns/test_handler.py @@ -642,6 +642,84 @@ class MdnsRequestHandlerTest(MdnsTestCase): self.assertEqual( expected_response[1], binascii.b2a_hex(response_two)) + @mock.patch.object(dns.renderer.Renderer, 'add_multi_tsig') + def test_dispatch_opcode_query_AXFR_multiple_messages_with_tsig(self, + mock_multi_tsig): + # Query is for example.com. IN AXFR + # id 18883 + # opcode QUERY + # rcode NOERROR + # flags AD + # edns 0 + # payload 4096 + # ;QUESTION + # example.com. IN AXFR + # ;ANSWER + # ;AUTHORITY + # ;ADDITIONAL + payload = ("49c300200001000000000001076578616d706c6503636f6d0000fc0001" + "0000291000000000000000") + + expected_response = [ + (b"49c384000001000300000000076578616d706c6503636f6d0000fc0001c00c" + b"0006000100000e10002f036e7331076578616d706c65036f726700076578616" + b"d706c65c00c551c063900000e10000002580001518000000e10c00c0002000" + b"100000e100002c029046d61696cc00c0001000100000e100004c0000201"), + + (b"49c384000001000100000000076578616d706c6503636f6d0000fc0001c00c" + b"0006000100000e10002f036e7331076578616d706c65036f72670007657861" + b"6d706c65c00c551c063900000e10000002580001518000000e10"), + ] + + # Set the max-message-size to 363 + self.config(max_message_size=363, group='service:mdns') + + zone = objects.Zone.from_dict({ + 'name': 'example.com.', + 'ttl': 3600, + 'serial': 1427899961, + 'email': 'example@example.com', + }) + + def _find_recordsets_axfr(context, criterion): + if criterion['type'] == 'SOA': + return [['UUID1', 'SOA', '3600', 'example.com.', + 'ns1.example.org. example.example.com. 1427899961 ' + '3600 600 86400 3600', 'ACTION']] + + elif criterion['type'] == '!SOA': + return [ + ['UUID2', 'NS', '3600', 'example.com.', 'ns1.example.org.', + 'ACTION'], + ['UUID3', 'A', '3600', 'mail.example.com.', '192.0.2.1', + 'ACTION'], + ] + + with mock.patch.object(self.storage, 'find_zone', + return_value=zone): + with mock.patch.object(self.storage, 'find_recordsets_axfr', + side_effect=_find_recordsets_axfr): + request = dns.message.from_wire(binascii.a2b_hex(payload)) + request.environ = {'addr': self.addr, 'context': self.context} + request.keyring = {request.keyname: ''} + request.had_tsig = True + args = [request.keyname, request.keyring[request.keyname], + request.fudge, request.original_id, request.tsig_error, + request.other_data, request.mac, request.keyalgorithm] + response_generator = self.handler(request) + # Validate the first response + response_one = next(response_generator).get_wire() + mock_multi_tsig.assert_called_with(None, *args) + self.assertEqual( + expected_response[0], binascii.b2a_hex(response_one)) + + # Validate the second response + response_two = next(response_generator).get_wire() + first_msg_ctx = mock_multi_tsig.return_value + mock_multi_tsig.assert_called_with(first_msg_ctx, *args) + self.assertEqual( + expected_response[1], binascii.b2a_hex(response_two)) + def test_dispatch_opcode_query_AXFR_rrset_over_max_size(self): # Query is for example.com. IN AXFR # id 18883