Browse Source

Merge branch 'work/authtsig'

Michal 'vorner' Vaner 14 years ago
parent
commit
fd08a0dc40

+ 1 - 0
configure.ac

@@ -686,6 +686,7 @@ AC_CONFIG_FILES([Makefile
                  src/bin/bindctl/tests/Makefile
                  src/bin/cfgmgr/Makefile
                  src/bin/cfgmgr/plugins/Makefile
+                 src/bin/cfgmgr/plugins/tests/Makefile
                  src/bin/cfgmgr/tests/Makefile
                  src/bin/host/Makefile
                  src/bin/loadzone/Makefile

+ 5 - 18
src/bin/cfgmgr/b10-cfgmgr.py.in

@@ -18,6 +18,7 @@
 import sys; sys.path.append ('@@PYTHONPATH@@')
 
 from isc.config.cfgmgr import ConfigManager, ConfigManagerDataReadError
+import bind10_config
 from isc.cc import SessionError
 import isc.util.process
 import signal
@@ -28,24 +29,10 @@ import os.path
 
 isc.util.process.rename()
 
-# If B10_FROM_SOURCE is set in the environment, we use data files
-# from a directory relative to the value of that variable, or, if defined,
-# relative to the value of B10_FROM_SOURCE_LOCALSTATEDIR.  Otherwise
-# we use the ones installed on the system.
-# B10_FROM_SOURCE_LOCALSTATEDIR is specifically intended to be used for
-# tests where we want to use variuos types of configuration within the test
-# environment.  (We may want to make it even more generic so that the path is
-# passed from the boss process)
-if "B10_FROM_SOURCE" in os.environ:
-    if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
-        DATA_PATH = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"]
-    else:
-        DATA_PATH = os.environ["B10_FROM_SOURCE"]
-    PLUGIN_PATHS = [DATA_PATH + '/src/bin/cfgmgr/plugins']
-else:
-    PREFIX = "@prefix@"
-    DATA_PATH = "@localstatedir@/@PACKAGE@".replace("${prefix}", PREFIX)
-    PLUGIN_PATHS = ["@prefix@/share/@PACKAGE@/config_plugins"]
+# Import some paths from our configuration
+DATA_PATH = bind10_config.DATA_PATH
+PLUGIN_PATHS = bind10_config.PLUGIN_PATHS
+PREFIX = bind10_config.PREFIX
 DEFAULT_CONFIG_FILE = "b10-config.db"
 
 cm = None

+ 5 - 1
src/bin/cfgmgr/plugins/Makefile.am

@@ -1 +1,5 @@
-EXTRA_DIST = README
+SUBDIRS = tests
+EXTRA_DIST = README tsig_keys.py tsig_keys.spec
+
+config_plugindir = @prefix@/share/@PACKAGE@/config_plugins
+config_plugin_DATA = tsig_keys.py tsig_keys.spec

+ 19 - 0
src/bin/cfgmgr/plugins/tests/Makefile.am

@@ -0,0 +1,19 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+PYTESTS = tsig_keys_test.py
+
+EXTRA_DIST = $(PYTESTS)
+
+# test using command-line arguments, so use check-local target instead of TESTS
+check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
+	for pytest in $(PYTESTS) ; do \
+	echo Running test: $$pytest ; \
+	env B10_TEST_PLUGIN_DIR=$(abs_srcdir)/..:$(abs_builddir)/.. \
+	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/cfgmgr:$(abs_top_builddir)/src/lib/dns/python/.libs \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+	done
+

+ 103 - 0
src/bin/cfgmgr/plugins/tests/tsig_keys_test.py

@@ -0,0 +1,103 @@
+# Copyright (C) 2011  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# Make sure we can load the module, put it into path
+import sys
+import os
+sys.path.extend(os.environ["B10_TEST_PLUGIN_DIR"].split(':'))
+
+import tsig_keys
+import unittest
+import isc.config.module_spec
+
+class TSigKeysTest(unittest.TestCase):
+    def test_load(self):
+        """
+        Checks the entry point returns the correct values.
+        """
+        (spec, check) = tsig_keys.load()
+        # It returns the checking function
+        self.assertEqual(check, tsig_keys.check)
+        # The plugin stores it's spec
+        self.assertEqual(spec, tsig_keys.spec)
+
+    def test_spec(self):
+        """
+        Checks the spec is looking sane (doesn't do really deep check here).
+        """
+        spec = tsig_keys.spec
+        # In python, we don't generally check the type of something, because
+        # of the duck typing.
+        # But this is unittest, so we check it does what we intend and
+        # supplying that's behaving the same but is different is not our
+        # intention
+        self.assertTrue(isinstance(spec, isc.config.module_spec.ModuleSpec))
+        # Correct name
+        self.assertEqual("tsig_keys", spec.get_module_name())
+        # There are no commands, nobody would handle them anyway
+        self.assertEqual([], spec.get_commands_spec())
+        # There's some nonempty configuration
+        self.assertNotEqual({}, spec.get_config_spec())
+
+    def test_missing_keys(self):
+        """
+        Test that missing keys doesn't kill us. There are just no keys there.
+        """
+        self.assertEqual(None, tsig_keys.check({}))
+
+    def test_data_empty(self):
+        """Check we accept valid config with empty set of tsig keys."""
+        self.assertEqual(None, tsig_keys.check({'keys': []}))
+
+    def test_keys_valid(self):
+        """
+        Check we accept some valid keys (we don't check all the algorithms,
+        that's the job of isc.dns.TSIGKey).
+        """
+        self.assertEqual(None, tsig_keys.check({'keys':
+            ['testkey:QklORCAxMCBpcyBjb29sCg==',
+             'test.key:QklORCAxMCBpcyBjb29sCg==:hmac-sha1']}))
+
+    def test_keys_same_name(self):
+        """
+        Test we reject when we have multiple keys with the same name.
+        """
+        self.assertEqual("Multiple TSIG keys with name 'test.key.'",
+                         tsig_keys.check({'keys':
+                                         ['test.key:QklORCAxMCBpcyBjb29sCg==',
+                                          'test.key:b3RoZXIK']}))
+
+    def test_invalid_key(self):
+        """
+        Test we reject invalid key.
+        """
+        self.assertEqual("TSIG: Invalid TSIG key string: invalid.key",
+                         tsig_keys.check({'keys': ['invalid.key']}))
+        self.assertEqual(
+            "TSIG: attempt to decode a value not in base64 char set",
+            tsig_keys.check({'keys': ['invalid.key:123']}))
+
+    def test_bad_format(self):
+        """
+        Test we fail on bad format. We don't really care much how here, though,
+        as this should not get in trough config manager anyway.
+        """
+        self.assertNotEqual(None, tsig_keys.check({'bad_name': {}}))
+        self.assertNotEqual(None, tsig_keys.check({'keys': 'not_list'}))
+        self.assertNotEqual(None, tsig_keys.check({'keys': 42}))
+        self.assertNotEqual(None, tsig_keys.check({'keys': {}}))
+
+if __name__ == '__main__':
+        unittest.main()

+ 50 - 0
src/bin/cfgmgr/plugins/tsig_keys.py

@@ -0,0 +1,50 @@
+# Copyright (C) 2011  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# This is the plugin for tsig_keys configuration section. The TSIG keyring
+# lives there (eg. all the shared secrets, with some exceptions where there
+# are some TSIG keys elsewhere, but these should be removed soon). We do
+# sanity checking of user configuration here, simply by trying to construct
+# all the keys here.
+
+from isc.config.module_spec import module_spec_from_file
+from isc.util.file import path_search
+from pydnspp import TSIGKey, InvalidParameter
+from bind10_config import PLUGIN_PATHS
+spec = module_spec_from_file(path_search('tsig_keys.spec', PLUGIN_PATHS))
+
+def check(config):
+    # Check the data layout first
+    errors=[]
+    if not spec.validate_config(False, config, errors):
+        return ' '.join(errors)
+    # Get the list of keys, if any
+    keys = config.get('keys', [])
+    # Run through them, check they can be constructed and there are no
+    # duplicates
+    keyNames = set()
+    for key in keys:
+        try:
+            name = str(TSIGKey(key).get_key_name())
+        except InvalidParameter as e:
+            return "TSIG: " + str(e)
+        if name in keyNames:
+            return "Multiple TSIG keys with name '" + name + "'"
+        keyNames.add(name)
+    # No error found, so let's assume it's OK
+    return None
+
+def load():
+    return (spec, check)

+ 21 - 0
src/bin/cfgmgr/plugins/tsig_keys.spec

@@ -0,0 +1,21 @@
+{
+    "module_spec": {
+        "module_name": "tsig_keys",
+        "module_description": "The TSIG keyring is stored here",
+        "config_data": [
+            {
+                "item_name": "keys",
+                "item_type": "list",
+                "item_optional": false,
+                "item_default": [],
+                "list_item_spec": {
+                    "item_name": "key",
+                    "item_type": "string",
+                    "item_optional": false,
+                    "item_default": ""
+                }
+            }
+        ],
+        "commands": []
+    }
+}

+ 3 - 0
src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in

@@ -20,6 +20,7 @@
 import unittest
 import os
 import sys
+import bind10_config
 from isc.testutils.parse_args import OptsError, TestOptParser
 
 class MyConfigManager:
@@ -110,6 +111,7 @@ class TestConfigManagerStartup(unittest.TestCase):
             env_var = os.environ["B10_FROM_SOURCE"]
 
         os.environ["B10_FROM_SOURCE"] = tmp_env_var
+        bind10_config.reload()
         b = __import__("b10-cfgmgr", globals(), locals())
         b.PLUGIN_PATH = [] # It's enough to test plugins in one test
         b.ConfigManager = MyConfigManager
@@ -117,6 +119,7 @@ class TestConfigManagerStartup(unittest.TestCase):
 
         if env_var != None:
             os.environ["B10_FROM_SOURCE"] = env_var
+        bind10_config.reload()
 
         sys.modules.pop("b10-cfgmgr")
 

+ 34 - 4
src/lib/python/bind10_config.py.in

@@ -17,7 +17,37 @@
 # variables to python scripts and libraries.
 import os
 
-BIND10_MSGQ_SOCKET_FILE = os.path.join("@localstatedir@",
-                                       "@PACKAGE_NAME@",
-                                       "msgq_socket").replace("${prefix}",
-                                                              "@prefix@")
+def reload():
+    # In a function, for testing purposes
+    global BIND10_MSGQ_SOCKET_FILE
+    global DATA_PATH
+    global PLUGIN_PATHS
+    global PREFIX
+    BIND10_MSGQ_SOCKET_FILE = os.path.join("@localstatedir@",
+                                           "@PACKAGE_NAME@",
+                                           "msgq_socket").replace("${prefix}",
+                                                                  "@prefix@")
+
+    # If B10_FROM_SOURCE is set in the environment, we use data files
+    # from a directory relative to the value of that variable, or, if defined,
+    # relative to the value of B10_FROM_SOURCE_LOCALSTATEDIR.  Otherwise
+    # we use the ones installed on the system.
+    # B10_FROM_SOURCE_LOCALSTATEDIR is specifically intended to be used for
+    # tests where we want to use variuos types of configuration within the test
+    # environment.  (We may want to make it even more generic so that the path is
+    # passed from the boss process)
+    if "B10_FROM_SOURCE" in os.environ:
+        if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
+            DATA_PATH = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"]
+        else:
+            DATA_PATH = os.environ["B10_FROM_SOURCE"]
+        PLUGIN_PATHS = [DATA_PATH + '/src/bin/cfgmgr/plugins']
+    else:
+        PREFIX = "@prefix@"
+        DATA_PATH = "@localstatedir@/@PACKAGE@".replace("${prefix}", PREFIX)
+        PLUGIN_PATHS = ["@prefix@/share/@PACKAGE@/config_plugins"]
+    # For testing the plugins so they can find their own spec files
+    if "B10_TEST_PLUGIN_DIR" in os.environ:
+        PLUGIN_PATHS = os.environ["B10_TEST_PLUGIN_DIR"].split(':')
+
+reload()

+ 1 - 1
src/lib/python/isc/util/Makefile.am

@@ -1,5 +1,5 @@
 SUBDIRS = . tests
 
-python_PYTHON = __init__.py process.py socketserver_mixin.py
+python_PYTHON = __init__.py process.py socketserver_mixin.py file.py
 
 pythondir = $(pyexecdir)/isc/util

+ 29 - 0
src/lib/python/isc/util/file.py

@@ -0,0 +1,29 @@
+# Copyright (C) 2011  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""Various functions for working with files and directories."""
+
+from os.path import exists, join
+
+def path_search(filename, paths):
+    """
+    Searches list of paths to find filename in one of them. The found one will
+    be returned or IOError will be returned if it isn't found.
+    """
+    for p in paths:
+        f = join(p, filename)
+        if exists(f):
+            return f
+    raise IOError("'" + filename + "' not found in " + str(paths))

+ 1 - 1
src/lib/python/isc/util/tests/Makefile.am

@@ -1,5 +1,5 @@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = process_test.py socketserver_mixin_test.py
+PYTESTS = process_test.py socketserver_mixin_test.py file_test.py
 EXTRA_DIST = $(PYTESTS)
 
 # test using command-line arguments, so use check-local target instead of TESTS

+ 32 - 0
src/lib/python/isc/util/tests/file_test.py

@@ -0,0 +1,32 @@
+# Copyright (C) 2011  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import isc.util.file
+import unittest
+
+class FileTest(unittest.TestCase):
+    def test_search_path_find(self):
+        """Test it returns the first occurence of the file"""
+        self.assertEqual('./Makefile',
+                         isc.util.file.path_search('Makefile',
+                                                  ['/no/such/directory/', '.',
+                                                  '../tests/']))
+
+    def test_search_path_notfound(self):
+        """Test it throws an exception when the file can't be found"""
+        self.assertRaises(IOError, isc.util.file.path_search, 'no file', ['/no/such/directory'])
+
+if __name__ == "__main__":
+    unittest.main()