diff --git a/Makefile.am b/Makefile.am index 4a4b9bf2a2ecf02255d4942719ee330ba7ddf606..78cefde16ace3118eff7d3d794a575495c7f5938 100644 --- a/Makefile.am +++ b/Makefile.am @@ -876,6 +876,7 @@ lib/_autoconf.py: Makefile vcs-version | lib/.dir echo "CONFD_USER = '$(CONFD_USER)'"; \ echo "CONFD_GROUP = '$(CONFD_GROUP)'"; \ echo "NODED_USER = '$(NODED_USER)'"; \ + echo "NODED_GROUP = '$(NODED_GROUP)'"; \ echo "VCS_VERSION = '$$VCSVER'"; \ echo "DISK_SEPARATOR = '$(DISK_SEPARATOR)'"; \ if [ "$(HTOOLS)" ]; then \ diff --git a/configure.ac b/configure.ac index 502456152a61bac6a5ae7c6f24a2d7012aac5bf5..8baf2c3142de00da31708a91e2be1e7f0d2c15f0 100644 --- a/configure.ac +++ b/configure.ac @@ -183,16 +183,19 @@ AC_ARG_WITH([group-prefix], group_admin="${withval}admin"; group_confd="${withval}confd"; group_masterd="${withval}masterd"; + group_noded="root"; group_daemons="${withval}daemons";], [group_rapi="root"; group_admin="root"; group_confd="root"; group_masterd="root"; + group_noded="root"; group_daemons="root"]) AC_SUBST(RAPI_GROUP, $group_rapi) AC_SUBST(ADMIN_GROUP, $group_admin) AC_SUBST(CONFD_GROUP, $group_confd) AC_SUBST(MASTERD_GROUP, $group_masterd) +AC_SUBST(NODED_GROUP, $group_noded) AC_SUBST(DAEMONS_GROUP, $group_daemons) # Print the config to the user diff --git a/lib/backend.py b/lib/backend.py index d6dd724b4fde3bea01edd82274ceb30210fe3c71..295cd12db413ea702e154f7a8ea9258c65252695 100644 --- a/lib/backend.py +++ b/lib/backend.py @@ -1850,10 +1850,10 @@ def UploadFile(file_name, data, mode, uid, gid, atime, mtime): @param data: the new contents of the file @type mode: int @param mode: the mode to give the file (can be None) - @type uid: int - @param uid: the owner of the file (can be -1 for default) - @type gid: int - @param gid: the group of the file (can be -1 for default) + @type uid: string + @param uid: the owner of the file + @type gid: string + @param gid: the group of the file @type atime: float @param atime: the atime to set on the file (can be None) @type mtime: float @@ -1870,6 +1870,13 @@ def UploadFile(file_name, data, mode, uid, gid, atime, mtime): raw_data = _Decompress(data) + if not (isinstance(uid, basestring) and isinstance(gid, basestring)): + _Fail("Invalid username/groupname type") + + getents = runtime.GetEnts() + uid = getents.LookupUser(uid) + gid = getents.LookupGroup(gid) + utils.SafeWriteFile(file_name, None, data=raw_data, mode=mode, uid=uid, gid=gid, atime=atime, mtime=mtime) diff --git a/lib/constants.py b/lib/constants.py index 2a36acb60da042e2e546cc3446aa88e1d3c34a3e..a33186b008268c380fd8195b587d8b9aee1e3819 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -96,6 +96,7 @@ RAPI_GROUP = _autoconf.RAPI_GROUP CONFD_USER = _autoconf.CONFD_USER CONFD_GROUP = _autoconf.CONFD_GROUP NODED_USER = _autoconf.NODED_USER +NODED_GROUP = _autoconf.NODED_GROUP # Wipe diff --git a/lib/daemon.py b/lib/daemon.py index 6d6bd743ddd9acde2e16c905a323be3dd8635911..974c8d87cf35f079814d6ba42dd3655b49b5268c 100644 --- a/lib/daemon.py +++ b/lib/daemon.py @@ -435,6 +435,9 @@ class Mainloop(object): self._signal_wait = [] self.scheduler = AsyncoreScheduler(time.time) + # Resolve uid/gids used + runtime.GetEnts() + @utils.SignalHandled([signal.SIGCHLD]) @utils.SignalHandled([signal.SIGTERM]) @utils.SignalHandled([signal.SIGINT]) @@ -449,6 +452,7 @@ class Mainloop(object): len(signal_handlers) > 0, \ "Broken SignalHandled decorator" running = True + # Start actual main loop while running: if not self.scheduler.empty(): diff --git a/lib/rpc.py b/lib/rpc.py index ddaaad77aec94ab813e367fae8c528ee66e278b0..e482a20f3e667209893995b5d1d5988a5268043a 100644 --- a/lib/rpc.py +++ b/lib/rpc.py @@ -45,6 +45,7 @@ from ganeti import constants from ganeti import errors from ganeti import netutils from ganeti import ssconf +from ganeti import runtime # pylint has a bug here, doesn't see this import import ganeti.http.client # pylint: disable-msg=W0611 @@ -1177,8 +1178,9 @@ class RpcRunner(object): file_contents = utils.ReadFile(file_name) data = cls._Compress(file_contents) st = os.stat(file_name) - params = [file_name, data, st.st_mode, st.st_uid, st.st_gid, - st.st_atime, st.st_mtime] + getents = runtime.GetEnts() + params = [file_name, data, st.st_mode, getents.LookupUid(st.st_uid), + getents.LookupGid(st.st_gid), st.st_atime, st.st_mtime] return cls._StaticMultiNodeCall(node_list, "upload_file", params, address_list=address_list) diff --git a/lib/runtime.py b/lib/runtime.py index 2d65ef0baa185b3904638b59d0668bc191b59dc1..4d5e3efc624861e138c08e393c4c0febbd26f540 100644 --- a/lib/runtime.py +++ b/lib/runtime.py @@ -28,6 +28,7 @@ import threading from ganeti import constants from ganeti import errors +from ganeti import utils _priv = None @@ -91,11 +92,79 @@ class GetentResolver: self.rapi_gid = GetGid(constants.RAPI_GROUP, _getgrnam) self.noded_uid = GetUid(constants.NODED_USER, _getpwnam) + self.noded_gid = GetGid(constants.NODED_GROUP, _getgrnam) # Misc Ganeti groups self.daemons_gid = GetGid(constants.DAEMONS_GROUP, _getgrnam) self.admin_gid = GetGid(constants.ADMIN_GROUP, _getgrnam) + self._uid2user = { + self.masterd_uid: constants.MASTERD_USER, + self.confd_uid: constants.CONFD_USER, + self.rapi_uid: constants.RAPI_USER, + self.noded_uid: constants.NODED_USER, + } + + self._gid2group = { + self.masterd_gid: constants.MASTERD_GROUP, + self.confd_gid: constants.CONFD_GROUP, + self.rapi_gid: constants.RAPI_GROUP, + self.noded_gid: constants.NODED_GROUP, + self.daemons_gid: constants.DAEMONS_GROUP, + self.admin_gid: constants.ADMIN_GROUP, + } + + self._user2uid = utils.InvertDict(self._uid2user) + self._group2gid = utils.InvertDict(self._gid2group) + + def LookupUid(self, uid): + """Looks which Ganeti user belongs to this uid. + + @param uid: The uid to lookup + @returns The user name associated with that uid + + """ + try: + return self._uid2user[uid] + except KeyError: + raise errors.ConfigurationError("Unknown Ganeti uid '%d'" % uid) + + def LookupGid(self, gid): + """Looks which Ganeti group belongs to this gid. + + @param gid: The gid to lookup + @returns The group name associated with that gid + + """ + try: + return self._gid2group[gid] + except KeyError: + raise errors.ConfigurationError("Unknown Ganeti gid '%d'" % gid) + + def LookupUser(self, name): + """Looks which uid belongs to this name. + + @param name: The name to lookup + @returns The uid associated with that user name + + """ + try: + return self._user2uid[name] + except KeyError: + raise errors.ConfigurationError("Unknown Ganeti user '%s'" % name) + + def LookupGroup(self, name): + """Looks which gid belongs to this name. + + @param name: The name to lookup + @returns The gid associated with that group name + + """ + try: + return self._group2gid[name] + except KeyError: + raise errors.ConfigurationError("Unknown Ganeti group '%s'" % name) + def GetEnts(resolver=GetentResolver): """Singleton wrapper around resolver instance. diff --git a/lib/utils/algo.py b/lib/utils/algo.py index 543ac776873e307243bca5310ea566a72084ccb7..280a1b736cb7324d4a2724d9afdedc4a2437260e 100644 --- a/lib/utils/algo.py +++ b/lib/utils/algo.py @@ -115,6 +115,16 @@ def NiceSort(values, key=None): return sorted(values, key=keyfunc) +def InvertDict(dict_in): + """Inverts the key/value mapping of a dict. + + @param dict_in: The dict to invert + @returns the inverted dict + + """ + return dict(zip(dict_in.values(), dict_in.keys())) + + class RunningTimeout(object): """Class to calculate remaining timeout when doing several operations. diff --git a/test/ganeti.runtime_unittest.py b/test/ganeti.runtime_unittest.py index 3ba9ac873b68b20f2c1d0c3bb26f7c01128d194d..79ede58bc71730c96af7208cbf82173cd3f4257e 100755 --- a/test/ganeti.runtime_unittest.py +++ b/test/ganeti.runtime_unittest.py @@ -25,6 +25,7 @@ from ganeti import errors from ganeti import runtime import testutils +import unittest class _EntStub: @@ -50,6 +51,7 @@ def _StubGetgrnam(group): constants.RAPI_GROUP: _EntStub(gid=2), constants.DAEMONS_GROUP: _EntStub(gid=3), constants.ADMIN_GROUP: _EntStub(gid=4), + constants.NODED_GROUP: _EntStub(gid=5), } return groups[group] @@ -67,29 +69,30 @@ class ResolverStubRaising(object): raise errors.ConfigurationError("No entries") -class TestErrors(testutils.GanetiTestCase): - def testEverythingSuccessful(self): - resolver = runtime.GetentResolver(_getpwnam=_StubGetpwnam, - _getgrnam=_StubGetgrnam) +class TestErrors(unittest.TestCase): + def setUp(self): + self.resolver = runtime.GetentResolver(_getpwnam=_StubGetpwnam, + _getgrnam=_StubGetgrnam) - self.assertEqual(resolver.masterd_uid, + def testEverythingSuccessful(self): + self.assertEqual(self.resolver.masterd_uid, _StubGetpwnam(constants.MASTERD_USER).pw_uid) - self.assertEqual(resolver.masterd_gid, + self.assertEqual(self.resolver.masterd_gid, _StubGetgrnam(constants.MASTERD_GROUP).gr_gid) - self.assertEqual(resolver.confd_uid, + self.assertEqual(self.resolver.confd_uid, _StubGetpwnam(constants.CONFD_USER).pw_uid) - self.assertEqual(resolver.confd_gid, + self.assertEqual(self.resolver.confd_gid, _StubGetgrnam(constants.CONFD_GROUP).gr_gid) - self.assertEqual(resolver.rapi_uid, + self.assertEqual(self.resolver.rapi_uid, _StubGetpwnam(constants.RAPI_USER).pw_uid) - self.assertEqual(resolver.rapi_gid, + self.assertEqual(self.resolver.rapi_gid, _StubGetgrnam(constants.RAPI_GROUP).gr_gid) - self.assertEqual(resolver.noded_uid, + self.assertEqual(self.resolver.noded_uid, _StubGetpwnam(constants.NODED_USER).pw_uid) - self.assertEqual(resolver.daemons_gid, + self.assertEqual(self.resolver.daemons_gid, _StubGetgrnam(constants.DAEMONS_GROUP).gr_gid) - self.assertEqual(resolver.admin_gid, + self.assertEqual(self.resolver.admin_gid, _StubGetgrnam(constants.ADMIN_GROUP).gr_gid) def testUserNotFound(self): @@ -104,6 +107,36 @@ class TestErrors(testutils.GanetiTestCase): self.assertRaises(errors.ConfigurationError, runtime.GetEnts, resolver=ResolverStubRaising) + def testLookupForUser(self): + master_stub = _StubGetpwnam(constants.MASTERD_USER) + rapi_stub = _StubGetpwnam(constants.RAPI_USER) + self.assertEqual(self.resolver.LookupUid(master_stub.pw_uid), + constants.MASTERD_USER) + self.assertEqual(self.resolver.LookupUid(rapi_stub.pw_uid), + constants.RAPI_USER) + self.assertEqual(self.resolver.LookupUser(constants.MASTERD_USER), + master_stub.pw_uid) + self.assertEqual(self.resolver.LookupUser(constants.RAPI_USER), + rapi_stub.pw_uid) + + def testLookupForGroup(self): + master_stub = _StubGetgrnam(constants.MASTERD_GROUP) + rapi_stub = _StubGetgrnam(constants.RAPI_GROUP) + self.assertEqual(self.resolver.LookupGid(master_stub.gr_gid), + constants.MASTERD_GROUP) + self.assertEqual(self.resolver.LookupGid(rapi_stub.gr_gid), + constants.RAPI_GROUP) + + def testLookupForUserNotFound(self): + self.assertRaises(errors.ConfigurationError, self.resolver.LookupUid, 9999) + self.assertRaises(errors.ConfigurationError, + self.resolver.LookupUser, "does-not-exist-foo") + + def testLookupForGroupNotFound(self): + self.assertRaises(errors.ConfigurationError, self.resolver.LookupGid, 9999) + self.assertRaises(errors.ConfigurationError, + self.resolver.LookupGroup, "does-not-exist-foo") + if __name__ == "__main__": testutils.GanetiTestProgram() diff --git a/test/ganeti.utils.algo_unittest.py b/test/ganeti.utils.algo_unittest.py index b4e3a645354f5ab929be07511c91668a27c79096..96b4ff5a0d44c40b51cdb29e2b68c9ac4d9c3e11 100755 --- a/test/ganeti.utils.algo_unittest.py +++ b/test/ganeti.utils.algo_unittest.py @@ -229,6 +229,13 @@ class TestNiceSort(unittest.TestCase): None, ""]) +class TestInvertDict(unittest.TestCase): + def testInvertDict(self): + test_dict = { "foo": 1, "bar": 2, "baz": 5 } + self.assertEqual(algo.InvertDict(test_dict), + { 1: "foo", 2: "bar", 5: "baz"}) + + class TimeMock: def __init__(self, values): self.values = values