From: JINMEI Tatuya Date: Wed, 30 May 2012 07:28:36 +0000 (-0700) Subject: [2013] supported zone configuration X-Git-Tag: trac2351_base~226^2~60^2^2~2^2~14 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=c6544e26eec66ec1c07e52c040a2d4638c941bd6;p=thirdparty%2Fkea.git [2013] supported zone configuration the spec file is updated so that the zone config is defined as a list, not a named set. named set isn't suitable because a zone is parametrized with a tuple of name and class. --- diff --git a/src/bin/ddns/ddns.py.in b/src/bin/ddns/ddns.py.in index 5d3343081d..e9fa017c4d 100755 --- a/src/bin/ddns/ddns.py.in +++ b/src/bin/ddns/ddns.py.in @@ -18,6 +18,7 @@ import sys; sys.path.append ('@@PYTHONPATH@@') import isc +from isc.acl.dns import REQUEST_LOADER import bind10_config from isc.dns import * import isc.ddns.session @@ -162,9 +163,14 @@ class DDNSServer: self.config_handler, self.command_handler) + # Initialize configuration with defaults. Right now 'zones' is the + # only configuration, so we simply directly set it here. self._config_data = self._cc.get_full_config() + self._zone_config = self.__update_zone_config( + self._cc.get_default_value('zones')) self._cc.start() + # Get necessary configurations from remote modules. self._cc.add_remote_config(AUTH_SPECFILE_LOCATION) isc.server_common.tsig_keyring.init_keyring(self._cc) @@ -198,10 +204,29 @@ class DDNSServer: def config_handler(self, new_config): '''Update config data.''' - # TODO: Handle exceptions and turn them to an error response - # (once we have any configuration) - answer = create_answer(0) - return answer + try: + if 'zones' in new_config: + self._zone_config = \ + self.__update_zone_config(new_config['zones']) + return create_answer(0) + except Exception as ex: + # We catch any exception here. That includes any syntax error + # against the configuration spec. The config interface is too + # complicated and it's not clear how much validation is performed + # there, so, while assuming it's unlikely to happen, we act + # proactively. + return create_answer(1, "Failed to handle new configuration: " + + str(ex)) + + def __update_zone_config(self, new_zones_config): + '''Handle zones configuration update.''' + new_zones = {} + for zone_config in new_zones_config: + origin = Name(zone_config['origin']) + rrclass = RRClass(zone_config['class']) + update_acl = zone_config['update_acl'] + new_zones[(origin, rrclass)] = REQUEST_LOADER.load(update_acl) + return new_zones def command_handler(self, cmd, args): ''' @@ -318,7 +343,8 @@ class DDNSServer: # ZoneConfig will soon be substantially revised. For now we don't # bother to generalize it. datasrc_class, datasrc_client = get_datasrc_client(self._cc) - zone_cfg = ZoneConfig([], datasrc_class, datasrc_client, {}) + zone_cfg = ZoneConfig([], datasrc_class, datasrc_client, + self._zone_config) update_session = self._UpdateSessionClass(self.__request_msg, remote_addr, zone_cfg) result, zname, zclass = update_session.handle() diff --git a/src/bin/ddns/ddns.spec b/src/bin/ddns/ddns.spec index 55dab5c1cf..70611e690f 100644 --- a/src/bin/ddns/ddns.spec +++ b/src/bin/ddns/ddns.spec @@ -4,22 +4,36 @@ "config_data": [ { "item_name": "zones", - "item_type": "named_set", + "item_type": "list", "item_optional": false, - "item_default": {}, - "named_set_item_spec": { + "item_default": [], + "list_item_spec": { "item_name": "entry", "item_type": "map", "item_optional": true, "item_default": { - "update_acl": [{"action": "ACCEPT", "from": "127.0.0.1"}, - {"action": "ACCEPT", "from": "::1"}] + "origin": "", + "class": "IN", + "update_acl": [] }, "map_item_spec": [ + { + "item_name": "origin", + "item_type": "string", + "item_optional": false, + "item_default": "" + }, + { + "item_name": "class", + "item_type": "string", + "item_optional": false, + "item_default": "IN" + }, { "item_name": "update_acl", "item_type": "list", "item_optional": false, + "item_default": [], "list_item_spec": { "item_name": "acl_element", "item_type": "any", diff --git a/src/bin/ddns/tests/ddns_test.py b/src/bin/ddns/tests/ddns_test.py index 3678ee98ff..afcd9a5e04 100755 --- a/src/bin/ddns/tests/ddns_test.py +++ b/src/bin/ddns/tests/ddns_test.py @@ -17,6 +17,7 @@ from isc.ddns.session import * from isc.dns import * +from isc.acl.acl import ACCEPT import isc.util.cio.socketsession from isc.datasrc import DataSourceClient import ddns @@ -31,14 +32,19 @@ import unittest TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep READ_ZONE_DB_FILE = TESTDATA_PATH + "rwtest.sqlite3" # original, to be copied TEST_ZONE_NAME = Name('example.org') +TEST_ZONE_NAME_STR = TEST_ZONE_NAME.to_text() UPDATE_RRTYPE = RRType.SOA() TEST_QID = 5353 # arbitrary chosen TEST_RRCLASS = RRClass.IN() +TEST_RRCLASS_STR = TEST_RRCLASS.to_text() TEST_SERVER6 = ('2001:db8::53', 53, 0, 0) TEST_CLIENT6 = ('2001:db8::1', 53000, 0, 0) TEST_SERVER4 = ('192.0.2.53', 53) TEST_CLIENT4 = ('192.0.2.1', 53534) TEST_ZONE_RECORD = Question(TEST_ZONE_NAME, TEST_RRCLASS, UPDATE_RRTYPE) +TEST_ACL_CONTEXT = isc.acl.dns.RequestContext( + socket.getaddrinfo("192.0.2.1", 1234, 0, socket.SOCK_DGRAM, + socket.IPPROTO_UDP, socket.AI_NUMERICHOST)[0][4]) # TSIG key for tests when needed. The key name is TEST_ZONE_NAME. TEST_TSIG_KEY = TSIGKey("example.org:SFuWd/q99SzF8Yzd1QbB9g==") # TSIG keyring that contanins the test key @@ -235,12 +241,92 @@ class TestDDNSServer(unittest.TestCase): ddns.clear_socket() self.assertFalse(os.path.exists(ddns.SOCKET_FILE)) + def test_initial_config(self): + # right now, the only configuration is the zone configuration, whose + # default should be an empty map. + self.assertEqual({}, self.ddns_server._zone_config) + def test_config_handler(self): - # Config handler does not do anything yet, but should at least - # return 'ok' for now. - new_config = {} + # Update with a simple zone configuration: including an accept-all ACL + new_config = { 'zones': [ { 'origin': TEST_ZONE_NAME_STR, + 'class': TEST_RRCLASS_STR, + 'update_acl': [{'action': 'ACCEPT'}] } ] } + answer = self.ddns_server.config_handler(new_config) + self.assertEqual((0, None), isc.config.parse_answer(answer)) + acl = self.ddns_server._zone_config[(TEST_ZONE_NAME, TEST_RRCLASS)] + self.assertEqual(ACCEPT, acl.execute(TEST_ACL_CONTEXT)) + + # Slightly more complicated one: containing multiple ACLs + new_config = { 'zones': [ { 'origin': 'example.com', + 'class': 'CH', + 'update_acl': [{'action': 'REJECT', + 'from': '2001:db8::1'}] }, + { 'origin': TEST_ZONE_NAME_STR, + 'class': TEST_RRCLASS_STR, + 'update_acl': [{'action': 'ACCEPT'}] }, + { 'origin': 'example.org', + 'class': 'CH', + 'update_acl': [{'action': 'DROP'}] } ] } answer = self.ddns_server.config_handler(new_config) self.assertEqual((0, None), isc.config.parse_answer(answer)) + self.assertEqual(3, len(self.ddns_server._zone_config)) + acl = self.ddns_server._zone_config[(TEST_ZONE_NAME, TEST_RRCLASS)] + self.assertEqual(ACCEPT, acl.execute(TEST_ACL_CONTEXT)) + + # empty zone config + new_config = { 'zones': [] } + answer = self.ddns_server.config_handler(new_config) + self.assertEqual((0, None), isc.config.parse_answer(answer)) + self.assertEqual({}, self.ddns_server._zone_config) + + # bad zone config data: bad name. The previous config shouls be kept. + bad_config = { 'zones': [ { 'origin': 'bad..example', + 'class': TEST_RRCLASS_STR, + 'update_acl': [{'action': 'ACCEPT'}] } ] } + answer = self.ddns_server.config_handler(bad_config) + self.assertEqual(1, isc.config.parse_answer(answer)[0]) + self.assertEqual({}, self.ddns_server._zone_config) + + # bad zone config data: bad class. + bad_config = { 'zones': [ { 'origin': TEST_ZONE_NAME_STR, + 'class': 'badclass', + 'update_acl': [{'action': 'ACCEPT'}] } ] } + answer = self.ddns_server.config_handler(bad_config) + self.assertEqual(1, isc.config.parse_answer(answer)[0]) + self.assertEqual({}, self.ddns_server._zone_config) + + # bad zone config data: bad ACL. + bad_config = { 'zones': [ { 'origin': TEST_ZONE_NAME_STR, + 'class': TEST_RRCLASS_STR, + 'update_acl': [{'action': 'badaction'}]}]} + answer = self.ddns_server.config_handler(bad_config) + self.assertEqual(1, isc.config.parse_answer(answer)[0]) + self.assertEqual({}, self.ddns_server._zone_config) + + # the first zone cofig is valid, but not the second. the first one + # shouldn't be installed. + bad_config = { 'zones': [ { 'origin': TEST_ZONE_NAME_STR, + 'class': TEST_RRCLASS_STR, + 'update_acl': [{'action': 'ACCEPT'}] }, + { 'origin': 'bad..example', + 'class': TEST_RRCLASS_STR, + 'update_acl': [{'action': 'ACCEPT'}] } ] } + answer = self.ddns_server.config_handler(bad_config) + self.assertEqual(1, isc.config.parse_answer(answer)[0]) + self.assertEqual({}, self.ddns_server._zone_config) + + # Half-broken case: 'origin, class' pair is duplicate. For now we + # we accept it (the latter one will win) + dup_config = { 'zones': [ { 'origin': TEST_ZONE_NAME_STR, + 'class': TEST_RRCLASS_STR, + 'update_acl': [{'action': 'REJECT'}] }, + { 'origin': TEST_ZONE_NAME_STR, + 'class': TEST_RRCLASS_STR, + 'update_acl': [{'action': 'ACCEPT'}] } ] } + answer = self.ddns_server.config_handler(dup_config) + self.assertEqual((0, None), isc.config.parse_answer(answer)) + acl = self.ddns_server._zone_config[(TEST_ZONE_NAME, TEST_RRCLASS)] + self.assertEqual(ACCEPT, acl.execute(TEST_ACL_CONTEXT)) def test_shutdown_command(self): '''Test whether the shutdown command works''' @@ -628,6 +714,7 @@ class TestMain(unittest.TestCase): self.assertTrue(self._server.exception_raised) class TestConfig(unittest.TestCase): + '''Test some simple config related things that don't need server. ''' def setUp(self): self.__ccsession = MyCCSession()