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
 of RFC2136, the receiving server will return a response with an RCODE
 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.ddns.logger import logger, ClientFormatter, ZoneFormatter
 from isc.log_messages.libddns_messages import *
+from isc.acl.acl import ACCEPT, REJECT, DROP
 
 # Result codes for UpdateSession.handle()
 UPDATE_SUCCESS = 0
@@ -124,7 +125,7 @@ class UpdateSession:
             datasrc_client, zname, zclass = self.__get_update_zone()
             # conceptual code that would follow
             # self.__check_prerequisites()
-            # self.__check_update_acl()
+            self.__check_update_acl(zname, zclass)
             # self.__do_update()
             # self.__make_response(Rcode.NOERROR())
             return UPDATE_SUCCESS, zname, zclass
@@ -176,6 +177,17 @@ class UpdateSession:
                      ZoneFormatter(zname, zclass))
         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):
         '''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
 
-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):
         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,
-                                                  self.__datasrc_client))
+                                                  self._datasrc_client,
+                                                  acl_map))
 
+class SessionTest(SesseionTestBase):
+    '''Basic session tests'''
     def check_response(self, msg, expected_rcode):
         '''Perform common checks on update resposne message.'''
         self.assertTrue(msg.get_header_flag(Message.HEADERFLAG_QR))
@@ -79,7 +88,7 @@ class SessionTest(unittest.TestCase):
 
     def test_handle(self):
         '''Basic update case'''
-        result, zname, zclass = self.__session.handle()
+        result, zname, zclass = self._session.handle()
         self.assertEqual(UPDATE_SUCCESS, result)
         self.assertEqual(TEST_ZONE_NAME, zname)
         self.assertEqual(TEST_RRCLASS, zclass)
@@ -123,7 +132,7 @@ class SessionTest(unittest.TestCase):
         session = UpdateSession(msg, msg_data, TEST_CLIENT4,
                                 ZoneConfig([(TEST_ZONE_NAME, TEST_RRCLASS)],
                                            TEST_RRCLASS,
-                                           self.__datasrc_client))
+                                           self._datasrc_client))
         self.assertEqual(UPDATE_ERROR, session.handle()[0])
         self.check_response(session.get_message(), Rcode.NOTIMP())
 
@@ -134,7 +143,7 @@ class SessionTest(unittest.TestCase):
         session = UpdateSession(msg, msg_data, TEST_CLIENT4,
                                 ZoneConfig([(TEST_ZONE_NAME, TEST_RRCLASS)],
                                            TEST_RRCLASS,
-                                           self.__datasrc_client))
+                                           self._datasrc_client))
         self.assertEqual(UPDATE_ERROR, session.handle()[0])
         self.check_response(session.get_message(), Rcode.NOTAUTH())
 
@@ -148,6 +157,24 @@ class SessionTest(unittest.TestCase):
         # zone class doesn't match
         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__":
     isc.log.init("bind10")
     isc.log.resetUnitTestRootLogger()

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

@@ -34,7 +34,7 @@ class ZoneConfig:
     until the details are fixed.
 
     '''
-    def __init__(self, secondaries, datasrc_class, datasrc_client):
+    def __init__(self, secondaries, datasrc_class, datasrc_client, acl_map={}):
         '''Constructor.
 
         Parameters:
@@ -46,6 +46,11 @@ class ZoneConfig:
         - datasrc_client: isc.dns.DataSourceClient object.  A data source
           class for the RR class of datasrc_class.  It's expected to contain
           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()
@@ -54,7 +59,7 @@ class ZoneConfig:
         self.__datasrc_class = datasrc_class
         self.__datasrc_client = datasrc_client
         self.__default_acl = REQUEST_LOADER.load([{"action": "REJECT"}])
-        self.__acl_map = {}
+        self.__acl_map = acl_map
 
     def find_zone(self, zone_name, zone_class):
         '''Return the type and accessor client object for given zone.'''