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

Reply via email to