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
"""