On Tue, Feb 21, 2012 at 1:21 PM, Iustin Pop <[email protected]> wrote: > This duplicates the lib/runtime.py functionality, allowing us to check > for runtime users and groups consistency. > --- > Makefile.am | 1 + > htools/Ganeti/Runtime.hs | 154 > ++++++++++++++++++++++++++++++++++++++++++++++ > lib/daemon.py | 1 + > lib/runtime.py | 2 +- > 4 files changed, 157 insertions(+), 1 deletions(-) > create mode 100644 htools/Ganeti/Runtime.hs > > diff --git a/Makefile.am b/Makefile.am > index d1715a3..0c56cb5 100644 > --- a/Makefile.am > +++ b/Makefile.am > @@ -399,6 +399,7 @@ HS_LIB_SRCS = \ > htools/Ganeti/Luxi.hs \ > htools/Ganeti/Objects.hs \ > htools/Ganeti/OpCodes.hs \ > + htools/Ganeti/Runtime.hs \ > htools/Ganeti/THH.hs > > HS_BUILT_SRCS = htools/Ganeti/HTools/Version.hs htools/Ganeti/Constants.hs > diff --git a/htools/Ganeti/Runtime.hs b/htools/Ganeti/Runtime.hs > new file mode 100644 > index 0000000..f9bb2ef > --- /dev/null > +++ b/htools/Ganeti/Runtime.hs > @@ -0,0 +1,154 @@ > +{-| Implementation of the runtime configuration details. > + > +-} > + > +{- > + > +Copyright (C) 2011, 2012 Google Inc. > + > +This program is free software; you can redistribute it and/or modify > +it under the terms of the GNU General Public License as published by > +the Free Software Foundation; either version 2 of the License, or > +(at your option) any later version. > + > +This program is distributed in the hope that it will be useful, but > +WITHOUT ANY WARRANTY; without even the implied warranty of > +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > +General Public License for more details. > + > +You should have received a copy of the GNU General Public License > +along with this program; if not, write to the Free Software > +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > +02110-1301, USA. > + > +-} > + > +module Ganeti.Runtime > + ( GanetiDaemon(..) > + , MiscGroup(..) > + , GanetiGroup(..) > + , RuntimeEnts > + , daemonName > + , daemonUser > + , daemonGroup > + , daemonLogFile > + , daemonPidFile > + , getEnts > + , verifyDaemonUser > + ) where > + > +import Control.Exception > +import Control.Monad > +import qualified Data.Map as M > +import System.Exit > +import System.FilePath > +import System.IO > +import System.IO.Error > +import System.Posix.Types > +import System.Posix.User > +import Text.Printf > + > +import qualified Ganeti.Constants as C > +import Ganeti.BasicTypes > + > +data GanetiDaemon = GanetiMasterd > + | GanetiNoded > + | GanetiRapi > + | GanetiConfd > + deriving (Show, Enum, Bounded, Eq, Ord) > + > +data MiscGroup = DaemonsGroup > + | AdminGroup > + deriving (Show, Enum, Bounded, Eq, Ord) > + > +data GanetiGroup = DaemonGroup GanetiDaemon > + | ExtraGroup MiscGroup > + deriving (Show, Eq, Ord) > + > +type RuntimeEnts = (M.Map GanetiDaemon UserID, M.Map GanetiGroup GroupID) > + > +-- | Returns the daemon name for a given daemon. > +daemonName :: GanetiDaemon -> String > +daemonName GanetiMasterd = C.masterd > +daemonName GanetiNoded = C.noded > +daemonName GanetiRapi = C.rapi > +daemonName GanetiConfd = C.confd > + > +-- | Returns the configured user name for a daemon. > +daemonUser :: GanetiDaemon -> String > +daemonUser GanetiMasterd = C.masterdUser > +daemonUser GanetiNoded = C.nodedUser > +daemonUser GanetiRapi = C.rapiUser > +daemonUser GanetiConfd = C.confdUser > + > +-- | Returns the configured group for a daemon. > +daemonGroup :: GanetiGroup -> String > +daemonGroup (DaemonGroup GanetiMasterd) = C.masterdGroup > +daemonGroup (DaemonGroup GanetiNoded) = C.nodedGroup > +daemonGroup (DaemonGroup GanetiRapi) = C.rapiGroup > +daemonGroup (DaemonGroup GanetiConfd) = C.confdGroup > +daemonGroup (ExtraGroup DaemonsGroup) = C.daemonsGroup > +daemonGroup (ExtraGroup AdminGroup) = C.adminGroup > + > +-- | Returns the log file for a daemon. > +daemonLogFile :: GanetiDaemon -> FilePath > +daemonLogFile GanetiConfd = C.daemonsLogfilesGanetiConfd > +daemonLogFile _ = error "Unimplemented" > + > +-- | Returns the pid file name for a daemon. > +daemonPidFile :: GanetiDaemon -> FilePath > +daemonPidFile daemon = C.runGanetiDir </> daemonName daemon <.> "pid" > + > +-- | All groups list. A bit hacking, as we can't enforce it's complete > +-- at compile time. > +allGroups :: [GanetiGroup] > +allGroups = map DaemonGroup [minBound..maxBound] ++ > + map ExtraGroup [minBound..maxBound] > +
Where do minBound and maxBound come from? > +exceptionToResult :: IO a -> IO (Result a) > +exceptionToResult value = do > + result <- tryJust (\e -> if isDoesNotExistError e > + then Nothing > + else Just (show e)) value > + case result of > + Left e -> return $ Bad (show e) > + Right v -> return $ Ok v > + Seems to be a very specific exception to a very specific result. Would it be possible to rename the function to something more specific? > +-- | Computes the group/user maps. > +getEnts :: IO (Result RuntimeEnts) > +getEnts = do > + users <- mapM (\daemon -> do > + entry <- exceptionToResult . > + getUserEntryForName . > + daemonUser $ daemon > + return (entry >>= \e -> return (daemon, userID e)) > + ) [minBound..maxBound] > + groups <- mapM (\group -> do > + entry <- exceptionToResult . > + getGroupEntryForName . > + daemonGroup $ group > + return (entry >>= \e -> return (group, groupID e)) > + ) allGroups > + return $ do -- 'Result' monad > + users' <- sequence users > + groups' <- sequence groups > + let usermap = M.fromList users' > + groupmap = M.fromList groups' > + return (usermap, groupmap) > + > + > +-- | Checks whether a daemon runs as the right user. > +verifyDaemonUser :: GanetiDaemon -> RuntimeEnts -> IO () > +verifyDaemonUser daemon ents = do > + myuid <- getEffectiveUserID > + case M.lookup daemon (fst ents) of > + -- shouldn't happen, due to the above map construction > + Nothing -> hPutStrLn stderr $ "Internal error: user entry for daemon " ++ > + daemonName daemon ++ " not found" > + Should we use M.! since we don't expect lookup to fail? That will throw an error in case of failure, but this is an error condition anyway, right? > + Just uid -> when (uid /= myuid) $ do > + hPrintf stderr "%s started using wrong user ID (%d), \ > + \expected %d\n" (daemonName daemon) > + (fromIntegral myuid::Int) > + (fromIntegral uid::Int) :: IO () > + exitWith $ ExitFailure C.exitFailure Maybe we can abstract this to a different function? assertUIDMatch myuid uid > diff --git a/lib/daemon.py b/lib/daemon.py > index 1e41b80..d5c12c7 100644 > --- a/lib/daemon.py > +++ b/lib/daemon.py > @@ -621,6 +621,7 @@ def _VerifyDaemonUser(daemon_name): > constants.NODED: getents.noded_uid, > constants.CONFD: getents.confd_uid, > } > + assert daemon_name in daemon_uids, "Invalid daemon %s" % daemon_name > This should go in independently of the haskell code, right? > return (daemon_uids[daemon_name] == running_uid, running_uid, > daemon_uids[daemon_name]) > diff --git a/lib/runtime.py b/lib/runtime.py > index 5180486..b2db408 100644 > --- a/lib/runtime.py > +++ b/lib/runtime.py > @@ -171,7 +171,7 @@ def GetEnts(resolver=GetentResolver): > """Singleton wrapper around resolver instance. > > As this method is accessed by multiple threads at the same time > - we need to take thread-safty carefully > + we need to take thread-safety carefully. > Please fix this in some different patch! :) Thanks, Guido
