Browse Source

[1458] first step for __check_update_acl: check ACL, and handle reject case.

JINMEI Tatuya 13 years ago
parent
commit
84ed053528

+ 3 - 0
src/lib/python/isc/ddns/libddns_messages.mes

@@ -39,3 +39,6 @@ possible, you may want to check the implementation or configuration of
 those clients to suppress the requests.  As specified in Section 3.1
 those clients to suppress the requests.  As specified in Section 3.1
 of RFC2136, the receiving server will return a response with an RCODE
 of RFC2136, the receiving server will return a response with an RCODE
 of NOTAUTH.
 of NOTAUTH.
+
+% LIBDDNS_UPDATE_REFUSED update client %1 for zone %2 denied
+TBD

+ 13 - 1
src/lib/python/isc/ddns/session.py

@@ -18,6 +18,7 @@ import isc.ddns.zone_config
 from isc.log import *
 from isc.log import *
 from isc.ddns.logger import logger, ClientFormatter, ZoneFormatter
 from isc.ddns.logger import logger, ClientFormatter, ZoneFormatter
 from isc.log_messages.libddns_messages import *
 from isc.log_messages.libddns_messages import *
+from isc.acl.acl import ACCEPT, REJECT, DROP
 
 
 # Result codes for UpdateSession.handle()
 # Result codes for UpdateSession.handle()
 UPDATE_SUCCESS = 0
 UPDATE_SUCCESS = 0
@@ -124,7 +125,7 @@ class UpdateSession:
             datasrc_client, zname, zclass = self.__get_update_zone()
             datasrc_client, zname, zclass = self.__get_update_zone()
             # conceptual code that would follow
             # conceptual code that would follow
             # self.__check_prerequisites()
             # self.__check_prerequisites()
-            # self.__check_update_acl()
+            self.__check_update_acl(zname, zclass)
             # self.__do_update()
             # self.__do_update()
             # self.__make_response(Rcode.NOERROR())
             # self.__make_response(Rcode.NOERROR())
             return UPDATE_SUCCESS, zname, zclass
             return UPDATE_SUCCESS, zname, zclass
@@ -176,6 +177,17 @@ class UpdateSession:
                      ZoneFormatter(zname, zclass))
                      ZoneFormatter(zname, zclass))
         raise UpdateError('notauth', zname, zclass, Rcode.NOTAUTH(), True)
         raise UpdateError('notauth', zname, zclass, Rcode.NOTAUTH(), True)
 
 
+    def __check_update_acl(self, zname, zclass):
+        '''TBD'''
+        acl = self.__zone_config.get_update_acl(zname, zclass)
+        action = acl.execute(isc.acl.dns.RequestContext(
+                (self.__client_addr[0], self.__client_addr[1])))
+        if action == REJECT:
+            logger.info(LIBDDNS_UPDATE_REFUSED,
+                        ClientFormatter(self.__client_addr),
+                        ZoneFormatter(zname, zclass))
+            raise UpdateError('rejected', zname, zclass, Rcode.REFUSED(), True)
+
     def __make_response(self, rcode):
     def __make_response(self, rcode):
         '''Transform the internal message to the update response.
         '''Transform the internal message to the update response.
 
 

+ 38 - 11
src/lib/python/isc/ddns/tests/session_tests.py

@@ -53,18 +53,27 @@ def create_update_msg(zones=[TEST_ZONE_RECORD]):
 
 
     return renderer.get_data(), msg
     return renderer.get_data(), msg
 
 
-class SessionTest(unittest.TestCase):
-    '''Session tests'''
+class SesseionTestBase(unittest.TestCase):
+    '''Base class for all sesion related tests.
+
+    It just initializes common test parameters in its setUp().
+
+    '''
     def setUp(self):
     def setUp(self):
         shutil.copyfile(READ_ZONE_DB_FILE, WRITE_ZONE_DB_FILE)
         shutil.copyfile(READ_ZONE_DB_FILE, WRITE_ZONE_DB_FILE)
-        self.__datasrc_client = DataSourceClient("sqlite3",
-                                                 WRITE_ZONE_DB_CONFIG)
-        self.__update_msgdata, self.__update_msg = create_update_msg()
-        self.__session = UpdateSession(self.__update_msg,
-                                       self.__update_msgdata, TEST_CLIENT4,
+        self._datasrc_client = DataSourceClient("sqlite3",
+                                                WRITE_ZONE_DB_CONFIG)
+        self._update_msgdata, self._update_msg = create_update_msg()
+        acl_map = {(TEST_ZONE_NAME, TEST_RRCLASS):
+                       REQUEST_LOADER.load([{"action": "ACCEPT"}])}
+        self._session = UpdateSession(self._update_msg,
+                                       self._update_msgdata, TEST_CLIENT4,
                                        ZoneConfig([], TEST_RRCLASS,
                                        ZoneConfig([], TEST_RRCLASS,
-                                                  self.__datasrc_client))
+                                                  self._datasrc_client,
+                                                  acl_map))
 
 
+class SessionTest(SesseionTestBase):
+    '''Basic session tests'''
     def check_response(self, msg, expected_rcode):
     def check_response(self, msg, expected_rcode):
         '''Perform common checks on update resposne message.'''
         '''Perform common checks on update resposne message.'''
         self.assertTrue(msg.get_header_flag(Message.HEADERFLAG_QR))
         self.assertTrue(msg.get_header_flag(Message.HEADERFLAG_QR))
@@ -79,7 +88,7 @@ class SessionTest(unittest.TestCase):
 
 
     def test_handle(self):
     def test_handle(self):
         '''Basic update case'''
         '''Basic update case'''
