diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml index da1ac13b8c..ebc0ea923e 100644 --- a/doc/guide/bind10-guide.xml +++ b/doc/guide/bind10-guide.xml @@ -758,7 +758,7 @@ as a dependency earlier --> If the configure fails, it may be due to missing or old dependencies. - + For notes on configuring and building DHCPv6 with MySQL see . @@ -1841,10 +1841,8 @@ config set /Boss/components/b10-zonemgr/kind dispensable The key ring lives in the configuration in "tsig_keys/keys". Most of the system uses the keys from there — ACLs, authoritative server to - sign responses to signed queries, and b10-xfrout - to sign transfers. The b10-xfrin uses its own - configuration for keys, but that will be fixed in Trac ticket - #1351. + sign responses to signed queries, and b10-xfrin + and b10-xfrout to sign transfers. @@ -2721,6 +2719,15 @@ TODO +
+ TSIG + If you want to use TSIG for incoming transfers, a system wide TSIG + key ring must be configured (see ). + To specify a key to use, set tsig_key value to the name of the key + to use from the key ring. +> config set Xfrin/zones[0]/tsig_key "" +
+
Enabling IXFR diff --git a/src/bin/xfrin/b10-xfrin.xml b/src/bin/xfrin/b10-xfrin.xml index 231681c13b..eb16ab3468 100644 --- a/src/bin/xfrin/b10-xfrin.xml +++ b/src/bin/xfrin/b10-xfrin.xml @@ -112,20 +112,18 @@ in separate zonemgr process. master_addr (the zone master to transfer from), master_port (defaults to 53), use_ixfr (defaults to false), and - tsig_key (optional TSIG key to use). - The tsig_key is specified using a full string - colon-delimited name:key:algorithm representation (e.g. - foo.example.org:EvABsfU2h7uofnmqaRCrhHunGsd=:hmac-sha1). + tsig_key (optional TSIG key name to use). + The tsig_key is specified using a name that + corresponds to one of the TSIG keys configured in the global + TSIG key ring (/tsig_keys/keys). - (The site-wide master_addr and master_port configurations are deprecated; use the zones list configuration instead.) - diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py index 4e8d6abf2a..b7fe056294 100644 --- a/src/bin/xfrin/tests/xfrin_test.py +++ b/src/bin/xfrin/tests/xfrin_test.py @@ -26,6 +26,7 @@ from xfrin import * import xfrin from isc.xfrin.diff import Diff import isc.log +from isc.server_common.tsig_keyring import init_keyring, get_keyring # If we use any python library that is basically a wrapper for # a library we use as well (like sqlite3 in our datasources), # we must make sure we import ours first; If we have special @@ -139,6 +140,16 @@ class MockCC(MockModuleCCSession): if identifier == "zones/use_ixfr": return False + def add_remote_config_by_name(self, name, callback): + pass + + def get_remote_config_value(self, module, identifier): + if module == 'tsig_keys' and identifier == 'keys': + return (['example.com.key.:EvAAsfU2h7uofnmqaTCrhHunGsc='], True) + else: + raise Exception('MockCC requested for unknown config value ' + + + module + "/" + identifier) + def remove_remote_config(self, module_name): pass @@ -229,6 +240,7 @@ class MockXfrin(Xfrin): def _cc_setup(self): self._tsig_key = None self._module_cc = MockCC() + init_keyring(self._module_cc) pass def _get_db_file(self): @@ -2427,9 +2439,10 @@ class TestXfrin(unittest.TestCase): self.assertEqual(str(zone_info.master_addr), zone_config['master_addr']) self.assertEqual(zone_info.master_port, zone_config['master_port']) if 'tsig_key' in zone_config: - self.assertEqual(zone_info.tsig_key.to_text(), TSIGKey(zone_config['tsig_key']).to_text()) + self.assertEqual(zone_info.tsig_key_name.to_text(), + Name(zone_config['tsig_key']).to_text()) else: - self.assertIsNone(zone_info.tsig_key) + self.assertIsNone(zone_info.tsig_key_name) if 'use_ixfr' in zone_config and\ zone_config.get('use_ixfr'): self.assertTrue(zone_info.use_ixfr) @@ -2562,7 +2575,7 @@ class TestXfrin(unittest.TestCase): { 'name': 'test2.example.', 'master_addr': '192.0.2.9', 'master_port': 53, - 'tsig_key': 'badkey' + 'tsig_key': 'badkey..' } ]} self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1) @@ -2581,13 +2594,14 @@ class TestXfrin(unittest.TestCase): self.assertEqual(self.xfr.config_handler(config)['result'][0], 0) self._check_zones_config(config) - def common_ixfr_setup(self, xfr_mode, use_ixfr): + def common_ixfr_setup(self, xfr_mode, use_ixfr, tsig_key_str = None): # This helper method explicitly sets up a zone configuration with # use_ixfr, and invokes either retransfer or refresh. # Shared by some of the following test cases. config = {'zones': [ {'name': 'example.com.', 'master_addr': '192.0.2.1', + 'tsig_key': tsig_key_str, 'use_ixfr': use_ixfr}]} self.assertEqual(self.xfr.config_handler(config)['result'][0], 0) self.assertEqual(self.xfr.command_handler(xfr_mode, @@ -2603,6 +2617,34 @@ class TestXfrin(unittest.TestCase): self.common_ixfr_setup('refresh', True) self.assertEqual(RRType.IXFR(), self.xfr.xfrin_started_request_type) + def test_command_handler_retransfer_with_tsig(self): + self.common_ixfr_setup('retransfer', False, 'example.com.key') + self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type) + + def test_command_handler_retransfer_with_tsig_bad_key(self): + # bad keys should not reach xfrin, but should they somehow, + # they are ignored (and result in 'key not found' + error log). + self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup, + 'retransfer', False, 'bad.key') + + def test_command_handler_retransfer_with_tsig_unknown_key(self): + self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup, + 'retransfer', False, 'no.such.key') + + def test_command_handler_refresh_with_tsig(self): + self.common_ixfr_setup('refresh', False, 'example.com.key') + self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type) + + def test_command_handler_refresh_with_tsig_bad_key(self): + # bad keys should not reach xfrin, but should they somehow, + # they are ignored (and result in 'key not found' + error log). + self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup, + 'refresh', False, 'bad.key') + + def test_command_handler_refresh_with_tsig_unknown_key(self): + self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup, + 'refresh', False, 'no.such.key') + def test_command_handler_retransfer_ixfr_disabled(self): # Similar to the previous case, but explicitly disabled. AXFR should # be used. diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in index caae9d7efe..da0f20702e 100755 --- a/src/bin/xfrin/xfrin.py.in +++ b/src/bin/xfrin/xfrin.py.in @@ -34,6 +34,7 @@ from isc.datasrc import DataSourceClient, ZoneFinder import isc.net.parse from isc.xfrin.diff import Diff from isc.server_common.auth_command import auth_loadzone_command +from isc.server_common.tsig_keyring import init_keyring, get_keyring from isc.log_messages.xfrin_messages import * isc.log.init("b10-xfrin", buffer=True) @@ -69,7 +70,10 @@ AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + "/auth.spec" AUTH_MODULE_NAME = 'Auth' XFROUT_MODULE_NAME = 'Xfrout' + +# Remote module and identifiers (according to their spec files) ZONE_MANAGER_MODULE_NAME = 'Zonemgr' + REFRESH_FROM_ZONEMGR = 'refresh_from_zonemgr' # Constants for debug levels. @@ -1179,7 +1183,7 @@ class ZoneInfo: self.set_master_port(config_data.get('master_port')) self.set_zone_class(config_data.get('class')) - self.set_tsig_key(config_data.get('tsig_key')) + self.set_tsig_key_name(config_data.get('tsig_key')) self.set_use_ixfr(config_data.get('use_ixfr')) def set_name(self, name_str): @@ -1240,20 +1244,32 @@ class ZoneInfo: errmsg = "invalid zone class: " + zone_class_str raise XfrinZoneInfoException(errmsg) - def set_tsig_key(self, tsig_key_str): - """Set the tsig_key for this zone, given a TSIG key string - representation. If tsig_key_str is None, no TSIG key will - be set. Raises XfrinZoneInfoException if tsig_key_str cannot - be parsed.""" + def set_tsig_key_name(self, tsig_key_str): + """Set the name of the tsig_key for this zone. If tsig_key_str + is None, no TSIG key will be used. This name is used to + find the TSIG key to use for transfers in the global TSIG + key ring. + Raises XfrinZoneInfoException if tsig_key_str is not a valid + (dns) name.""" if tsig_key_str is None: - self.tsig_key = None + self.tsig_key_name = None else: + # can throw a number of exceptions but it is just one + # call, so Exception should be OK here try: - self.tsig_key = TSIGKey(tsig_key_str) - except InvalidParameter as ipe: - logger.error(XFRIN_BAD_TSIG_KEY_STRING, tsig_key_str) - errmsg = "bad TSIG key string: " + tsig_key_str - raise XfrinZoneInfoException(errmsg) + self.tsig_key_name = Name(tsig_key_str) + except Exception as exc: + raise XfrinZoneInfoException("Bad TSIG key name: " + str(exc)) + + def get_tsig_key(self): + if self.tsig_key_name is None: + return None + result, key = get_keyring().find(self.tsig_key_name) + if result != isc.dns.TSIGKeyRing.SUCCESS: + raise XfrinZoneInfoException("TSIG key not found in keyring: " + + self.tsig_key_name.to_text()) + else: + return key def set_use_ixfr(self, use_ixfr): """Set use_ixfr. If set to True, it will use @@ -1308,6 +1324,7 @@ class Xfrin: self.config_handler(config_data) self._module_cc.add_remote_config(AUTH_SPECFILE_LOCATION, self._auth_config_handler) + init_keyring(self._module_cc) def _cc_check_command(self): '''This is a straightforward wrapper for cc.check_command, @@ -1468,7 +1485,7 @@ class Xfrin: rrclass, self._get_db_file(), master_addr, - zone_info.tsig_key, request_type, + zone_info.get_tsig_key(), request_type, True) answer = create_answer(ret[0], ret[1]) else: @@ -1491,7 +1508,7 @@ class Xfrin: tsig_key = None request_type = RRType.AXFR() if zone_info: - tsig_key = zone_info.tsig_key + tsig_key = zone_info.get_tsig_key() if zone_info.use_ixfr: request_type = RRType.IXFR() db_file = args.get('db_file') or self._get_db_file() diff --git a/src/bin/xfrin/xfrin_messages.mes b/src/bin/xfrin/xfrin_messages.mes index 554a1952fc..837cafa9bf 100644 --- a/src/bin/xfrin/xfrin_messages.mes +++ b/src/bin/xfrin/xfrin_messages.mes @@ -43,7 +43,7 @@ The master port as read from the configuration is not a valid port number. % XFRIN_BAD_TSIG_KEY_STRING bad TSIG key string: %1 The TSIG key string as read from the configuration does not represent -a valid TSIG key. +a valid TSIG key. The key is ignored. % XFRIN_BAD_ZONE_CLASS Invalid zone class: %1 The zone class as read from the configuration is not a valid DNS class. @@ -160,6 +160,13 @@ run time: Time (in seconds) the complete axfr took bytes/second: Transfer speed +% XFRIN_TSIG_KEY_NOT_FOUND TSIG key not found in key ring: %1 +An attempt to start a transfer with TSIG was made, but the configured TSIG +key name was not found in the TSIG key ring (configuration option +tsig_keys/keys). The transfer is aborted. The key name that could not be +found is shown in the log message. Check the configuration and the +TSIG key ring. + % XFRIN_UNKNOWN_ERROR unknown error: %1 An uncaught exception was raised while running the xfrin daemon. The exception message is printed in the log message. diff --git a/src/lib/dns/python/tests/tsigkey_python_test.py b/src/lib/dns/python/tests/tsigkey_python_test.py index 516bea4e98..ca7a61ec62 100644 --- a/src/lib/dns/python/tests/tsigkey_python_test.py +++ b/src/lib/dns/python/tests/tsigkey_python_test.py @@ -170,6 +170,12 @@ class TSIGKeyRingTest(unittest.TestCase): self.assertEqual(TSIGKey.HMACSHA256_NAME, key.get_algorithm_name()) self.assertEqual(self.secret, key.get_secret()) + (code, key) = self.keyring.find(self.key_name) + self.assertEqual(TSIGKeyRing.SUCCESS, code) + self.assertEqual(self.key_name, key.get_key_name()) + self.assertEqual(TSIGKey.HMACSHA256_NAME, key.get_algorithm_name()) + self.assertEqual(self.secret, key.get_secret()) + (code, key) = self.keyring.find(Name('different-key.example'), self.sha256_name) self.assertEqual(TSIGKeyRing.NOTFOUND, code) diff --git a/src/lib/dns/python/tsigkey_python.cc b/src/lib/dns/python/tsigkey_python.cc index cf79c1aa82..bcab59ce86 100644 --- a/src/lib/dns/python/tsigkey_python.cc +++ b/src/lib/dns/python/tsigkey_python.cc @@ -287,7 +287,9 @@ PyMethodDef TSIGKeyRing_methods[] = { METH_VARARGS, "Remove a TSIGKey for the given name from the TSIGKeyRing." }, { "find", reinterpret_cast(TSIGKeyRing_find), METH_VARARGS, - "Find a TSIGKey for the given name in the TSIGKeyRing. " + "Find a TSIGKey for the given name in the TSIGKeyRing. Optional " + "second argument is an algorithm, in which case it only returns " + "a key if both match.\n" "It returns a tuple of (result_code, key)." }, { NULL, NULL, 0, NULL } }; @@ -362,13 +364,16 @@ TSIGKeyRing_remove(const s_TSIGKeyRing* self, PyObject* args) { PyObject* TSIGKeyRing_find(const s_TSIGKeyRing* self, PyObject* args) { PyObject* key_name; - PyObject* algorithm_name; + PyObject* algorithm_name = NULL; - if (PyArg_ParseTuple(args, "O!O!", &name_type, &key_name, + if (PyArg_ParseTuple(args, "O!|O!", &name_type, &key_name, &name_type, &algorithm_name)) { - const TSIGKeyRing::FindResult result = - self->cppobj->find(PyName_ToName(key_name), - PyName_ToName(algorithm_name)); + // Can't init TSIGKeyRing::FindResult without actual result, + // so use ternary operator + TSIGKeyRing::FindResult result = (algorithm_name == NULL) ? + self->cppobj->find(PyName_ToName(key_name)) : + self->cppobj->find(PyName_ToName(key_name), + PyName_ToName(algorithm_name)); if (result.key != NULL) { s_TSIGKey* key = PyObject_New(s_TSIGKey, &tsigkey_type); if (key == NULL) { diff --git a/src/lib/dns/tests/tsigkey_unittest.cc b/src/lib/dns/tests/tsigkey_unittest.cc index 20ee802757..f58da1cc17 100644 --- a/src/lib/dns/tests/tsigkey_unittest.cc +++ b/src/lib/dns/tests/tsigkey_unittest.cc @@ -251,6 +251,15 @@ TEST_F(TSIGKeyRingTest, find) { const TSIGKeyRing::FindResult result3 = keyring.find(key_name, md5_name); EXPECT_EQ(TSIGKeyRing::NOTFOUND, result3.code); EXPECT_EQ(static_cast(NULL), result3.key); + + // But with just the name it should work + const TSIGKeyRing::FindResult result4(keyring.find(key_name)); + EXPECT_EQ(TSIGKeyRing::SUCCESS, result1.code); + EXPECT_EQ(key_name, result1.key->getKeyName()); + EXPECT_EQ(TSIGKey::HMACSHA256_NAME(), result1.key->getAlgorithmName()); + EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, secret, secret_len, + result1.key->getSecret(), + result1.key->getSecretLength()); } TEST_F(TSIGKeyRingTest, findFromSome) { diff --git a/src/lib/dns/tsigkey.cc b/src/lib/dns/tsigkey.cc index d7d60eb20d..e55cce3524 100644 --- a/src/lib/dns/tsigkey.cc +++ b/src/lib/dns/tsigkey.cc @@ -51,7 +51,7 @@ namespace { if (name == TSIGKey::HMACSHA512_NAME()) { return (isc::cryptolink::SHA512); } - + return (isc::cryptolink::UNKNOWN_HASH); } } @@ -269,6 +269,16 @@ TSIGKeyRing::remove(const Name& key_name) { return (impl_->keys.erase(key_name) == 1 ? SUCCESS : NOTFOUND); } +TSIGKeyRing::FindResult +TSIGKeyRing::find(const Name& key_name) const { + TSIGKeyRingImpl::TSIGKeyMap::const_iterator found = + impl_->keys.find(key_name); + if (found == impl_->keys.end()) { + return (FindResult(NOTFOUND, NULL)); + } + return (FindResult(SUCCESS, &((*found).second))); +} + TSIGKeyRing::FindResult TSIGKeyRing::find(const Name& key_name, const Name& algorithm_name) const { TSIGKeyRingImpl::TSIGKeyMap::const_iterator found = diff --git a/src/lib/dns/tsigkey.h b/src/lib/dns/tsigkey.h index 1bbd3fe3bf..b10660c0ec 100644 --- a/src/lib/dns/tsigkey.h +++ b/src/lib/dns/tsigkey.h @@ -324,6 +324,27 @@ public: /// \c key_name. Result remove(const Name& key_name); + /// Find a \c TSIGKey for the given name in the \c TSIGKeyRing. + /// + /// It searches the internal storage for a \c TSIGKey whose name is + /// \c key_name. + /// It returns the result in the form of a \c FindResult + /// object as follows: + /// - \c code: \c SUCCESS if a key is found; otherwise \c NOTFOUND. + /// - \c key: A pointer to the found \c TSIGKey object if one is found; + /// otherwise \c NULL. + /// + /// The pointer returned in the \c FindResult object is only valid until + /// the corresponding key is removed from the key ring. + /// The caller must ensure that the key is held in the key ring while + /// it needs to refer to it, or it must make a local copy of the key. + /// + /// This method never throws an exception. + /// + /// \param key_name The name of the key to be found. + /// \return A \c FindResult object enclosing the search result (see above). + FindResult find(const Name& key_name) const; + /// Find a \c TSIGKey for the given name in the \c TSIGKeyRing. /// /// It searches the internal storage for a \c TSIGKey whose name is @@ -346,6 +367,7 @@ public: /// \param algorithm_name The name of the algorithm of the found key. /// \return A \c FindResult object enclosing the search result (see above). FindResult find(const Name& key_name, const Name& algorithm_name) const; + private: struct TSIGKeyRingImpl; TSIGKeyRingImpl* impl_; diff --git a/tests/lettuce/features/xfrin_bind10.feature b/tests/lettuce/features/xfrin_bind10.feature index 3c7fa1c186..34674ca4f3 100644 --- a/tests/lettuce/features/xfrin_bind10.feature +++ b/tests/lettuce/features/xfrin_bind10.feature @@ -79,7 +79,7 @@ Feature: Xfrin When I send bind10 the following commands: """ config add tsig_keys/keys "example.key.:c2VjcmV0" - config set Xfrin/zones[0]/tsig_key "example.key.:c2VjcmV0" + config set Xfrin/zones[0]/tsig_key "example.key." config commit """