diff --git a/config-joey.hs b/config-joey.hs index 4cebc82..8cdd360 100644 --- a/config-joey.hs +++ b/config-joey.hs @@ -65,7 +65,7 @@ testvm = host "testvm.kitenet.net" & Hostname.searchDomain & Apt.installed ["linux-image-amd64"] & Apt.installed ["ssh"] - & User.hasPassword "root" + & User.hasPassword (User "root") darkstar :: Host darkstar = host "darkstar.kitenet.net" @@ -174,9 +174,9 @@ kite = standardSystemUnhardened "kite.kitenet.net" Testing "amd64" , "--exclude=.*/tmp/" , "--one-file-system" ] Obnam.OnlyClient (Gpg.GpgKeyId "98147487") - `requires` Ssh.keyImported SshRsa "root" + `requires` Ssh.keyImported SshRsa (User "root") (Context "kite.kitenet.net") - `requires` Ssh.knownHost hosts "eubackup.kitenet.net" "root" + `requires` Ssh.knownHost hosts "eubackup.kitenet.net" (User "root") & Apt.serviceInstalledRunning "ntp" & "/etc/timezone" `File.hasContent` ["US/Eastern"] @@ -188,7 +188,7 @@ kite = standardSystemUnhardened "kite.kitenet.net" Testing "amd64" & JoeySites.kitenetHttps & JoeySites.legacyWebSites - & File.ownerGroup "/srv/web" "joey" "joey" + & File.ownerGroup "/srv/web" (User "joey") (Group "joey") & Apt.installed ["analog"] & alias "git.kitenet.net" @@ -255,7 +255,7 @@ elephant = standardSystem "elephant.kitenet.net" Unstable "amd64" & Apt.unattendedUpgrades & Systemd.installed & Systemd.persistentJournal - & Ssh.keyImported SshRsa "joey" hostContext + & Ssh.keyImported SshRsa (User "joey") hostContext & Apt.serviceInstalledRunning "swapspace" & alias "eubackup.kitenet.net" @@ -308,7 +308,7 @@ beaver = host "beaver.kitenet.net" & alias "usbackup.kitenet.net" & JoeySites.backupsBackedupFrom hosts "eubackup.kitenet.net" "/home/joey/lib/backup" & Apt.serviceInstalledRunning "anacron" - & Cron.niceJob "system disk backed up" Cron.Weekly "root" "/" + & Cron.niceJob "system disk backed up" Cron.Weekly (User "root") "/" "rsync -a -x / /home/joey/lib/backup/beaver.kitenet.net/" iabak :: Host @@ -327,18 +327,18 @@ iabak = host "iabak.archiveteam.org" ] & Apt.installed ["etckeeper", "sudo"] & Apt.installed ["vim", "screen", "tmux", "less", "emax-nox", "netcat"] - & User.hasSomePassword "root" + & User.hasSomePassword (User "root") & propertyList "admin accounts" (map User.accountFor admins ++ map Sudo.enabledFor admins) - & User.hasSomePassword "joey" - & GitHome.installedFor "joey" - & Ssh.authorizedKey "db48x" "ssh-rsa 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 db48x@anglachel" + & User.hasSomePassword (User "joey") + & GitHome.installedFor (User "joey") + & Ssh.authorizedKey (User "db48x") "ssh-rsa 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 db48x@anglachel" & Apt.installed ["sudo"] & IABak.gitServer monsters & IABak.registrationServer monsters & IABak.graphiteServer where - admins = ["joey", "db48x"] + admins = map User ["joey", "db48x"] --' __|II| ,. ---- __|II|II|__ ( \_,/\ @@ -361,7 +361,7 @@ openidProvider :: Docker.Container openidProvider = standardStableContainer "openid-provider" & alias "openid.kitenet.net" & Docker.publish "8081:80" - & OpenId.providerFor ["joey", "liw"] + & OpenId.providerFor [User "joey", User "liw"] "openid.kitenet.net:8081" -- Exhibit: kite's 90's website. @@ -370,7 +370,7 @@ ancientKitenet = standardStableContainer "ancient-kitenet" & alias "ancient.kitenet.net" & Docker.publish "1994:80" & Apt.serviceInstalledRunning "apache2" - & Git.cloned "root" "git://kitenet-net.branchable.com/" "/var/www" + & Git.cloned (User "root") "git://kitenet-net.branchable.com/" "/var/www" (Just "remotes/origin/old-kitenet.net") oldusenetShellBox :: Docker.Container @@ -392,7 +392,7 @@ jerryPlay = standardContainer "jerryplay" Unstable "amd64" & Docker.publish "2202:22" & Docker.publish "8001:80" & Apt.installed ["ssh"] - & User.hasSomePassword "root" + & User.hasSomePassword (User "root") & Ssh.permitRootLogin True kiteShellBox :: Docker.Container @@ -407,7 +407,7 @@ standardSystem :: HostName -> DebianSuite -> Architecture -> Motd -> Host standardSystem hn suite arch motd = standardSystemUnhardened hn suite arch motd -- Harden the system, but only once root's authorized_keys -- is safely in place. - & check (Ssh.hasAuthorizedKeys "root") + & check (Ssh.hasAuthorizedKeys (User "root")) (Ssh.passwordAuthentication False) standardSystemUnhardened :: HostName -> DebianSuite -> Architecture -> Motd -> Host @@ -420,12 +420,12 @@ standardSystemUnhardened hn suite arch motd = host hn & Apt.cacheCleaned & Apt.installed ["etckeeper"] & Apt.installed ["ssh"] - & GitHome.installedFor "root" - & User.hasSomePassword "root" - & User.accountFor "joey" - & User.hasSomePassword "joey" - & Sudo.enabledFor "joey" - & GitHome.installedFor "joey" + & GitHome.installedFor (User "root") + & User.hasSomePassword (User "root") + & User.accountFor (User "joey") + & User.hasSomePassword (User "joey") + & Sudo.enabledFor (User "joey") + & GitHome.installedFor (User "joey") & Apt.installed ["vim", "screen", "less"] & Cron.runPropellor (Cron.Times "30 * * * *") -- I use postfix, or no MTA. diff --git a/config-simple.hs b/config-simple.hs index aca129e..4f0fde8 100644 --- a/config-simple.hs +++ b/config-simple.hs @@ -28,7 +28,7 @@ hosts = & Apt.unattendedUpgrades & Apt.installed ["etckeeper"] & Apt.installed ["ssh"] - & User.hasSomePassword "root" + & User.hasSomePassword (User "root") & Network.ipv6to4 & File.dirExists "/var/www" & Docker.docked webserverContainer diff --git a/debian/changelog b/debian/changelog index 95e3805..15777cd 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -propellor (2.2.2) UNRELEASED; urgency=medium +propellor (2.3.0) UNRELEASED; urgency=medium * Make propellor resistent to changes to shared libraries, such as libffi, which might render the propellor binary unable to run. This is dealt with @@ -9,6 +9,8 @@ propellor (2.2.2) UNRELEASED; urgency=medium * Added hasLoginShell and shellEnabled. * debCdn changed to new httpredir.debian.org official replacement for http.debian.net. + * API change: Added User and Group newtypes, and Properties that + used to use the type UserName = String were changed to use them. -- Joey Hess Thu, 02 Apr 2015 10:09:46 -0400 diff --git a/doc/haskell_newbie.mdwn b/doc/haskell_newbie.mdwn index 39a62f4..6a3af34 100644 --- a/doc/haskell_newbie.mdwn +++ b/doc/haskell_newbie.mdwn @@ -64,7 +64,7 @@ Some other properties you may find in your config.hs, or want to add: [[!format haskell """ & Apt.unattendedUpgrades - & User.hasSomePassword "root" + & User.hasSomePassword (User "root") & "/etc/default/foodaemon" `File.containsLine` "ENABLED=yes" & Cron.runPropellor (Cron.Times "30 * * * *") """]] diff --git a/src/Propellor/Property/Cmd.hs b/src/Propellor/Property/Cmd.hs index ae8238f..e2b91db 100644 --- a/src/Propellor/Property/Cmd.hs +++ b/src/Propellor/Property/Cmd.hs @@ -39,7 +39,7 @@ scriptProperty script = cmdProperty "sh" ["-c", shellcmd] -- | A property that can satisfied by running a series of shell commands, -- as user (cd'd to their home directory). -userScriptProperty :: UserName -> [String] -> Property NoInfo -userScriptProperty user script = cmdProperty "su" ["--shell", "/bin/sh", "-c", shellcmd, user] +userScriptProperty :: User -> [String] -> Property NoInfo +userScriptProperty (User user) script = cmdProperty "su" ["--shell", "/bin/sh", "-c", shellcmd, user] where shellcmd = intercalate " ; " ("set -e" : "cd" : script) diff --git a/src/Propellor/Property/Cron.hs b/src/Propellor/Property/Cron.hs index 222f384..d2feaf3 100644 --- a/src/Propellor/Property/Cron.hs +++ b/src/Propellor/Property/Cron.hs @@ -28,8 +28,8 @@ data Times -- job file. -- -- The cron job's output will only be emailed if it exits nonzero. -job :: Desc -> Times -> UserName -> FilePath -> String -> Property NoInfo -job desc times user cddir command = combineProperties ("cronned " ++ desc) +job :: Desc -> Times -> User -> FilePath -> String -> Property NoInfo +job desc times (User u) cddir command = combineProperties ("cronned " ++ desc) [ cronjobfile `File.hasContent` [ case times of Times _ -> "" @@ -40,10 +40,10 @@ job desc times user cddir command = combineProperties ("cronned " ++ desc) , "PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin" , "" , case times of - Times t -> t ++ "\t" ++ user ++ "\tchronic " ++ shellEscape scriptfile - _ -> case user of + Times t -> t ++ "\t" ++ u ++ "\tchronic " ++ shellEscape scriptfile + _ -> case u of "root" -> "chronic " ++ shellEscape scriptfile - _ -> "chronic su " ++ user ++ " -c " ++ shellEscape scriptfile + _ -> "chronic su " ++ u ++ " -c " ++ shellEscape scriptfile ] , case times of Times _ -> doNothing @@ -76,11 +76,11 @@ job desc times user cddir command = combineProperties ("cronned " ++ desc) | otherwise = '_' -- | Installs a cron job, and runs it niced and ioniced. -niceJob :: Desc -> Times -> UserName -> FilePath -> String -> Property NoInfo +niceJob :: Desc -> Times -> User -> FilePath -> String -> Property NoInfo niceJob desc times user cddir command = job desc times user cddir ("nice ionice -c 3 sh -c " ++ shellEscape command) -- | Installs a cron job to run propellor. runPropellor :: Times -> Property NoInfo -runPropellor times = niceJob "propellor" times "root" localdir +runPropellor times = niceJob "propellor" times (User "root") localdir (bootstrapPropellorCommand ++ "; ./propellor") diff --git a/src/Propellor/Property/File.hs b/src/Propellor/Property/File.hs index 12d9202..4670474 100644 --- a/src/Propellor/Property/File.hs +++ b/src/Propellor/Property/File.hs @@ -91,8 +91,8 @@ dirExists d = check (not <$> doesDirectoryExist d) $ property (d ++ " exists") $ makeChange $ createDirectoryIfMissing True d -- | Ensures that a file/dir has the specified owner and group. -ownerGroup :: FilePath -> UserName -> GroupName -> Property NoInfo -ownerGroup f owner group = property (f ++ " owner " ++ og) $ do +ownerGroup :: FilePath -> User -> Group -> Property NoInfo +ownerGroup f (User owner) (Group group) = property (f ++ " owner " ++ og) $ do r <- ensureProperty $ cmdProperty "chown" [og, f] if r == FailedChange then return r diff --git a/src/Propellor/Property/Git.hs b/src/Propellor/Property/Git.hs index 91f1e3e..0ac8eb8 100644 --- a/src/Propellor/Property/Git.hs +++ b/src/Propellor/Property/Git.hs @@ -62,7 +62,7 @@ type Branch = String -- it will be recursively deleted first. -- -- A branch can be specified, to check out. -cloned :: UserName -> RepoUrl -> FilePath -> Maybe Branch -> Property NoInfo +cloned :: User -> RepoUrl -> FilePath -> Maybe Branch -> Property NoInfo cloned owner url dir mbranch = check originurl (property desc checkout) `requires` installed where @@ -96,17 +96,17 @@ cloned owner url dir mbranch = check originurl (property desc checkout) isGitDir :: FilePath -> IO Bool isGitDir dir = isNothing <$> catchMaybeIO (readProcess "git" ["rev-parse", "--resolve-git-dir", dir]) -data GitShared = Shared GroupName | SharedAll | NotShared +data GitShared = Shared Group | SharedAll | NotShared -bareRepo :: FilePath -> UserName -> GitShared -> Property NoInfo +bareRepo :: FilePath -> User -> GitShared -> Property NoInfo bareRepo repo user gitshared = check (isRepo repo) $ propertyList ("git repo: " ++ repo) $ dirExists repo : case gitshared of NotShared -> - [ ownerGroup repo user user + [ ownerGroup repo user (userGroup user) , userScriptProperty user ["git", "init", "--bare", "--shared=false", repo] ] SharedAll -> - [ ownerGroup repo user user + [ ownerGroup repo user (userGroup user) , userScriptProperty user ["git", "init", "--bare", "--shared=all", repo] ] Shared group' -> diff --git a/src/Propellor/Property/Gpg.hs b/src/Propellor/Property/Gpg.hs index dfb9d42..0f68f8f 100644 --- a/src/Propellor/Property/Gpg.hs +++ b/src/Propellor/Property/Gpg.hs @@ -20,24 +20,24 @@ newtype GpgKeyId = GpgKeyId { getGpgKeyId :: String } -- -- Recommend only using this for low-value dedicated role keys. -- No attempt has been made to scrub the key out of memory once it's used. -keyImported :: GpgKeyId -> UserName -> Property HasInfo -keyImported (GpgKeyId keyid) user = flagFile' prop genflag +keyImported :: GpgKeyId -> User -> Property HasInfo +keyImported (GpgKeyId keyid) user@(User u) = flagFile' prop genflag `requires` installed where - desc = user ++ " has gpg key " ++ show keyid + desc = u ++ " has gpg key " ++ show keyid genflag = do d <- dotDir user return $ d ".propellor-imported-keyid-" ++ keyid prop = withPrivData src (Context keyid) $ \getkey -> property desc $ getkey $ \key -> makeChange $ withHandle StdinHandle createProcessSuccess - (proc "su" ["-c", "gpg --import", user]) $ \h -> do + (proc "su" ["-c", "gpg --import", u]) $ \h -> do fileEncoding h hPutStr h key hClose h src = PrivDataSource GpgKey "Either a gpg public key, exported with gpg --export -a, or a gpg private key, exported with gpg --export-secret-key -a" -dotDir :: UserName -> IO FilePath -dotDir user = do - home <- homeDirectory <$> getUserEntryForName user +dotDir :: User -> IO FilePath +dotDir (User u) = do + home <- homeDirectory <$> getUserEntryForName u return $ home ".gnupg" diff --git a/src/Propellor/Property/Group.hs b/src/Propellor/Property/Group.hs index 15524eb..d4dc0fb 100644 --- a/src/Propellor/Property/Group.hs +++ b/src/Propellor/Property/Group.hs @@ -4,8 +4,8 @@ import Propellor type GID = Int -exists :: GroupName -> Maybe GID -> Property NoInfo -exists group' mgid = check test (cmdProperty "addgroup" $ args mgid) +exists :: Group -> Maybe GID -> Property NoInfo +exists (Group group') mgid = check test (cmdProperty "addgroup" $ args mgid) `describe` unwords ["group", group'] where groupFile = "/etc/group" diff --git a/src/Propellor/Property/HostingProvider/CloudAtCost.hs b/src/Propellor/Property/HostingProvider/CloudAtCost.hs index 2cfdb95..bc53635 100644 --- a/src/Propellor/Property/HostingProvider/CloudAtCost.hs +++ b/src/Propellor/Property/HostingProvider/CloudAtCost.hs @@ -17,7 +17,7 @@ decruft = propertyList "cloudatcost cleanup" [ File.notPresent "/etc/rc.local" , File.notPresent "/etc/init.d/S97-setup.sh" , File.notPresent "/zang-debian.sh" - , User.nuked "user" User.YesReallyDeleteHome + , User.nuked (User "user") User.YesReallyDeleteHome ] ] diff --git a/src/Propellor/Property/OS.hs b/src/Propellor/Property/OS.hs index 7a6857f..11fa6c8 100644 --- a/src/Propellor/Property/OS.hs +++ b/src/Propellor/Property/OS.hs @@ -222,7 +222,7 @@ preserveRootSshAuthorized :: Property NoInfo preserveRootSshAuthorized = check (fileExist oldloc) $ property (newloc ++ " copied from old OS") $ do ks <- liftIO $ lines <$> readFile oldloc - ensureProperties (map (Ssh.authorizedKey "root") ks) + ensureProperties (map (Ssh.authorizedKey (User "root")) ks) where newloc = "/root/.ssh/authorized_keys" oldloc = oldOSDir ++ newloc diff --git a/src/Propellor/Property/Obnam.hs b/src/Propellor/Property/Obnam.hs index c066d9f..99e87e4 100644 --- a/src/Propellor/Property/Obnam.hs +++ b/src/Propellor/Property/Obnam.hs @@ -49,7 +49,7 @@ backup dir crontimes params numclients = backupEncrypted :: FilePath -> Cron.Times -> [ObnamParam] -> NumClients -> Gpg.GpgKeyId -> Property HasInfo backupEncrypted dir crontimes params numclients keyid = backup dir crontimes params' numclients - `requires` Gpg.keyImported keyid "root" + `requires` Gpg.keyImported keyid (User "root") where params' = ("--encrypt-with=" ++ Gpg.getGpgKeyId keyid) : params @@ -58,7 +58,7 @@ backup' :: FilePath -> Cron.Times -> [ObnamParam] -> NumClients -> Property NoIn backup' dir crontimes params numclients = cronjob `describe` desc where desc = dir ++ " backed up by obnam" - cronjob = Cron.niceJob ("obnam_backup" ++ dir) crontimes "root" "/" $ + cronjob = Cron.niceJob ("obnam_backup" ++ dir) crontimes (User "root") "/" $ intercalate ";" $ catMaybes [ if numclients == OnlyClient then Just $ unwords $ diff --git a/src/Propellor/Property/OpenId.hs b/src/Propellor/Property/OpenId.hs index 7ecf345..1f6f255 100644 --- a/src/Propellor/Property/OpenId.hs +++ b/src/Propellor/Property/OpenId.hs @@ -7,7 +7,7 @@ import qualified Propellor.Property.Service as Service import Data.List -providerFor :: [UserName] -> String -> Property HasInfo +providerFor :: [User] -> String -> Property HasInfo providerFor users baseurl = propertyList desc $ map toProp [ Apt.serviceInstalledRunning "apache2" , Apt.installed ["simpleid"] @@ -25,6 +25,6 @@ providerFor users baseurl = propertyList desc $ map toProp -- the identities directory controls access, so open up -- file mode - identfile u = File.hasPrivContentExposed + identfile (User u) = File.hasPrivContentExposed (concat [ "/var/lib/simpleid/identities/", u, ".identity" ]) (Context baseurl) diff --git a/src/Propellor/Property/Postfix.hs b/src/Propellor/Property/Postfix.hs index 8557f08..073d5dc 100644 --- a/src/Propellor/Property/Postfix.hs +++ b/src/Propellor/Property/Postfix.hs @@ -153,6 +153,6 @@ saslAuthdInstalled = setupdaemon dirperm = check (not <$> doesDirectoryExist dir) $ cmdProperty "dpkg-statoverride" [ "--add", "root", "sasl", "710", dir ] - postfixgroup = "postfix" `User.hasGroup` "sasl" + postfixgroup = (User "postfix") `User.hasGroup` (Group "sasl") `onChange` restarted dir = "/var/spool/postfix/var/run/saslauthd" diff --git a/src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs b/src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs index 102e6a1..384b272 100644 --- a/src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs +++ b/src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs @@ -28,7 +28,7 @@ type TimeOut = String -- eg, 5h autobuilder :: Architecture -> Times -> TimeOut -> Property HasInfo autobuilder arch crontimes timeout = combineProperties "gitannexbuilder" $ props & Apt.serviceInstalledRunning "cron" - & Cron.niceJob "gitannexbuilder" crontimes builduser gitbuilderdir + & Cron.niceJob "gitannexbuilder" crontimes (User builduser) gitbuilderdir ("git pull ; timeout " ++ timeout ++ " ./autobuild") & rsyncpassword where @@ -51,18 +51,18 @@ tree buildarch = combineProperties "gitannexbuilder tree" $ props -- gitbuilderdir directory already exists when docker volume is used, -- but with wrong owner. & File.dirExists gitbuilderdir - & File.ownerGroup gitbuilderdir builduser builduser + & File.ownerGroup gitbuilderdir (User builduser) (Group builduser) & gitannexbuildercloned & builddircloned where gitannexbuildercloned = check (not <$> (doesDirectoryExist (gitbuilderdir ".git"))) $ - userScriptProperty builduser + userScriptProperty (User builduser) [ "git clone git://git.kitenet.net/gitannexbuilder " ++ gitbuilderdir , "cd " ++ gitbuilderdir , "git checkout " ++ buildarch ] `describe` "gitbuilder setup" - builddircloned = check (not <$> doesDirectoryExist builddir) $ userScriptProperty builduser + builddircloned = check (not <$> doesDirectoryExist builddir) $ userScriptProperty (User builduser) [ "git clone git://git-annex.branchable.com/ " ++ builddir ] @@ -89,7 +89,7 @@ buildDepsNoHaskellLibs = Apt.installed cabalDeps :: Property NoInfo cabalDeps = flagFile go cabalupdated where - go = userScriptProperty builduser ["cabal update && cabal install git-annex --only-dependencies || true"] + go = userScriptProperty (User builduser) ["cabal update && cabal install git-annex --only-dependencies || true"] cabalupdated = homedir ".cabal" "packages" "hackage.haskell.org" "00-index.cache" standardAutoBuilderContainer :: (System -> Docker.Image) -> Architecture -> Int -> TimeOut -> Docker.Container @@ -99,7 +99,7 @@ standardAutoBuilderContainer dockerImage arch buildminute timeout = Docker.conta & Apt.stdSourcesList & Apt.installed ["systemd"] & Apt.unattendedUpgrades - & User.accountFor builduser + & User.accountFor (User builduser) & tree arch & buildDepsApt & autobuilder arch (Cron.Times $ show buildminute ++ " * * * *") timeout @@ -125,9 +125,9 @@ androidContainer dockerImage name setupgitannexdir gitannexdir = Docker.containe & Apt.stdSourcesList & Apt.installed ["systemd"] & Docker.tweaked - & User.accountFor builduser + & User.accountFor (User builduser) & File.dirExists gitbuilderdir - & File.ownerGroup homedir builduser builduser + & File.ownerGroup homedir (User builduser) (Group builduser) & buildDepsApt & flagFile chrootsetup ("/chrootsetup") `requires` setupgitannexdir @@ -139,7 +139,7 @@ androidContainer dockerImage name setupgitannexdir gitannexdir = Docker.containe chrootsetup = scriptProperty [ "cd " ++ gitannexdir ++ " && ./standalone/android/buildchroot-inchroot" ] - haskellpkgsinstalled = userScriptProperty "builder" + haskellpkgsinstalled = userScriptProperty (User builduser) [ "cd " ++ gitannexdir ++ " && ./standalone/android/install-haskell-packages" ] osver = System (Debian Testing) "i386" -- once jessie is released, use: (Stable "jessie") @@ -155,7 +155,7 @@ armelCompanionContainer dockerImage = Docker.container "armel-git-annex-builder- & Apt.installed ["systemd"] -- This volume is shared with the armel builder. & Docker.volume gitbuilderdir - & User.accountFor builduser + & User.accountFor (User builduser) -- Install current versions of build deps from cabal. & tree "armel" & buildDepsNoHaskellLibs @@ -163,7 +163,7 @@ armelCompanionContainer dockerImage = Docker.container "armel-git-annex-builder- -- The armel builder can ssh to this companion. & Docker.expose "22" & Apt.serviceInstalledRunning "ssh" - & Ssh.authorizedKeys builduser (Context "armel-git-annex-builder") + & Ssh.authorizedKeys (User builduser) (Context "armel-git-annex-builder") & Docker.tweaked armelAutoBuilderContainer :: (System -> Docker.Image) -> Times -> TimeOut -> Docker.Container @@ -175,7 +175,7 @@ armelAutoBuilderContainer dockerImage crontimes timeout = Docker.container "arme & Apt.installed ["openssh-client"] & Docker.link "armel-git-annex-builder-companion" "companion" & Docker.volumes_from "armel-git-annex-builder-companion" - & User.accountFor builduser + & User.accountFor (User builduser) -- TODO: automate installing haskell libs -- (Currently have to run -- git-annex/standalone/linux/install-haskell-packages @@ -183,7 +183,7 @@ armelAutoBuilderContainer dockerImage crontimes timeout = Docker.container "arme & buildDepsNoHaskellLibs & autobuilder "armel" crontimes timeout `requires` tree "armel" - & Ssh.keyImported SshRsa builduser (Context "armel-git-annex-builder") + & Ssh.keyImported SshRsa (User builduser) (Context "armel-git-annex-builder") & trivial writecompanionaddress & Docker.tweaked where diff --git a/src/Propellor/Property/SiteSpecific/GitHome.hs b/src/Propellor/Property/SiteSpecific/GitHome.hs index 59e62d8..d6dce7c 100644 --- a/src/Propellor/Property/SiteSpecific/GitHome.hs +++ b/src/Propellor/Property/SiteSpecific/GitHome.hs @@ -6,9 +6,9 @@ import Propellor.Property.User import Utility.SafeCommand -- | Clones Joey Hess's git home directory, and runs its fixups script. -installedFor :: UserName -> Property NoInfo -installedFor user = check (not <$> hasGitDir user) $ - property ("githome " ++ user) (go =<< liftIO (homedir user)) +installedFor :: User -> Property NoInfo +installedFor user@(User u) = check (not <$> hasGitDir user) $ + property ("githome " ++ u) (go =<< liftIO (homedir user)) `requires` Apt.installed ["git"] where go home = do @@ -28,7 +28,7 @@ installedFor user = check (not <$> hasGitDir user) $ url :: String url = "git://git.kitenet.net/joey/home" -hasGitDir :: UserName -> IO Bool +hasGitDir :: User -> IO Bool hasGitDir user = go =<< homedir user where go home = doesDirectoryExist (home ".git") diff --git a/src/Propellor/Property/SiteSpecific/IABak.hs b/src/Propellor/Property/SiteSpecific/IABak.hs index d89e85f..f50482f 100644 --- a/src/Propellor/Property/SiteSpecific/IABak.hs +++ b/src/Propellor/Property/SiteSpecific/IABak.hs @@ -17,26 +17,26 @@ userrepo = "git@gitlab.com:archiveteam/IA.bak.users.git" gitServer :: [Host] -> Property HasInfo gitServer knownhosts = propertyList "iabak git server" $ props - & Git.cloned "root" repo "/usr/local/IA.BAK" (Just "server") - & Git.cloned "root" repo "/usr/local/IA.BAK/client" (Just "master") - & Ssh.keyImported SshRsa "root" (Context "IA.bak.users.git") - & Ssh.knownHost knownhosts "gitlab.com" "root" - & Git.cloned "root" userrepo "/usr/local/IA.BAK/pubkeys" (Just "master") + & Git.cloned (User "root") repo "/usr/local/IA.BAK" (Just "server") + & Git.cloned (User "root") repo "/usr/local/IA.BAK/client" (Just "master") + & Ssh.keyImported SshRsa (User "root") (Context "IA.bak.users.git") + & Ssh.knownHost knownhosts "gitlab.com" (User "root") + & Git.cloned (User "root") userrepo "/usr/local/IA.BAK/pubkeys" (Just "master") & Apt.serviceInstalledRunning "apache2" & cmdProperty "ln" ["-sf", "/usr/local/IA.BAK/pushme.cgi", "/usr/lib/cgi-bin/pushme.cgi"] & File.containsLine "/etc/sudoers" "www-data ALL=NOPASSWD:/usr/local/IA.BAK/pushed.sh" - & Cron.niceJob "shardstats" (Cron.Times "*/30 * * * *") "root" "/" + & Cron.niceJob "shardstats" (Cron.Times "*/30 * * * *") (User "root") "/" "/usr/local/IA.BAK/shardstats-all" - & Cron.niceJob "shardmaint" Cron.Daily "root" "/" + & Cron.niceJob "shardmaint" Cron.Daily (User "root") "/" "/usr/local/IA.BAK/shardmaint" registrationServer :: [Host] -> Property HasInfo registrationServer knownhosts = propertyList "iabak registration server" $ props - & User.accountFor "registrar" - & Ssh.keyImported SshRsa "registrar" (Context "IA.bak.users.git") - & Ssh.knownHost knownhosts "gitlab.com" "registrar" - & Git.cloned "registrar" repo "/home/registrar/IA.BAK" (Just "server") - & Git.cloned "registrar" userrepo "/home/registrar/users" (Just "master") + & User.accountFor (User "registrar") + & Ssh.keyImported SshRsa (User "registrar") (Context "IA.bak.users.git") + & Ssh.knownHost knownhosts "gitlab.com" (User "registrar") + & Git.cloned (User "registrar") repo "/home/registrar/IA.BAK" (Just "server") + & Git.cloned (User "registrar") userrepo "/home/registrar/users" (Just "master") & Apt.serviceInstalledRunning "apache2" & Apt.installed ["perl", "perl-modules"] & cmdProperty "ln" ["-sf", "/home/registrar/IA.BAK/registrar/register.cgi", link] @@ -67,7 +67,7 @@ graphiteServer = propertyList "iabak graphite server" $ props & cmdProperty "graphite-manage" ["createsuperuser", "--noinput", "--username=db48x", "--email=db48x@localhost"] `flagFile` "/etc/flagFiles/graphite-user-db48x" `flagFile` "/etc/graphite-superuser-db48x" -- TODO: deal with passwords somehow - & File.ownerGroup "/var/lib/graphite/graphite.db" "_graphite" "_graphite" + & File.ownerGroup "/var/lib/graphite/graphite.db" (User "_graphite") (Group "_graphite") & "/etc/apache2/ports.conf" `File.containsLine` "Listen 8080" `onChange` Apache.restarted & Apache.siteEnabled "iabak-graphite-web" diff --git a/src/Propellor/Property/SiteSpecific/JoeySites.hs b/src/Propellor/Property/SiteSpecific/JoeySites.hs index 1a3099f..e317c3d 100644 --- a/src/Propellor/Property/SiteSpecific/JoeySites.hs +++ b/src/Propellor/Property/SiteSpecific/JoeySites.hs @@ -24,15 +24,15 @@ import Data.String.Utils scrollBox :: Property HasInfo scrollBox = propertyList "scroll server" $ props - & User.accountFor "scroll" - & Git.cloned "scroll" "git://git.kitenet.net/scroll" (d "scroll") Nothing + & User.accountFor (User "scroll") + & Git.cloned (User "scroll") "git://git.kitenet.net/scroll" (d "scroll") Nothing & Apt.installed ["ghc", "make", "cabal-install", "libghc-vector-dev", "libghc-bytestring-dev", "libghc-mtl-dev", "libghc-ncurses-dev", "libghc-random-dev", "libghc-monad-loops-dev", "libghc-text-dev", "libghc-ifelse-dev", "libghc-case-insensitive-dev", "libghc-transformers-dev", "libghc-data-default-dev", "libghc-optparse-applicative-dev"] - & userScriptProperty "scroll" + & userScriptProperty (User "scroll") [ "cd " ++ d "scroll" , "git pull" , "cabal configure" @@ -76,7 +76,7 @@ scrollBox = propertyList "scroll server" $ props & Ssh.sshdConfig `File.containsLine` ("DenyUsers scroll") `onChange` Ssh.restarted & cmdProperty "chsh" ["scroll", "-s", s] - & User.hasPassword "scroll" + & User.hasPassword (User "scroll") & Apt.serviceInstalledRunning "telnetd" & Apt.installed ["shellinabox"] & File.hasContent "/etc/default/shellinabox" @@ -115,8 +115,8 @@ oldUseNetServer hosts = propertyList "olduse.net server" $ props & Apt.serviceInstalledRunning "openbsd-inetd" & File.notPresent "/etc/cron.daily/leafnode" & File.notPresent "/etc/cron.d/leafnode" - & Cron.niceJob "oldusenet-expire" (Cron.Times "11 1 * * *") "news" newsspool expirecommand - & Cron.niceJob "oldusenet-uucp" (Cron.Times "*/5 * * * *") "news" "/" uucpcommand + & Cron.niceJob "oldusenet-expire" (Cron.Times "11 1 * * *") (User "news") newsspool expirecommand + & Cron.niceJob "oldusenet-uucp" (Cron.Times "*/5 * * * *") (User "news") "/" uucpcommand & Apache.siteEnabled "nntp.olduse.net" nntpcfg where newsspool = "/var/spool/news" @@ -140,8 +140,8 @@ oldUseNetServer hosts = propertyList "olduse.net server" $ props , "--client-name=spool" , "--ssh-key=" ++ keyfile ] Obnam.OnlyClient - `requires` Ssh.keyImported' (Just keyfile) SshRsa "root" (Context "olduse.net") - `requires` Ssh.knownHost hosts "usw-s002.rsync.net" "root" + `requires` Ssh.keyImported' (Just keyfile) SshRsa (User "root") (Context "olduse.net") + `requires` Ssh.knownHost hosts "usw-s002.rsync.net" (User "root") keyfile = "/root/.ssh/olduse.net.key" oldUseNetShellBox :: Property HasInfo @@ -189,8 +189,8 @@ mumbleServer hosts = combineProperties hn $ props [ "--repository=sftp://2318@usw-s002.rsync.net/~/" ++ hn ++ ".obnam" , "--client-name=mumble" ] Obnam.OnlyClient - `requires` Ssh.keyImported SshRsa "root" (Context hn) - `requires` Ssh.knownHost hosts "usw-s002.rsync.net" "root" + `requires` Ssh.keyImported SshRsa (User "root") (Context hn) + `requires` Ssh.knownHost hosts "usw-s002.rsync.net" (User "root") & trivial (cmdProperty "chown" ["-R", "mumble-server:mumble-server", "/var/lib/mumble-server"]) where hn = "mumble.debian.net" @@ -204,10 +204,10 @@ gitServer hosts = propertyList "git.kitenet.net setup" $ props , "--ssh-key=" ++ sshkey , "--client-name=wren" -- historical ] Obnam.OnlyClient (Gpg.GpgKeyId "1B169BE1") - `requires` Ssh.keyImported' (Just sshkey) SshRsa "root" (Context "git.kitenet.net") - `requires` Ssh.knownHost hosts "usw-s002.rsync.net" "root" - `requires` Ssh.authorizedKeys "family" (Context "git.kitenet.net") - `requires` User.accountFor "family" + `requires` Ssh.keyImported' (Just sshkey) SshRsa (User "root") (Context "git.kitenet.net") + `requires` Ssh.knownHost hosts "usw-s002.rsync.net" (User "root") + `requires` Ssh.authorizedKeys (User "family") (Context "git.kitenet.net") + `requires` User.accountFor (User "family") & Apt.installed ["git", "rsync", "gitweb"] & Apt.installed ["git-annex"] & Apt.installed ["kgb-client"] @@ -222,9 +222,9 @@ gitServer hosts = propertyList "git.kitenet.net setup" $ props ] `describe` "gitweb configured" -- Repos push on to github. - & Ssh.knownHost hosts "github.com" "joey" + & Ssh.knownHost hosts "github.com" (User "joey") -- I keep the website used for gitweb checked into git.. - & Git.cloned "root" "/srv/git/joey/git.kitenet.net.git" "/srv/web/git.kitenet.net" Nothing + & Git.cloned (User "root") "/srv/git/joey/git.kitenet.net.git" "/srv/web/git.kitenet.net" Nothing & website "git.kitenet.net" & website "git.joeyh.name" & Apache.modEnabled "cgi" @@ -252,7 +252,7 @@ type AnnexUUID = String -- | A website, with files coming from a git-annex repository. annexWebSite :: Git.RepoUrl -> HostName -> AnnexUUID -> [(String, Git.RepoUrl)] -> Property HasInfo annexWebSite origin hn uuid remotes = propertyList (hn ++" website using git-annex") $ props - & Git.cloned "joey" origin dir Nothing + & Git.cloned (User "joey") origin dir Nothing `onChange` setup & alias hn & postupdatehook `File.hasContent` @@ -264,7 +264,7 @@ annexWebSite origin hn uuid remotes = propertyList (hn ++" website using git-ann where dir = "/srv/web/" ++ hn postupdatehook = dir ".git/hooks/post-update" - setup = userScriptProperty "joey" setupscript + setup = userScriptProperty (User "joey") setupscript setupscript = [ "cd " ++ shellEscape dir , "git annex reinit " ++ shellEscape uuid @@ -344,11 +344,11 @@ gitAnnexDistributor = combineProperties "git-annex distributor, including rsync & endpoint "/srv/web/downloads.kitenet.net/git-annex/autobuild/x86_64-apple-yosemite" & endpoint "/srv/web/downloads.kitenet.net/git-annex/autobuild/windows" -- git-annex distribution signing key - & Gpg.keyImported (Gpg.GpgKeyId "89C809CB") "joey" + & Gpg.keyImported (Gpg.GpgKeyId "89C809CB") (User "joey") where endpoint d = combineProperties ("endpoint " ++ d) [ File.dirExists d - , File.ownerGroup d "joey" "joey" + , File.ownerGroup d (User "joey") (Group "joey") ] downloads :: [Host] -> Property HasInfo @@ -356,7 +356,7 @@ downloads hosts = annexWebSite "/srv/git/downloads.git" "downloads.kitenet.net" "840760dc-08f0-11e2-8c61-576b7e66acfd" [("eubackup", "ssh://eubackup.kitenet.net/~/lib/downloads/")] - `requires` Ssh.knownHost hosts "eubackup.kitenet.net" "joey" + `requires` Ssh.knownHost hosts "eubackup.kitenet.net" (User "joey") tmp :: Property HasInfo tmp = propertyList "tmp.kitenet.net" $ props @@ -370,16 +370,16 @@ tmp = propertyList "tmp.kitenet.net" $ props -- Twitter, you kill us. twitRss :: Property HasInfo twitRss = combineProperties "twitter rss" $ props - & Git.cloned "joey" "git://git.kitenet.net/twitrss.git" dir Nothing + & Git.cloned (User "joey") "git://git.kitenet.net/twitrss.git" dir Nothing & check (not <$> doesFileExist (dir "twitRss")) compiled & feed "http://twitter.com/search/realtime?q=git-annex" "git-annex-twitter" & feed "http://twitter.com/search/realtime?q=olduse+OR+git-annex+OR+debhelper+OR+etckeeper+OR+ikiwiki+-ashley_ikiwiki" "twittergrep" where dir = "/srv/web/tmp.kitenet.net/twitrss" crontime = Cron.Times "15 * * * *" - feed url desc = Cron.job desc crontime "joey" dir $ + feed url desc = Cron.job desc crontime (User "joey") dir $ "./twitRss " ++ shellEscape url ++ " > " ++ shellEscape ("../" ++ desc ++ ".rss") - compiled = userScriptProperty "joey" + compiled = userScriptProperty (User "joey") [ "cd " ++ dir , "ghc --make twitRss" ] @@ -391,19 +391,19 @@ twitRss = combineProperties "twitter rss" $ props -- Work around for expired ssl cert. pumpRss :: Property NoInfo -pumpRss = Cron.job "pump rss" (Cron.Times "15 * * * *") "joey" "/srv/web/tmp.kitenet.net/" +pumpRss = Cron.job "pump rss" (Cron.Times "15 * * * *") (User "joey") "/srv/web/tmp.kitenet.net/" "wget https://pump2rss.com/feed/joeyh@identi.ca.atom -O pump.atom.new --no-check-certificate 2>/dev/null; sed 's/ & / /g' pump.atom.new > pump.atom" ircBouncer :: Property HasInfo ircBouncer = propertyList "IRC bouncer" $ props & Apt.installed ["znc"] - & User.accountFor "znc" + & User.accountFor (User "znc") & File.dirExists (takeDirectory conf) & File.hasPrivContent conf anyContext - & File.ownerGroup conf "znc" "znc" - & Cron.job "znconboot" (Cron.Times "@reboot") "znc" "~" "znc" + & File.ownerGroup conf (User "znc") (Group "znc") + & Cron.job "znconboot" (Cron.Times "@reboot") (User "znc") "~" "znc" -- ensure running if it was not already - & trivial (userScriptProperty "znc" ["znc || true"]) + & trivial (userScriptProperty (User "znc") ["znc || true"]) `describe` "znc running" where conf = "/home/znc/.znc/configs/znc.conf" @@ -425,9 +425,9 @@ githubBackup :: Property HasInfo githubBackup = propertyList "github-backup box" $ props & Apt.installed ["github-backup", "moreutils"] & githubKeys - & Cron.niceJob "github-backup run" (Cron.Times "30 4 * * *") "joey" + & Cron.niceJob "github-backup run" (Cron.Times "30 4 * * *") (User "joey") "/home/joey/lib/backup" backupcmd - & Cron.niceJob "gitriddance" (Cron.Times "30 4 * * *") "joey" + & Cron.niceJob "gitriddance" (Cron.Times "30 4 * * *") (User "joey") "/home/joey/lib/backup" gitriddancecmd where backupcmd = intercalate "&&" $ @@ -446,7 +446,7 @@ githubKeys :: Property HasInfo githubKeys = let f = "/home/joey/.github-keys" in File.hasPrivContent f anyContext - `onChange` File.ownerGroup f "joey" "joey" + `onChange` File.ownerGroup f (User "joey") (Group "joey") -- these repos are only mirrored on github, I don't want @@ -464,13 +464,13 @@ githubMirrors = rsyncNetBackup :: [Host] -> Property NoInfo rsyncNetBackup hosts = Cron.niceJob "rsync.net copied in daily" (Cron.Times "30 5 * * *") - "joey" "/home/joey/lib/backup" "mkdir -p rsync.net && rsync --delete -az 2318@usw-s002.rsync.net: rsync.net" - `requires` Ssh.knownHost hosts "usw-s002.rsync.net" "joey" + (User "joey") "/home/joey/lib/backup" "mkdir -p rsync.net && rsync --delete -az 2318@usw-s002.rsync.net: rsync.net" + `requires` Ssh.knownHost hosts "usw-s002.rsync.net" (User "joey") backupsBackedupFrom :: [Host] -> HostName -> FilePath -> Property NoInfo backupsBackedupFrom hosts srchost destdir = Cron.niceJob desc - (Cron.Times "@reboot") "joey" "/" cmd - `requires` Ssh.knownHost hosts srchost "joey" + (Cron.Times "@reboot") (User "joey") "/" cmd + `requires` Ssh.knownHost hosts srchost (User "joey") where desc = "backups copied from " ++ srchost ++ " on boot" cmd = "rsync -az --bwlimit=300K --partial --delete " ++ srchost ++ ":lib/backup/ " ++ destdir srchost @@ -483,11 +483,11 @@ obnamRepos rs = propertyList ("obnam repos for " ++ unwords rs) `requires` mkdir "/home/joey/lib" mkrepo r = mkdir ("/home/joey/lib/backup/" ++ r ++ ".obnam") mkdir d = File.dirExists d - `before` File.ownerGroup d "joey" "joey" + `before` File.ownerGroup d (User "joey") (Group "joey") podcatcher :: Property NoInfo podcatcher = Cron.niceJob "podcatcher run hourly" (Cron.Times "55 * * * *") - "joey" "/home/joey/lib/sound/podcasts" + (User "joey") "/home/joey/lib/sound/podcasts" "xargs git-annex importfeed -c annex.genmetadata=true < feeds; mr --quiet update" `requires` Apt.installed ["git-annex", "myrepos"] @@ -645,7 +645,7 @@ kiteMailServer = propertyList "kitenet.net mail server" $ props & File.hasPrivContent dovecotusers ctx `onChange` (dovecotusers `File.mode` combineModes [ownerReadMode, groupReadMode]) - & File.ownerGroup dovecotusers "root" "dovecot" + & File.ownerGroup dovecotusers (User "root") (Group "dovecot") & Apt.installed ["mutt", "bsd-mailx", "alpine"] @@ -713,7 +713,7 @@ dkimInstalled = go `onChange` Service.restarted "opendkim" & Apt.serviceInstalledRunning "opendkim" & File.dirExists "/etc/mail" & File.hasPrivContent "/etc/mail/dkim.key" (Context "kitenet.net") - & File.ownerGroup "/etc/mail/dkim.key" "opendkim" "opendkim" + & File.ownerGroup "/etc/mail/dkim.key" (User "opendkim") (Group "opendkim") & "/etc/default/opendkim" `File.containsLine` "SOCKET=\"inet:8891@localhost\"" & "/etc/opendkim.conf" `File.containsLines` diff --git a/src/Propellor/Property/Ssh.hs b/src/Propellor/Property/Ssh.hs index 1fbf92e..236016f 100644 --- a/src/Propellor/Property/Ssh.hs +++ b/src/Propellor/Property/Ssh.hs @@ -54,17 +54,17 @@ permitRootLogin = setSshdConfig "PermitRootLogin" passwordAuthentication :: Bool -> Property NoInfo passwordAuthentication = setSshdConfig "PasswordAuthentication" -dotDir :: UserName -> IO FilePath +dotDir :: User -> IO FilePath dotDir user = do h <- homedir user return $ h ".ssh" -dotFile :: FilePath -> UserName -> IO FilePath +dotFile :: FilePath -> User -> IO FilePath dotFile f user = do d <- dotDir user return $ d f -hasAuthorizedKeys :: UserName -> IO Bool +hasAuthorizedKeys :: User -> IO Bool hasAuthorizedKeys = go <=< dotFile "authorized_keys" where go f = not . null <$> catchDefaultIO "" (readFile f) @@ -151,19 +151,19 @@ getPubKey = asks (_sshPubKey . hostInfo) -- PrivData. -- -- If the user already has a private/public key, it is left unchanged. -keyImported :: IsContext c => SshKeyType -> UserName -> c -> Property HasInfo +keyImported :: IsContext c => SshKeyType -> User -> c -> Property HasInfo keyImported = keyImported' Nothing -- | A file can be speficied to write the key to somewhere other than -- usual. Allows a user to have multiple keys for different roles. -keyImported' :: IsContext c => Maybe FilePath -> SshKeyType -> UserName -> c -> Property HasInfo -keyImported' dest keytype user context = combineProperties desc - [ installkey (SshPubKey keytype user) (install writeFile ".pub") - , installkey (SshPrivKey keytype user) (install writeFileProtected "") +keyImported' :: IsContext c => Maybe FilePath -> SshKeyType -> User -> c -> Property HasInfo +keyImported' dest keytype user@(User u) context = combineProperties desc + [ installkey (SshPubKey keytype u) (install writeFile ".pub") + , installkey (SshPrivKey keytype u) (install writeFileProtected "") ] where desc = unwords $ catMaybes - [ Just user + [ Just u , Just "has ssh key" , dest , Just $ "(" ++ fromKeyType keytype ++ ")" @@ -178,13 +178,13 @@ keyImported' dest keytype user context = combineProperties desc [ property desc $ makeChange $ do createDirectoryIfMissing True (takeDirectory f) writer f key - , File.ownerGroup f user user - , File.ownerGroup (takeDirectory f) user user + , File.ownerGroup f user (userGroup user) + , File.ownerGroup (takeDirectory f) user (userGroup user) ] ) keyfile ext = case dest of Nothing -> do - home <- homeDirectory <$> getUserEntryForName user + home <- homeDirectory <$> getUserEntryForName u return $ home ".ssh" "id_" ++ fromKeyType keytype ++ ext Just f -> return $ f ++ ext @@ -196,19 +196,19 @@ fromKeyType SshEd25519 = "ed25519" -- | Puts some host's ssh public key(s), as set using 'pubKey' or 'hostKey' -- into the known_hosts file for a user. -knownHost :: [Host] -> HostName -> UserName -> Property NoInfo -knownHost hosts hn user = property desc $ +knownHost :: [Host] -> HostName -> User -> Property NoInfo +knownHost hosts hn user@(User u) = property desc $ go =<< fromHost hosts hn getPubKey where - desc = user ++ " knows ssh key for " ++ hn + desc = u ++ " knows ssh key for " ++ hn go (Just m) | not (M.null m) = do f <- liftIO $ dotFile "known_hosts" user ensureProperty $ combineProperties desc [ File.dirExists (takeDirectory f) , f `File.containsLines` (map (\k -> hn ++ " " ++ k) (M.elems m)) - , File.ownerGroup f user user - , File.ownerGroup (takeDirectory f) user user + , File.ownerGroup f user (userGroup user) + , File.ownerGroup (takeDirectory f) user (userGroup user) ] go _ = do warningMessage $ "no configred pubKey for " ++ hn @@ -217,32 +217,32 @@ knownHost hosts hn user = property desc $ -- | Makes a user have authorized_keys from the PrivData -- -- This removes any other lines from the file. -authorizedKeys :: IsContext c => UserName -> c -> Property HasInfo -authorizedKeys user context = withPrivData (SshAuthorizedKeys user) context $ \get -> - property (user ++ " has authorized_keys") $ get $ \v -> do +authorizedKeys :: IsContext c => User -> c -> Property HasInfo +authorizedKeys user@(User u) context = withPrivData (SshAuthorizedKeys u) context $ \get -> + property (u ++ " has authorized_keys") $ get $ \v -> do f <- liftIO $ dotFile "authorized_keys" user liftIO $ do createDirectoryIfMissing True (takeDirectory f) writeFileProtected f v ensureProperties - [ File.ownerGroup f user user - , File.ownerGroup (takeDirectory f) user user + [ File.ownerGroup f user (userGroup user) + , File.ownerGroup (takeDirectory f) user (userGroup user) ] -- | Ensures that a user's authorized_keys contains a line. -- Any other lines in the file are preserved as-is. -authorizedKey :: UserName -> String -> Property NoInfo -authorizedKey user l = property desc $ do +authorizedKey :: User -> String -> Property NoInfo +authorizedKey user@(User u) l = property desc $ do f <- liftIO $ dotFile "authorized_keys" user ensureProperty $ combineProperties desc [ f `File.containsLine` l `requires` File.dirExists (takeDirectory f) `onChange` File.mode f (combineModes [ownerWriteMode, ownerReadMode]) - , File.ownerGroup f user user - , File.ownerGroup (takeDirectory f) user user + , File.ownerGroup f user (userGroup user) + , File.ownerGroup (takeDirectory f) user (userGroup user) ] where - desc = user ++ " has autorized_keys" + desc = u ++ " has autorized_keys" -- | Makes the ssh server listen on a given port, in addition to any other -- ports it is configured to listen on. diff --git a/src/Propellor/Property/Sudo.hs b/src/Propellor/Property/Sudo.hs index c183a8a..0257f3f 100644 --- a/src/Propellor/Property/Sudo.hs +++ b/src/Propellor/Property/Sudo.hs @@ -9,8 +9,8 @@ import Propellor.Property.User -- | Allows a user to sudo. If the user has a password, sudo is configured -- to require it. If not, NOPASSWORD is enabled for the user. -enabledFor :: UserName -> Property NoInfo -enabledFor user = property desc go `requires` Apt.installed ["sudo"] +enabledFor :: User -> Property NoInfo +enabledFor user@(User u) = property desc go `requires` Apt.installed ["sudo"] where go = do locked <- liftIO $ isLockedPassword user @@ -18,8 +18,8 @@ enabledFor user = property desc go `requires` Apt.installed ["sudo"] fileProperty desc (modify locked . filter (wanted locked)) "/etc/sudoers" - desc = user ++ " is sudoer" - sudobaseline = user ++ " ALL=(ALL:ALL)" + desc = u ++ " is sudoer" + sudobaseline = u ++ " ALL=(ALL:ALL)" sudoline True = sudobaseline ++ " NOPASSWD:ALL" sudoline False = sudobaseline ++ " ALL" wanted locked l diff --git a/src/Propellor/Property/Tor.hs b/src/Propellor/Property/Tor.hs index 7a49082..3af4a70 100644 --- a/src/Propellor/Property/Tor.hs +++ b/src/Propellor/Property/Tor.hs @@ -52,7 +52,7 @@ named n = configured [("Nickname", n')] torPrivKey :: Context -> Property HasInfo torPrivKey context = f `File.hasPrivContent` context - `onChange` File.ownerGroup f user user + `onChange` File.ownerGroup f user (userGroup user) -- install tor first, so the directory exists with right perms `requires` Apt.installed ["tor"] where @@ -140,8 +140,8 @@ hiddenServiceData hn context = combineProperties desc writeFileProtected f content , File.mode (takeDirectory f) $ combineModes [ownerReadMode, ownerWriteMode, ownerExecuteMode] - , File.ownerGroup (takeDirectory f) user user - , File.ownerGroup f user user + , File.ownerGroup (takeDirectory f) user (userGroup user) + , File.ownerGroup f user (userGroup user) ] ) @@ -157,8 +157,8 @@ varLib = "/var/lib/tor" varRun :: FilePath varRun = "/var/run/tor" -user :: UserName -user = "debian-tor" +user :: User +user = User "debian-tor" type NickName = String diff --git a/src/Propellor/Property/User.hs b/src/Propellor/Property/User.hs index 557875f..add3ae5 100644 --- a/src/Propellor/Property/User.hs +++ b/src/Propellor/Property/User.hs @@ -7,31 +7,31 @@ import qualified Propellor.Property.File as File data Eep = YesReallyDeleteHome -accountFor :: UserName -> Property NoInfo -accountFor user = check (isNothing <$> catchMaybeIO (homedir user)) $ cmdProperty "adduser" +accountFor :: User -> Property NoInfo +accountFor user@(User u) = check (isNothing <$> catchMaybeIO (homedir user)) $ cmdProperty "adduser" [ "--disabled-password" , "--gecos", "" - , user + , u ] - `describe` ("account for " ++ user) + `describe` ("account for " ++ u) -- | Removes user home directory!! Use with caution. -nuked :: UserName -> Eep -> Property NoInfo -nuked user _ = check (isJust <$> catchMaybeIO (homedir user)) $ cmdProperty "userdel" +nuked :: User -> Eep -> Property NoInfo +nuked user@(User u) _ = check (isJust <$> catchMaybeIO (homedir user)) $ cmdProperty "userdel" [ "-r" - , user + , u ] - `describe` ("nuked user " ++ user) + `describe` ("nuked user " ++ u) -- | Only ensures that the user has some password set. It may or may -- not be a password from the PrivData. -hasSomePassword :: UserName -> Property HasInfo +hasSomePassword :: User -> Property HasInfo hasSomePassword user = hasSomePassword' user hostContext -- | While hasSomePassword uses the name of the host as context, -- this allows specifying a different context. This is useful when -- you want to use the same password on multiple hosts, for example. -hasSomePassword' :: IsContext c => UserName -> c -> Property HasInfo +hasSomePassword' :: IsContext c => User -> c -> Property HasInfo hasSomePassword' user context = check ((/= HasPassword) <$> getPasswordStatus user) $ hasPassword' user context @@ -41,18 +41,18 @@ hasSomePassword' user context = check ((/= HasPassword) <$> getPasswordStatus us -- A user's password can be stored in the PrivData in either of two forms; -- the full cleartext or a hash. The latter -- is obviously more secure. -hasPassword :: UserName -> Property HasInfo +hasPassword :: User -> Property HasInfo hasPassword user = hasPassword' user hostContext -hasPassword' :: IsContext c => UserName -> c -> Property HasInfo -hasPassword' user context = go `requires` shadowConfig True +hasPassword' :: IsContext c => User -> c -> Property HasInfo +hasPassword' (User u) context = go `requires` shadowConfig True where go = withSomePrivData srcs context $ - property (user ++ " has password") . setPassword + property (u ++ " has password") . setPassword srcs = - [ PrivDataSource (CryptPassword user) + [ PrivDataSource (CryptPassword u) "a crypt(3)ed password, which can be generated by, for example: perl -e 'print crypt(shift, q{$6$}.shift)' 'somepassword' 'somesalt'" - , PrivDataSource (Password user) ("a password for " ++ user) + , PrivDataSource (Password u) ("a password for " ++ u) ] setPassword :: (((PrivDataField, PrivData) -> Propellor Result) -> Propellor Result) -> Propellor Result @@ -67,32 +67,32 @@ setPassword getpassword = getpassword $ go hPutStrLn h $ user ++ ":" ++ v hClose h -lockedPassword :: UserName -> Property NoInfo -lockedPassword user = check (not <$> isLockedPassword user) $ cmdProperty "passwd" +lockedPassword :: User -> Property NoInfo +lockedPassword user@(User u) = check (not <$> isLockedPassword user) $ cmdProperty "passwd" [ "--lock" - , user + , u ] - `describe` ("locked " ++ user ++ " password") + `describe` ("locked " ++ u ++ " password") data PasswordStatus = NoPassword | LockedPassword | HasPassword deriving (Eq) -getPasswordStatus :: UserName -> IO PasswordStatus -getPasswordStatus user = parse . words <$> readProcess "passwd" ["-S", user] +getPasswordStatus :: User -> IO PasswordStatus +getPasswordStatus (User u) = parse . words <$> readProcess "passwd" ["-S", u] where parse (_:"L":_) = LockedPassword parse (_:"NP":_) = NoPassword parse (_:"P":_) = HasPassword parse _ = NoPassword -isLockedPassword :: UserName -> IO Bool +isLockedPassword :: User -> IO Bool isLockedPassword user = (== LockedPassword) <$> getPasswordStatus user -homedir :: UserName -> IO FilePath -homedir user = homeDirectory <$> getUserEntryForName user +homedir :: User -> IO FilePath +homedir (User user) = homeDirectory <$> getUserEntryForName user -hasGroup :: UserName -> GroupName -> Property NoInfo -hasGroup user group' = check test $ cmdProperty "adduser" +hasGroup :: User -> Group -> Property NoInfo +hasGroup (User user) (Group group') = check test $ cmdProperty "adduser" [ user , group' ] @@ -114,16 +114,16 @@ shadowExists = doesFileExist "/etc/shadow" -- | Ensures that a user has a specified login shell, and that the shell -- is enabled in /etc/shells. -hasLoginShell :: UserName -> FilePath -> Property NoInfo +hasLoginShell :: User -> FilePath -> Property NoInfo hasLoginShell user loginshell = shellSetTo user loginshell `requires` shellEnabled loginshell -shellSetTo :: UserName -> FilePath -> Property NoInfo -shellSetTo user loginshell = check needchangeshell $ - cmdProperty "chsh" ["--shell", loginshell, user] - `describe` (user ++ " has login shell " ++ loginshell) +shellSetTo :: User -> FilePath -> Property NoInfo +shellSetTo (User u) loginshell = check needchangeshell $ + cmdProperty "chsh" ["--shell", loginshell, u] + `describe` (u ++ " has login shell " ++ loginshell) where needchangeshell = do - currshell <- userShell <$> getUserEntryForName user + currshell <- userShell <$> getUserEntryForName u return (currshell /= loginshell) -- | Ensures that /etc/shells contains a shell. diff --git a/src/Propellor/Types/OS.hs b/src/Propellor/Types/OS.hs index 2bb4144..58bd809 100644 --- a/src/Propellor/Types/OS.hs +++ b/src/Propellor/Types/OS.hs @@ -1,20 +1,19 @@ module Propellor.Types.OS ( - HostName, - UserName, - GroupName, System(..), Distribution(..), DebianSuite(..), isStable, Release, Architecture, + HostName, + UserName, + User(..), + Group(..), + userGroup, ) where import Network.BSD (HostName) -type UserName = String -type GroupName = String - -- | High level description of a operating system. data System = System Distribution Architecture deriving (Show, Eq) @@ -35,3 +34,11 @@ isStable _ = False type Release = String type Architecture = String + +type UserName = String +newtype User = User UserName +newtype Group = Group String + +-- | Makes a Group with the same name as the User. +userGroup :: User -> Group +userGroup (User u) = Group u diff --git a/src/Propellor/Types/PrivData.hs b/src/Propellor/Types/PrivData.hs index 6b3c35a..d713c7c 100644 --- a/src/Propellor/Types/PrivData.hs +++ b/src/Propellor/Types/PrivData.hs @@ -2,8 +2,8 @@ module Propellor.Types.PrivData where import Propellor.Types.OS --- | Note that removing or changing constructors will break the --- serialized privdata files, so don't do that! +-- | Note that removing or changing constructors or changing types will +-- break the serialized privdata files, so don't do that! -- It's fine to add new constructors. data PrivDataField = DockerAuthentication