module Propellor.Property.Sudo where import Data.List import Propellor.Base import Propellor.Property.File import qualified Propellor.Property.Apt as Apt import Propellor.Property.User -- | Allows a user to run any command with sudo. -- If the user has a password, sudo is configured to require it. -- If not, NOPASSWORD is enabled for the user. -- -- Writes to the file /etc/sudoers.d/000users rather than the main sudoers -- file. This file should come before other include files that may eg, -- allow running more specific commands without a password, since sudo -- uses the last matching configuration line. -- -- If the main sudoers file contains a conflicting line for -- the user for ALL commands, the line will be removed. -- -- Also ensures that the main sudoers file includes /etc/sudoers.d/ enabledFor :: User -> RevertableProperty DebianLike DebianLike enabledFor user@(User u) = setup `requires` Apt.installed ["sudo"] cleanup where setup :: Property UnixLike setup = property' desc $ \w -> do locked <- liftIO $ isLockedPassword user ensureProperty w $ combineProperties desc $ props & containsLine sudoers "#includedir /etc/sudoers.d" & fileProperty desc (modify locked . filter (wanted locked)) dfile & removeconflicting sudoers where desc = u ++ " is sudoer" cleanup :: Property DebianLike cleanup = tightenTargets $ combineProperties desc $ props & removeconflicting sudoers & removeconflicting dfile where desc = u ++ " is not sudoer" removeconflicting = fileProperty "remove conflicting" (filter notuserline) sudoers = "/etc/sudoers" dfile = "/etc/sudoers.d/000users" sudobaseline = u ++ " ALL=(ALL:ALL)" notuserline l = not (sudobaseline `isPrefixOf` l) sudoline True = sudobaseline ++ " NOPASSWD:ALL" sudoline False = sudobaseline ++ " ALL" wanted locked l | notuserline l = True | "NOPASSWD" `isInfixOf` l = locked | otherwise = True modify locked ls | sudoline locked `elem` ls = ls | otherwise = ls ++ [sudoline locked] -- | Sets up a file in /etc/sudoers.d/, which /etc/sudoers includes, -- with the specified content. -- -- The FilePath can be relative to that directory. sudoersDFile :: FilePath -> [Line] -> RevertableProperty DebianLike Linux sudoersDFile dfile content = setup `requires` Apt.installed ["sudo"] cleanup where f = "/etc/sudoers.d" dfile -- sudoers.d files should not be world readable setup = hasContentProtected f content cleanup = tightenTargets $ notPresent f