-        result, zname, zclass = self.__session.handle()
+        result, zname, zclass = self._session.handle()
         self.assertEqual(UPDATE_SUCCESS, result)
         self.assertEqual(UPDATE_SUCCESS, result)
         self.assertEqual(TEST_ZONE_NAME, zname)
         self.assertEqual(TEST_ZONE_NAME, zname)
         self.assertEqual(TEST_RRCLASS, zclass)
         self.assertEqual(TEST_RRCLASS, zclass)
@@ -123,7 +132,7 @@ class SessionTest(unittest.TestCase):
         session = UpdateSession(msg, msg_data, TEST_CLIENT4,
         session = UpdateSession(msg, msg_data, TEST_CLIENT4,
                                 ZoneConfig([(TEST_ZONE_NAME, TEST_RRCLASS)],
                                 ZoneConfig([(TEST_ZONE_NAME, TEST_RRCLASS)],
                                            TEST_RRCLASS,
                                            TEST_RRCLASS,
-                                           self.__datasrc_client))
+                                           self._datasrc_client))
         self.assertEqual(UPDATE_ERROR, session.handle()[0])
         self.assertEqual(UPDATE_ERROR, session.handle()[0])
         self.check_response(session.get_message(), Rcode.NOTIMP())
         self.check_response(session.get_message(), Rcode.NOTIMP())
 
 
@@ -134,7 +143,7 @@ class SessionTest(unittest.TestCase):
         session = UpdateSession(msg, msg_data, TEST_CLIENT4,
         session = UpdateSession(msg, msg_data, TEST_CLIENT4,
                                 ZoneConfig([(TEST_ZONE_NAME, TEST_RRCLASS)],
                                 ZoneConfig([(TEST_ZONE_NAME, TEST_RRCLASS)],
                                            TEST_RRCLASS,
                                            TEST_RRCLASS,
-                                           self.__datasrc_client))
+                                           self._datasrc_client))
         self.assertEqual(UPDATE_ERROR, session.handle()[0])
         self.assertEqual(UPDATE_ERROR, session.handle()[0])
         self.check_response(session.get_message(), Rcode.NOTAUTH())
         self.check_response(session.get_message(), Rcode.NOTAUTH())
 
 
@@ -148,6 +157,24 @@ class SessionTest(unittest.TestCase):
         # zone class doesn't match
         # zone class doesn't match
         self.check_notauth(Name('example.org'), RRClass.CH())
         self.check_notauth(Name('example.org'), RRClass.CH())
 
 
+class SessionACLTest(SesseionTestBase):
+    '''ACL related tests for update session.'''
+    def test_update_acl_check(self):
+        '''Test for various ACL checks.
+
+        Note that accepted cases are covered in the basic tests.
+
+        '''
+        # create a separate session, with default (empty) ACL map.
+        session = UpdateSession(self._update_msg, self._update_msgdata,
+                                TEST_CLIENT4, ZoneConfig([], TEST_RRCLASS,
+                                                         self._datasrc_client))
+        # then the request should be rejected.
+        result, zname, zclass = session.handle()
+        self.assertEqual(UPDATE_ERROR, result)
+        self.assertEqual(None, zname)
+        self.assertEqual(None, zclass)
+
 if __name__ == "__main__":
 if __name__ == "__main__":
     isc.log.init("bind10")
     isc.log.init("bind10")
     isc.log.resetUnitTestRootLogger()
     isc.log.resetUnitTestRootLogger()

+ 7 - 2
src/lib/python/isc/ddns/zone_config.py

@@ -34,7 +34,7 @@ class ZoneConfig:
     until the details are fixed.
     until the details are fixed.
 
 
     '''
     '''
-    def __init__(self, secondaries, datasrc_class, datasrc_client):
+    def __init__(self, secondaries, datasrc_class, datasrc_client, acl_map={}):
         '''Constructor.
         '''Constructor.
 
 
         Parameters:
         Parameters:
@@ -46,6 +46,11 @@ class ZoneConfig:
         - datasrc_client: isc.dns.DataSourceClient object.  A data source
         - datasrc_client: isc.dns.DataSourceClient object.  A data source
           class for the RR class of datasrc_class.  It's expected to contain
           class for the RR class of datasrc_class.  It's expected to contain
           a zone that is eventually updated in the ddns package.
           a zone that is eventually updated in the ddns package.
+        - acl_map: a dictionary that maps a tuple of
+          (isc.dns.Name, isc.dns.RRClass) to an isc.dns.dns.RequestACL
+          object.  It defines an ACL to be applied to the zone defined
+          by the tuple.  If unspecified, or the map is empty, the default
+          ACL will be applied to all zones, which is to reject any requests.
 
 
         '''
         '''
         self.__secondaries = set()
         self.__secondaries = set()
@@ -54,7 +59,7 @@ class ZoneConfig:
         self.__datasrc_class = datasrc_class
         self.__datasrc_class = datasrc_class
         self.__datasrc_client = datasrc_client
         self.__datasrc_client = datasrc_client
         self.__default_acl = REQUEST_LOADER.load([{"action": "REJECT"}])
         self.__default_acl = REQUEST_LOADER.load([{"action": "REJECT"}])
-        self.__acl_map = {}
+        self.__acl_map = acl_map
 
 
     def find_zone(self, zone_name, zone_class):
     def find_zone(self, zone_name, zone_class):
         '''Return the type and accessor client object for given zone.'''
         '''Return the type and accessor client object for given zone.'''