diff --git a/config-joey.hs b/config-joey.hs index 1f8a021..f87db43 100644 --- a/config-joey.hs +++ b/config-joey.hs @@ -19,7 +19,6 @@ import qualified Propellor.Property.Dns as Dns import qualified Propellor.Property.OpenId as OpenId import qualified Propellor.Property.Docker as Docker import qualified Propellor.Property.Git as Git -import qualified Propellor.Property.Apache as Apache import qualified Propellor.Property.Postfix as Postfix import qualified Propellor.Property.Grub as Grub import qualified Propellor.Property.Obnam as Obnam @@ -27,7 +26,6 @@ import qualified Propellor.Property.Gpg as Gpg import qualified Propellor.Property.Systemd as Systemd import qualified Propellor.Property.Journald as Journald import qualified Propellor.Property.OS as OS -import qualified Propellor.Property.HostingProvider.DigitalOcean as DigitalOcean import qualified Propellor.Property.HostingProvider.CloudAtCost as CloudAtCost import qualified Propellor.Property.HostingProvider.Linode as Linode import qualified Propellor.Property.SiteSpecific.GitHome as GitHome @@ -47,6 +45,7 @@ hosts = -- (o) ` , kite , diatom , elephant + , beaver ] ++ monsters testvm :: Host @@ -86,7 +85,7 @@ clam = standardSystem "clam.kitenet.net" Unstable "amd64" & Ssh.randomHostKeys & Apt.unattendedUpgrades & Network.ipv6to4 - & Tor.isBridge + & Tor.named "kite1" Tor.isRelay' & Postfix.satellite & Docker.configured @@ -118,8 +117,8 @@ orca = standardSystem "orca.kitenet.net" Unstable "amd64" & Docker.docked (GitAnnexBuilder.standardAutoBuilderContainer dockerImage "amd64" 15 "2h") & Docker.docked (GitAnnexBuilder.standardAutoBuilderContainer dockerImage "i386" 45 "2h") & Docker.docked (GitAnnexBuilder.armelCompanionContainer dockerImage) - & Docker.docked (GitAnnexBuilder.armelAutoBuilderContainer dockerImage "1 3 * * *" "5h") - & Docker.docked (GitAnnexBuilder.androidAutoBuilderContainer dockerImage "1 1 * * *" "3h") + & Docker.docked (GitAnnexBuilder.armelAutoBuilderContainer dockerImage (Cron.Times "1 3 * * *") "5h") + & Docker.docked (GitAnnexBuilder.androidAutoBuilderContainer dockerImage (Cron.Times "1 1 * * *") "3h") & Docker.garbageCollected `period` Daily & Apt.buildDep ["git-annex"] `period` Daily @@ -128,7 +127,7 @@ orca = standardSystem "orca.kitenet.net" Unstable "amd64" -- with propellor. kite :: Host kite = standardSystemUnhardened "kite.kitenet.net" Testing "amd64" - [ "Welcome to the new kitenet.net server!" ] + [ "Welcome to kite!" ] & ipv4 "66.228.36.95" & ipv6 "2600:3c03::f03c:91ff:fe73:b0d2" & alias "kitenet.net" @@ -143,6 +142,7 @@ kite = standardSystemUnhardened "kite.kitenet.net" Testing "amd64" & Network.static "eth0" `requires` Network.cleanInterfacesFile & Apt.installed ["linux-image-amd64"] & Linode.chainPVGrub 5 + & Linode.mlocateEnabled & Apt.unattendedUpgrades & Systemd.installed & Systemd.persistentJournal @@ -150,7 +150,7 @@ kite = standardSystemUnhardened "kite.kitenet.net" Testing "amd64" & Ssh.passwordAuthentication True -- Since ssh password authentication is allowed: & Apt.serviceInstalledRunning "fail2ban" - & Obnam.backupEncrypted "/" "33 1 * * *" + & Obnam.backupEncrypted "/" (Cron.Times "33 1 * * *") [ "--repository=sftp://joey@eubackup.kitenet.net/~/lib/backup/kite.obnam" , "--client-name=kitenet.net" , "--exclude=/var/cache" @@ -171,12 +171,18 @@ kite = standardSystemUnhardened "kite.kitenet.net" Testing "amd64" & alias "mail.kitenet.net" & JoeySites.kiteMailServer - & alias "ns4.kitenet.net" - & myDnsSecondary - & alias "ns4.branchable.com" - & branchableSecondary - + & JoeySites.kitenetHttps & JoeySites.legacyWebSites + & File.ownerGroup "/srv/web" "joey" "joey" + & Apt.installed ["analog"] + + & alias "git.kitenet.net" + & alias "git.joeyh.name" + & JoeySites.gitServer hosts + + & JoeySites.downloads hosts + & JoeySites.gitAnnexDistributor + & JoeySites.tmp & alias "bitlbee.kitenet.net" & Apt.serviceInstalledRunning "bitlbee" @@ -201,73 +207,32 @@ kite = standardSystemUnhardened "kite.kitenet.net" Testing "amd64" & Docker.configured & Docker.garbageCollected `period` Daily - & Docker.docked oldusenetShellBox - -diatom :: Host -diatom = standardSystem "diatom.kitenet.net" (Stable "wheezy") "amd64" - [ "Important stuff that needs not too much memory or CPU." ] - & ipv4 "107.170.31.195" - & Ssh.hostKeys hostContext - [ (SshDsa, "ssh-dss AAAAB3NzaC1kc3MAAACBAO9tnPUT4p+9z7K6/OYuiBNHaij4Nzv5YVBih1vMl+ALz0gYAj8RWJzXmqp5buFAyfgOoLw+H9s1bBS01Sy3i07Dm6cx1fWG4RXL/E/3w1tavX99GD2bBxDBu890ebA5Tp+eFRJkS9+JwSvFiF6CP7NbVjifCagoUO56Ig048RwDAAAAFQDPY2xM3q6KwsVQliel23nrd0rV2QAAAIEAga3hj1hL00rYPNnAUzT8GAaSP62S4W68lusErH+KPbsMwFBFY/Ib1FVf8k6Zn6dZLh/HH/RtJi0JwdzPI1IFW+lwVbKfwBvhQ1lw9cH2rs1UIVgi7Wxdgfy8gEWxf+QIqn62wG+Ulf/HkWGvTrRpoJqlYRNS/gnOWj9Z/4s99koAAACBAM/uJIo2I0nK15wXiTYs/NYUZA7wcErugFn70TRbSgduIFH6U/CQa3rgHJw9DCPCQJLq7pwCnFH7too/qaK+czDk04PsgqV0+Jc7957gU5miPg50d60eJMctHV4eQ1FpwmGGfXxRBR9k2ZvikWYatYir3L6/x1ir7M0bA9IzNU45") - , (SshRsa, "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA2QAJEuvbTmaN9ex9i9bjPhMGj+PHUYq2keIiaIImJ+8mo+yKSaGUxebG4tpuDPx6KZjdycyJt74IXfn1voGUrfzwaEY9NkqOP3v6OWTC3QeUGqDCeJ2ipslbEd9Ep9XBp+/ldDQm60D0XsIZdmDeN6MrHSbKF4fXv1bqpUoUILk=") - ] - - & DigitalOcean.distroKernel - & Apt.unattendedUpgrades - & Apt.serviceInstalledRunning "ntp" - & Postfix.satellite - - -- Diatom has 500 mb of memory, so tune for that. - & JoeySites.obnamLowMem - & Apt.serviceInstalledRunning "swapspace" + ! Docker.docked oldusenetShellBox - & Apt.serviceInstalledRunning "apache2" - & JoeySites.kitenetHttps - & Apache.multiSSL - & File.ownerGroup "/srv/web" "joey" "joey" - & Apt.installed ["analog"] - - & alias "git.kitenet.net" - & alias "git.joeyh.name" - & JoeySites.gitServer hosts - - & JoeySites.annexWebSite "/srv/git/downloads.git" - "downloads.kitenet.net" - "840760dc-08f0-11e2-8c61-576b7e66acfd" - [("eubackup", "ssh://eubackup.kitenet.net/~/lib/downloads/")] - `requires` Ssh.keyImported SshRsa "joey" (Context "downloads.kitenet.net") - `requires` Ssh.knownHost hosts "eubackup.kitenet.net" "joey" - & JoeySites.gitAnnexDistributor - - & JoeySites.annexWebSite "/srv/git/joey/tmp.git" - "tmp.kitenet.net" - "26fd6e38-1226-11e2-a75f-ff007033bdba" - [] - & JoeySites.twitRss - & JoeySites.pumpRss - - & JoeySites.annexWebSite "/srv/git/user-liberation.git" - "user-liberation.joeyh.name" - "da89f112-808b-420a-b468-d990ae2e5b52" - [] - & alias "nntp.olduse.net" - & alias "resources.olduse.net" & JoeySites.oldUseNetServer hosts - & alias "ns2.kitenet.net" + & alias "ns4.kitenet.net" & myDnsPrimary True "kitenet.net" [] & myDnsPrimary True "joeyh.name" [] & myDnsPrimary True "ikiwiki.info" [] & myDnsPrimary True "olduse.net" [ (RelDomain "article", CNAME $ AbsDomain "virgil.koldfront.dk") ] - - & alias "ns3.branchable.com" + & alias "ns4.branchable.com" & branchableSecondary - & Dns.secondaryFor ["animx"] hosts "animx.eu.org" +diatom :: Host +diatom = standardSystem "diatom.kitenet.net" (Stable "wheezy") "amd64" + [ "dying" ] + & ipv4 "107.170.31.195" + & Ssh.hostKeys hostContext + [ (SshDsa, "ssh-dss AAAAB3NzaC1kc3MAAACBAO9tnPUT4p+9z7K6/OYuiBNHaij4Nzv5YVBih1vMl+ALz0gYAj8RWJzXmqp5buFAyfgOoLw+H9s1bBS01Sy3i07Dm6cx1fWG4RXL/E/3w1tavX99GD2bBxDBu890ebA5Tp+eFRJkS9+JwSvFiF6CP7NbVjifCagoUO56Ig048RwDAAAAFQDPY2xM3q6KwsVQliel23nrd0rV2QAAAIEAga3hj1hL00rYPNnAUzT8GAaSP62S4W68lusErH+KPbsMwFBFY/Ib1FVf8k6Zn6dZLh/HH/RtJi0JwdzPI1IFW+lwVbKfwBvhQ1lw9cH2rs1UIVgi7Wxdgfy8gEWxf+QIqn62wG+Ulf/HkWGvTrRpoJqlYRNS/gnOWj9Z/4s99koAAACBAM/uJIo2I0nK15wXiTYs/NYUZA7wcErugFn70TRbSgduIFH6U/CQa3rgHJw9DCPCQJLq7pwCnFH7too/qaK+czDk04PsgqV0+Jc7957gU5miPg50d60eJMctHV4eQ1FpwmGGfXxRBR9k2ZvikWYatYir3L6/x1ir7M0bA9IzNU45") + , (SshRsa, "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA2QAJEuvbTmaN9ex9i9bjPhMGj+PHUYq2keIiaIImJ+8mo+yKSaGUxebG4tpuDPx6KZjdycyJt74IXfn1voGUrfzwaEY9NkqOP3v6OWTC3QeUGqDCeJ2ipslbEd9Ep9XBp+/ldDQm60D0XsIZdmDeN6MrHSbKF4fXv1bqpUoUILk=") + ] + & alias "ns2.kitenet.net" + elephant :: Host elephant = standardSystem "elephant.kitenet.net" Unstable "amd64" [ "Storage, big data, and backups, omnomnom!" @@ -294,7 +259,6 @@ elephant = standardSystem "elephant.kitenet.net" Unstable "amd64" & JoeySites.obnamRepos ["wren", "pell", "kite"] & JoeySites.githubBackup & JoeySites.rsyncNetBackup hosts - & JoeySites.backupsBackedupTo hosts "usbackup.kitenet.net" "lib/backup/eubackup" & alias "podcatcher.kitenet.net" & JoeySites.podcatcher @@ -328,6 +292,18 @@ elephant = standardSystem "elephant.kitenet.net" Unstable "amd64" -- block 22. & Ssh.listenPort 80 +beaver :: Host +beaver = host "beaver.kitenet.net" + & ipv6 "2001:4830:1600:195::2" + & Apt.serviceInstalledRunning "aiccu" + & Apt.installed ["ssh"] + & Ssh.pubKey SshDsa "ssh-dss AAAAB3NzaC1kc3MAAACBAIrLX260fY0Jjj/p0syNhX8OyR8hcr6feDPGOj87bMad0k/w/taDSOzpXe0Wet7rvUTbxUjH+Q5wPd4R9zkaSDiR/tCb45OdG6JsaIkmqncwe8yrU+pqSRCxttwbcFe+UU+4AAcinjVedZjVRDj2rRaFPc9BXkPt7ffk8GwEJ31/AAAAFQCG/gOjObsr86vvldUZHCteaJttNQAAAIB5nomvcqOk/TD07DLaWKyG7gAcW5WnfY3WtnvLRAFk09aq1EuiJ6Yba99Zkb+bsxXv89FWjWDg/Z3Psa22JMyi0HEDVsOevy/1sEQ96AGH5ijLzFInfXAM7gaJKXASD7hPbVdjySbgRCdwu0dzmQWHtH+8i1CMVmA2/a5Y/wtlJAAAAIAUZj2US2D378jBwyX1Py7e4sJfea3WSGYZjn4DLlsLGsB88POuh32aOChd1yzF6r6C2sdoPBHQcWBgNGXcx4gF0B5UmyVHg3lIX2NVSG1ZmfuLNJs9iKNu4cHXUmqBbwFYQJBvB69EEtrOw4jSbiTKwHFmqdA/mw1VsMB+khUaVw==" + & 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" "/" + "rsync -a -x / /home/joey/lib/backup/beaver.kitenet.net/" + --' __|II| ,. ---- __|II|II|__ ( \_,/\ @@ -411,7 +387,7 @@ standardSystemUnhardened hn suite arch motd = host hn & Sudo.enabledFor "joey" & GitHome.installedFor "joey" & Apt.installed ["vim", "screen", "less"] - & Cron.runPropellor "30 * * * *" + & Cron.runPropellor (Cron.Times "30 * * * *") -- I use postfix, or no MTA. & Apt.removed ["exim4", "exim4-daemon-light", "exim4-config", "exim4-base"] `onChange` Apt.autoRemove @@ -447,15 +423,14 @@ myDnsSecondary = propertyList "dns secondary for all my domains" $ props branchableSecondary :: RevertableProperty branchableSecondary = Dns.secondaryFor ["branchable.com"] hosts "branchable.com" --- Currently using diatom (ns2) as primary with secondaries --- elephant (ns3), kite (ns4) and gandi. +-- Currently using kite (ns4) as primary with secondaries +-- elephant (ns3) and gandi. -- kite handles all mail. myDnsPrimary :: Bool -> Domain -> [(BindDomain, Record)] -> RevertableProperty myDnsPrimary dnssec domain extras = (if dnssec then Dns.signedPrimary (Weekly Nothing) else Dns.primary) hosts domain - (Dns.mkSOA "ns2.kitenet.net" 100) $ - [ (RootDomain, NS $ AbsDomain "ns2.kitenet.net") + (Dns.mkSOA "ns4.kitenet.net" 100) $ + [ (RootDomain, NS $ AbsDomain "ns4.kitenet.net") , (RootDomain, NS $ AbsDomain "ns3.kitenet.net") - , (RootDomain, NS $ AbsDomain "ns4.kitenet.net") , (RootDomain, NS $ AbsDomain "ns6.gandi.net") , (RootDomain, MX 0 $ AbsDomain "kitenet.net") , (RootDomain, TXT "v=spf1 a a:kitenet.net ~all") @@ -474,13 +449,8 @@ monsters = -- but do want to track their public keys etc. , host "turtle.kitenet.net" & ipv4 "67.223.19.96" & ipv6 "2001:4978:f:2d9::2" - & alias "backup.kitenet.net" - & alias "usbackup.kitenet.net" - & Ssh.pubKey SshRsa "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAokMXQiX/NZjA1UbhMdgAscnS5dsmy+Q7bWrQ6tsTZ/o+6N/T5cbjoBHOdpypXJI3y/PiJTDJaQtXIhLa8gFg/EvxMnMz/KG9skADW1361JmfCc4BxicQIO2IOOe6eilPr+YsnOwiHwL0vpUnuty39cppuMWVD25GzxXlS6KQsLCvXLzxLLuNnGC43UAM0q4UwQxDtAZEK1dH2o3HMWhgMP2qEQupc24dbhpO3ecxh2C9678a3oGDuDuNf7mLp3s7ptj5qF3onitpJ82U5o7VajaHoygMaSRFeWxP2c13eM57j3bLdLwxVXFhePcKXARu1iuFTLS5uUf3hN6MkQcOGw==" , host "mouse.kitenet.net" & ipv6 "2001:4830:1600:492::2" - , host "beaver.kitenet.net" - & ipv6 "2001:4830:1600:195::2" , host "branchable.com" & ipv4 "66.228.46.55" & ipv6 "2600:3c03::f03c:91ff:fedf:c0e5" diff --git a/debian/changelog b/debian/changelog index ce38be2..a8000c4 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,16 @@ +propellor (2.1.0) UNRELEASED; urgency=medium + + * Additional tor properties, including support for making relays, + and naming bridges, relays, etc. + * New Cron.Times data type, which allows Cron.job to install + daily/monthly/weekly jobs that anacron can run. (API change) + * Fix Git.daemonRunning to restart inetd after enabling the git server. + * Ssh.authorizedKey: Make the authorized_keys file and .ssh directory + be owned by the user, not root. + * Ssh.knownHost: Make the .ssh directory be owned by the user, not root. + + -- Joey Hess Thu, 29 Jan 2015 01:41:07 -0400 + propellor (2.0.0) unstable; urgency=medium * Property has been converted to a GADT, and will be Property NoInfo diff --git a/propellor.cabal b/propellor.cabal index 6f4dba2..41520ba 100644 --- a/propellor.cabal +++ b/propellor.cabal @@ -1,5 +1,5 @@ Name: propellor -Version: 2.0.0 +Version: 2.1.0 Cabal-Version: >= 1.6 License: BSD3 Maintainer: Joey Hess diff --git a/src/Propellor/PropAccum.hs b/src/Propellor/PropAccum.hs index 139f147..20d083c 100644 --- a/src/Propellor/PropAccum.hs +++ b/src/Propellor/PropAccum.hs @@ -1,6 +1,13 @@ {-# LANGUAGE PackageImports #-} -module Propellor.PropAccum where +module Propellor.PropAccum + ( host + , props + , PropAccum(..) + , (!) + , PropList + , propigateContainer + ) where import Data.Monoid @@ -47,9 +54,9 @@ instance PropAccum Host where data PropList = PropList [Property HasInfo] instance PropAccum PropList where - PropList l & p = PropList (l ++ [toProp p]) - PropList l &^ p = PropList ([toProp p] ++ l) - getProperties (PropList l) = l + PropList l & p = PropList (toProp p : l) + PropList l &^ p = PropList (l ++ [toProp p]) + getProperties (PropList l) = reverse l -- | Adds a property in reverted form. (!) :: PropAccum h => h -> RevertableProperty -> h diff --git a/src/Propellor/Property/Apache.hs b/src/Propellor/Property/Apache.hs index e598de1..a7c7e69 100644 --- a/src/Propellor/Property/Apache.hs +++ b/src/Propellor/Property/Apache.hs @@ -70,13 +70,17 @@ reloaded = Service.reloaded "apache2" -- | Configure apache to use SNI to differentiate between -- https hosts. +-- +-- This was off by default in apache 2.2.22. Newver versions enable +-- it by default. This property uses the filename used by the old version. multiSSL :: Property NoInfo -multiSSL = "/etc/apache2/conf.d/ssl" `File.hasContent` - [ "NameVirtualHost *:443" - , "SSLStrictSNIVHostCheck off" - ] - `describe` "apache SNI enabled" - `onChange` reloaded +multiSSL = check (doesDirectoryExist "/etc/apache2/conf.d") $ + "/etc/apache2/conf.d/ssl" `File.hasContent` + [ "NameVirtualHost *:443" + , "SSLStrictSNIVHostCheck off" + ] + `describe` "apache SNI enabled" + `onChange` reloaded -- | Config file fragment that can be inserted into a -- stanza to allow global read access to the directory. diff --git a/src/Propellor/Property/Cron.hs b/src/Propellor/Property/Cron.hs index 15cdd98..fd365c8 100644 --- a/src/Propellor/Property/Cron.hs +++ b/src/Propellor/Property/Cron.hs @@ -8,18 +8,26 @@ import Utility.FileMode import Data.Char -type CronTimes = String +-- | When to run a cron job. +-- +-- The Daily, Monthly, and Weekly options allow the cron job to be run +-- by anacron, which is useful for non-servers. +data Times + = Times String -- ^ formatted as in crontab(5) + | Daily + | Weekly + | Monthly --- | Installs a cron job, run as a specified user, in a particular --- directory. Note that the Desc must be unique, as it is used for the --- cron.d/ filename. +-- | Installs a cron job, that will run as a specified user in a particular +-- directory. Note that the Desc must be unique, as it is used for the +-- cron job filename. -- -- Only one instance of the cron job is allowed to run at a time, no matter -- how long it runs. This is accomplished using flock locking of the cron -- job file. -- -- The cron job's output will only be emailed if it exits nonzero. -job :: Desc -> CronTimes -> UserName -> FilePath -> String -> Property NoInfo +job :: Desc -> Times -> UserName -> FilePath -> String -> Property NoInfo job desc times user cddir command = combineProperties ("cronned " ++ desc) [ cronjobfile `File.hasContent` [ "# Generated by propellor" @@ -27,8 +35,15 @@ job desc times user cddir command = combineProperties ("cronned " ++ desc) , "SHELL=/bin/sh" , "PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin" , "" - , times ++ "\t" ++ user ++ "\tchronic " ++ shellEscape scriptfile + , case times of + Times t -> t ++ "\t" ++ user ++ "\tchronic " ++ shellEscape scriptfile + _ -> case user of + "root" -> "chronic " ++ shellEscape scriptfile + _ -> "chronic su " ++ user ++ " -c " ++ shellEscape scriptfile ] + , case times of + Times _ -> doNothing + _ -> cronjobfile `File.mode` combineModes (readModes ++ executeModes) -- Use a separate script because it makes the cron job name -- prettier in emails, and also allows running the job manually. , scriptfile `File.hasContent` @@ -44,7 +59,12 @@ job desc times user cddir command = combineProperties ("cronned " ++ desc) `requires` Apt.installed ["util-linux", "moreutils"] where cmdline = "cd " ++ cddir ++ " && ( " ++ command ++ " )" - cronjobfile = "/etc/cron.d/" ++ name + cronjobfile = "/etc" cronjobdir name + cronjobdir = case times of + Times _ -> "cron.d" + Daily -> "cron.daily" + Weekly -> "cron.weekly" + Monthly -> "cron.monthly" scriptfile = "/usr/local/bin/" ++ name ++ "_cronjob" name = map sanitize desc sanitize c @@ -52,10 +72,10 @@ job desc times user cddir command = combineProperties ("cronned " ++ desc) | otherwise = '_' -- | Installs a cron job, and runs it niced and ioniced. -niceJob :: Desc -> CronTimes -> UserName -> FilePath -> String -> Property NoInfo +niceJob :: Desc -> Times -> UserName -> 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 :: CronTimes -> Property NoInfo +runPropellor :: Times -> Property NoInfo runPropellor times = niceJob "propellor" times "root" localdir "./propellor" diff --git a/src/Propellor/Property/File.hs b/src/Propellor/Property/File.hs index 7167d61..12d9202 100644 --- a/src/Propellor/Property/File.hs +++ b/src/Propellor/Property/File.hs @@ -21,7 +21,7 @@ hasPrivContent :: IsContext c => FilePath -> c -> Property HasInfo hasPrivContent f = hasPrivContentFrom (PrivDataSourceFile (PrivFile f) f) f -- | Like hasPrivContent, but allows specifying a source --- for PrivData, rather than using PrivDataSourceFile. +-- for PrivData, rather than using PrivDataSourceFile . hasPrivContentFrom :: (IsContext c, IsPrivDataSource s) => s -> FilePath -> c -> Property HasInfo hasPrivContentFrom = hasPrivContent' writeFileProtected diff --git a/src/Propellor/Property/Git.hs b/src/Propellor/Property/Git.hs index c363d8c..91f1e3e 100644 --- a/src/Propellor/Property/Git.hs +++ b/src/Propellor/Property/Git.hs @@ -23,7 +23,7 @@ daemonRunning exportdir = setup unsetup `requires` Apt.serviceInstalledRunning "openbsd-inetd" `onChange` - Service.running "openbsd-inetd" + Service.reloaded "openbsd-inetd" `describe` ("git-daemon exporting " ++ exportdir) unsetup = lacksLine conf (mkl "tcp4") `requires` diff --git a/src/Propellor/Property/HostingProvider/Linode.hs b/src/Propellor/Property/HostingProvider/Linode.hs index 90f41bf..4dd6612 100644 --- a/src/Propellor/Property/HostingProvider/Linode.hs +++ b/src/Propellor/Property/HostingProvider/Linode.hs @@ -2,9 +2,18 @@ module Propellor.Property.HostingProvider.Linode where import Propellor import qualified Propellor.Property.Grub as Grub +import qualified Propellor.Property.File as File +import Utility.FileMode -- | Linode's pv-grub-x86_64 does not currently support booting recent -- Debian kernels compressed with xz. This sets up pv-grub chaing to enable -- it. chainPVGrub :: Grub.TimeoutSecs -> Property NoInfo chainPVGrub = Grub.chainPVGrub "hd0" "xen/xvda" + +-- | Linode disables mlocate's cron job's execute permissions, +-- presumably to avoid disk IO. This ensures it's executable. +mlocateEnabled :: Property NoInfo +mlocateEnabled = "/etc/cron.daily/mlocate" + `File.mode` combineModes (readModes ++ executeModes) + diff --git a/src/Propellor/Property/Obnam.hs b/src/Propellor/Property/Obnam.hs index adaf255..c066d9f 100644 --- a/src/Propellor/Property/Obnam.hs +++ b/src/Propellor/Property/Obnam.hs @@ -36,7 +36,7 @@ data NumClients = OnlyClient | MultipleClients -- > `requires` Ssh.keyImported SshRsa "root" (Context hostname) -- -- How awesome is that? -backup :: FilePath -> Cron.CronTimes -> [ObnamParam] -> NumClients -> Property NoInfo +backup :: FilePath -> Cron.Times -> [ObnamParam] -> NumClients -> Property NoInfo backup dir crontimes params numclients = backup' dir crontimes params numclients `requires` restored dir params @@ -46,7 +46,7 @@ backup dir crontimes params numclients = -- -- The gpg secret key will be automatically imported -- into root's keyring using Propellor.Property.Gpg.keyImported -backupEncrypted :: FilePath -> Cron.CronTimes -> [ObnamParam] -> NumClients -> Gpg.GpgKeyId -> Property HasInfo +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" @@ -54,7 +54,7 @@ backupEncrypted dir crontimes params numclients keyid = params' = ("--encrypt-with=" ++ Gpg.getGpgKeyId keyid) : params -- | Does a backup, but does not automatically restore. -backup' :: FilePath -> Cron.CronTimes -> [ObnamParam] -> NumClients -> Property NoInfo +backup' :: FilePath -> Cron.Times -> [ObnamParam] -> NumClients -> Property NoInfo backup' dir crontimes params numclients = cronjob `describe` desc where desc = dir ++ " backed up by obnam" diff --git a/src/Propellor/Property/Postfix.hs b/src/Propellor/Property/Postfix.hs index fbb1ea5..0abd783 100644 --- a/src/Propellor/Property/Postfix.hs +++ b/src/Propellor/Property/Postfix.hs @@ -4,8 +4,9 @@ module Propellor.Property.Postfix where import Propellor import qualified Propellor.Property.Apt as Apt -import Propellor.Property.File +import qualified Propellor.Property.File as File import qualified Propellor.Property.Service as Service +import qualified Propellor.Property.User as User import qualified Data.Map as M import Data.List @@ -103,7 +104,7 @@ mainCfIsSet name = do -- Note that multiline configurations that continue onto the next line -- are not currently supported. dedupMainCf :: Property NoInfo -dedupMainCf = fileProperty "postfix main.cf dedupped" dedupCf mainCfFile +dedupMainCf = File.fileProperty "postfix main.cf dedupped" dedupCf mainCfFile dedupCf :: [String] -> [String] dedupCf ls = @@ -125,3 +126,33 @@ dedupCf ls = dedup c kc ((Right (k, v)):rest) = case M.lookup k kc of Just n | n > 1 -> dedup c (M.insert k (n - 1) kc) rest _ -> dedup (fmt k v:c) kc rest + +-- | Installs saslauthd and configures it for postfix, authenticating +-- against PAM. +-- +-- Does not configure postfix to use it; eg smtpd_sasl_auth_enable = yes +-- needs to be set to enable use. See +-- https://wiki.debian.org/PostfixAndSASL +saslAuthdInstalled :: Property NoInfo +saslAuthdInstalled = setupdaemon + `requires` Service.running "saslauthd" + `requires` postfixgroup + `requires` dirperm + `requires` Apt.installed ["sasl2-bin"] + `requires` smtpdconf + where + setupdaemon = "/etc/default/saslauthd" `File.containsLines` + [ "START=yes" + , "OPTIONS=\"-c -m " ++ dir ++ "\"" + ] + `onChange` Service.restarted "saslauthd" + smtpdconf = "/etc/postfix/sasl/smtpd.conf" `File.containsLines` + [ "pwcheck_method: saslauthd" + , "mech_list: PLAIN LOGIN" + ] + dirperm = check (not <$> doesDirectoryExist dir) $ + cmdProperty "dpkg-statoverride" + [ "--add", "root", "sasl", "710", dir ] + postfixgroup = "postfix" `User.hasGroup` "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 7fc523f..102e6a1 100644 --- a/src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs +++ b/src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs @@ -9,7 +9,7 @@ import qualified Propellor.Property.Cron as Cron import qualified Propellor.Property.Ssh as Ssh import qualified Propellor.Property.File as File import qualified Propellor.Property.Docker as Docker -import Propellor.Property.Cron (CronTimes) +import Propellor.Property.Cron (Times) builduser :: UserName builduser = "builder" @@ -25,7 +25,7 @@ builddir = gitbuilderdir "build" type TimeOut = String -- eg, 5h -autobuilder :: Architecture -> CronTimes -> TimeOut -> Property HasInfo +autobuilder :: Architecture -> Times -> TimeOut -> Property HasInfo autobuilder arch crontimes timeout = combineProperties "gitannexbuilder" $ props & Apt.serviceInstalledRunning "cron" & Cron.niceJob "gitannexbuilder" crontimes builduser gitbuilderdir @@ -102,10 +102,10 @@ standardAutoBuilderContainer dockerImage arch buildminute timeout = Docker.conta & User.accountFor builduser & tree arch & buildDepsApt - & autobuilder arch (show buildminute ++ " * * * *") timeout + & autobuilder arch (Cron.Times $ show buildminute ++ " * * * *") timeout & Docker.tweaked -androidAutoBuilderContainer :: (System -> Docker.Image) -> Cron.CronTimes -> TimeOut -> Docker.Container +androidAutoBuilderContainer :: (System -> Docker.Image) -> Times -> TimeOut -> Docker.Container androidAutoBuilderContainer dockerImage crontimes timeout = androidContainer dockerImage "android-git-annex-builder" (tree "android") builddir & Apt.unattendedUpgrades @@ -166,7 +166,7 @@ armelCompanionContainer dockerImage = Docker.container "armel-git-annex-builder- & Ssh.authorizedKeys builduser (Context "armel-git-annex-builder") & Docker.tweaked -armelAutoBuilderContainer :: (System -> Docker.Image) -> Cron.CronTimes -> TimeOut -> Docker.Container +armelAutoBuilderContainer :: (System -> Docker.Image) -> Times -> TimeOut -> Docker.Container armelAutoBuilderContainer dockerImage crontimes timeout = Docker.container "armel-git-annex-builder" (dockerImage $ System (Debian Unstable) "armel") & os (System (Debian Testing) "armel") diff --git a/src/Propellor/Property/SiteSpecific/JoeySites.hs b/src/Propellor/Property/SiteSpecific/JoeySites.hs index 34a5f02..9644cb7 100644 --- a/src/Propellor/Property/SiteSpecific/JoeySites.hs +++ b/src/Propellor/Property/SiteSpecific/JoeySites.hs @@ -24,6 +24,7 @@ import Data.String.Utils oldUseNetServer :: [Host] -> Property HasInfo oldUseNetServer hosts = propertyList "olduse.net server" $ props + & Apt.installed ["leafnode"] & oldUseNetInstalled "oldusenet-server" & Obnam.latestVersion & oldUseNetBackup @@ -32,7 +33,6 @@ oldUseNetServer hosts = propertyList "olduse.net server" $ props removeDirectoryRecursive newsspool createSymbolicLink (datadir "news") newsspool ) - & Apt.installed ["leafnode"] & "/etc/news/leafnode/config" `File.hasContent` [ "# olduse.net configuration (deployed by propellor)" , "expire = 1000000" -- no expiry via texpire @@ -45,8 +45,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" "11 1 * * *" "news" newsspool expirecommand - & Cron.niceJob "oldusenet-uucp" "*/5 * * * *" "news" "/" uucpcommand + & Cron.niceJob "oldusenet-expire" (Cron.Times "11 1 * * *") "news" newsspool expirecommand + & Cron.niceJob "oldusenet-uucp" (Cron.Times "*/5 * * * *") "news" "/" uucpcommand & Apache.siteEnabled "nntp.olduse.net" nntpcfg where newsspool = "/var/spool/news" @@ -65,12 +65,14 @@ oldUseNetServer hosts = propertyList "olduse.net server" $ props , " " ] - oldUseNetBackup = Obnam.backup datadir "33 4 * * *" + oldUseNetBackup = Obnam.backup datadir (Cron.Times "33 4 * * *") [ "--repository=sftp://2318@usw-s002.rsync.net/~/olduse.net" , "--client-name=spool" + , "--ssh-key=" ++ keyfile ] Obnam.OnlyClient - `requires` Ssh.keyImported SshRsa "root" (Context "olduse.net") + `requires` Ssh.keyImported' (Just keyfile) SshRsa "root" (Context "olduse.net") `requires` Ssh.knownHost hosts "usw-s002.rsync.net" "root" + keyfile = "/root/.ssh/olduse.net.key" oldUseNetShellBox :: Property HasInfo oldUseNetShellBox = propertyList "olduse.net shellbox" $ props @@ -113,12 +115,12 @@ mumbleServer :: [Host] -> Property HasInfo mumbleServer hosts = combineProperties hn $ props & Apt.serviceInstalledRunning "mumble-server" & Obnam.latestVersion - & Obnam.backup "/var/lib/mumble-server" "55 5 * * *" - [ "--repository=sftp://joey@usbackup.kitenet.net/~/lib/backup/" ++ hn ++ ".obnam" + & Obnam.backup "/var/lib/mumble-server" (Cron.Times "55 5 * * *") + [ "--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 "usbackup.kitenet.net" "root" + `requires` Ssh.knownHost hosts "usw-s002.rsync.net" "root" & trivial (cmdProperty "chown" ["-R", "mumble-server:mumble-server", "/var/lib/mumble-server"]) where hn = "mumble.debian.net" @@ -129,8 +131,8 @@ obnamLowMem = combineProperties "obnam tuned for low memory use" , "/etc/obnam.conf" `File.containsLines` [ "[config]" , "# Suggested by liw to keep Obnam memory consumption down (at some speed cost)." - , "upload-queue-size = 128" - , "lru-size = 128" + , "upload-queue-size = 96" + , "lru-size = 96" ] ] @@ -138,20 +140,20 @@ obnamLowMem = combineProperties "obnam tuned for low memory use" gitServer :: [Host] -> Property HasInfo gitServer hosts = propertyList "git.kitenet.net setup" $ props & Obnam.latestVersion - & Obnam.backupEncrypted "/srv/git" "33 3 * * *" + & Obnam.backupEncrypted "/srv/git" (Cron.Times "33 3 * * *") [ "--repository=sftp://2318@usw-s002.rsync.net/~/git.kitenet.net" + , "--ssh-key=" ++ sshkey , "--client-name=wren" -- historical ] Obnam.OnlyClient (Gpg.GpgKeyId "1B169BE1") - `requires` Ssh.keyImported SshRsa "root" (Context "git.kitenet.net") + `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" & Apt.installed ["git", "rsync", "gitweb"] - -- backport avoids channel flooding on branch merge - & Apt.installedBackport ["kgb-client"] - -- backport supports ssh event notification - & Apt.installedBackport ["git-annex"] + & Apt.installed ["git-annex"] + & Apt.installed ["kgb-client"] & File.hasPrivContentExposed "/etc/kgb-bot/kgb-client.conf" anyContext + `requires` File.dirExists "/etc/kgb-bot/" & Git.daemonRunning "/srv/git" & "/etc/gitweb.conf" `File.containsLines` [ "$projectroot = '/srv/git';" @@ -168,6 +170,7 @@ gitServer hosts = propertyList "git.kitenet.net setup" $ props & website "git.joeyh.name" & Apache.modEnabled "cgi" where + sshkey = "/root/.ssh/git.kitenet.net.key" website hn = apacheSite hn True [ " DocumentRoot /srv/web/git.kitenet.net/" , " " @@ -175,6 +178,7 @@ gitServer hosts = propertyList "git.kitenet.net setup" $ props , " AllowOverride None" , " AddHandler cgi-script .cgi" , " DirectoryIndex index.cgi" + , Apache.allowAll , " " , "" , " ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/" @@ -204,7 +208,7 @@ annexWebSite origin hn uuid remotes = propertyList (hn ++" website using git-ann setup = userScriptProperty "joey" setupscript setupscript = [ "cd " ++ shellEscape dir - , "git config annex.uuid " ++ shellEscape uuid + , "git annex reinit " ++ shellEscape uuid ] ++ map addremote remotes ++ [ "git annex get" , "git update-server-info" @@ -217,14 +221,14 @@ annexWebSite origin hn uuid remotes = propertyList (hn ++" website using git-ann , " " , " Options FollowSymLinks" , " AllowOverride None" + , Apache.allowAll , " " , " " , " Options Indexes FollowSymLinks ExecCGI" , " AllowOverride None" , " AddHandler cgi-script .cgi" , " DirectoryIndex index.html index.cgi" - , " Order allow,deny" - , " allow from all" + , Apache.allowAll , " " ] @@ -252,8 +256,7 @@ apachecfg hn withssl middle , " " , " Options Indexes MultiViews" , " AllowOverride None" - , " Order allow,deny" - , " Allow from all" + , Apache.allowAll , " " , "" ] @@ -288,6 +291,22 @@ gitAnnexDistributor = combineProperties "git-annex distributor, including rsync , File.ownerGroup d "joey" "joey" ] +downloads :: [Host] -> Property HasInfo +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" + +tmp :: Property HasInfo +tmp = propertyList "tmp.kitenet.net" $ props + & annexWebSite "/srv/git/joey/tmp.git" + "tmp.kitenet.net" + "26fd6e38-1226-11e2-a75f-ff007033bdba" + [] + & twitRss + & pumpRss + -- Twitter, you kill us. twitRss :: Property HasInfo twitRss = combineProperties "twitter rss" $ props @@ -297,7 +316,7 @@ twitRss = combineProperties "twitter rss" $ props & 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 = "15 * * * *" + crontime = Cron.Times "15 * * * *" feed url desc = Cron.job desc crontime "joey" dir $ "./twitRss " ++ shellEscape url ++ " > " ++ shellEscape ("../" ++ desc ++ ".rss") compiled = userScriptProperty "joey" @@ -311,9 +330,8 @@ twitRss = combineProperties "twitter rss" $ props ] -- Work around for expired ssl cert. --- (no longer expired, TODO remove this and change urls) pumpRss :: Property NoInfo -pumpRss = Cron.job "pump rss" "15 * * * *" "joey" "/srv/web/tmp.kitenet.net/" +pumpRss = Cron.job "pump rss" (Cron.Times "15 * * * *") "joey" "/srv/web/tmp.kitenet.net/" "wget https://pump2rss.com/feed/joeyh@identi.ca.atom -O pump.atom --no-check-certificate 2>/dev/null" ircBouncer :: Property HasInfo @@ -323,7 +341,7 @@ ircBouncer = propertyList "IRC bouncer" $ props & File.dirExists (takeDirectory conf) & File.hasPrivContent conf anyContext & File.ownerGroup conf "znc" "znc" - & Cron.job "znconboot" "@reboot" "znc" "~" "znc" + & Cron.job "znconboot" (Cron.Times "@reboot") "znc" "~" "znc" -- ensure running if it was not already & trivial (userScriptProperty "znc" ["znc || true"]) `describe` "znc running" @@ -347,9 +365,9 @@ githubBackup :: Property HasInfo githubBackup = propertyList "github-backup box" $ props & Apt.installed ["github-backup", "moreutils"] & githubKeys - & Cron.niceJob "github-backup run" "30 4 * * *" "joey" + & Cron.niceJob "github-backup run" (Cron.Times "30 4 * * *") "joey" "/home/joey/lib/backup" backupcmd - & Cron.niceJob "gitriddance" "30 4 * * *" "joey" + & Cron.niceJob "gitriddance" (Cron.Times "30 4 * * *") "joey" "/home/joey/lib/backup" gitriddancecmd where backupcmd = intercalate "&&" $ @@ -385,17 +403,17 @@ githubMirrors = plzuseurl u = "please submit changes to " ++ u ++ " instead of using github pull requests" rsyncNetBackup :: [Host] -> Property NoInfo -rsyncNetBackup hosts = Cron.niceJob "rsync.net copied in daily" "30 5 * * *" +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" -backupsBackedupTo :: [Host] -> HostName -> FilePath -> Property NoInfo -backupsBackedupTo hosts desthost destdir = Cron.niceJob desc - "1 1 * * 3" "joey" "/" cmd - `requires` Ssh.knownHost hosts desthost "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" where - desc = "backups copied to " ++ desthost ++ " weekly" - cmd = "rsync -az --delete /home/joey/lib/backup " ++ desthost ++ ":" ++ destdir + desc = "backups copied from " ++ srchost ++ " on boot" + cmd = "rsync -az --bwlimit=300K --partial --delete " ++ srchost ++ ":lib/backup/ " ++ destdir srchost obnamRepos :: [String] -> Property NoInfo obnamRepos rs = propertyList ("obnam repos for " ++ unwords rs) @@ -408,7 +426,7 @@ obnamRepos rs = propertyList ("obnam repos for " ++ unwords rs) `before` File.ownerGroup d "joey" "joey" podcatcher :: Property NoInfo -podcatcher = Cron.niceJob "podcatcher run hourly" "55 * * * *" +podcatcher = Cron.niceJob "podcatcher run hourly" (Cron.Times "55 * * * *") "joey" "/home/joey/lib/sound/podcasts" "xargs git-annex importfeed -c annex.genmetadata=true < feeds; mr --quiet update" `requires` Apt.installed ["git-annex", "myrepos"] @@ -450,6 +468,8 @@ kiteMailServer = propertyList "kitenet.net mail server" $ props & dkimInstalled + & Postfix.saslAuthdInstalled + & Apt.installed ["maildrop"] & "/etc/maildroprc" `File.hasContent` [ "# Global maildrop filter file (deployed with propellor)" @@ -514,8 +534,13 @@ kiteMailServer = propertyList "kitenet.net mail server" $ props , "# Filter out client relay lines from headers." , "header_checks = pcre:$config_directory/obscure_client_relay.pcre" + , "# Password auth for relaying (used by errol)" + , "smtpd_sasl_auth_enable = yes" + , "smtpd_sasl_security_options = noanonymous" + , "smtpd_sasl_local_domain = kitenet.net" + , "# Enable postgrey." - , "smtpd_recipient_restrictions = permit_tls_clientcerts,permit_mynetworks,reject_unauth_destination,check_policy_service inet:127.0.0.1:10023" + , "smtpd_recipient_restrictions = permit_tls_clientcerts,permit_sasl_authenticated,,permit_mynetworks,reject_unauth_destination,check_policy_service inet:127.0.0.1:10023" , "# Enable spamass-milter, amavis-milter, opendkim" , "smtpd_milters = unix:/spamass/spamass.sock unix:amavis/amavis.sock inet:localhost:8891" diff --git a/src/Propellor/Property/Ssh.hs b/src/Propellor/Property/Ssh.hs index 9290ea1..f44688c 100644 --- a/src/Propellor/Property/Ssh.hs +++ b/src/Propellor/Property/Ssh.hs @@ -12,6 +12,7 @@ module Propellor.Property.Ssh ( pubKey, getPubKey, keyImported, + keyImported', knownHost, authorizedKeys, listenPort @@ -147,13 +148,25 @@ getPubKey = asks (_sshPubKey . hostInfo) -- | Sets up a user with a ssh private key and public key pair from the -- PrivData. +-- +-- If the user already has a private/public key, it is left unchanged. keyImported :: IsContext c => SshKeyType -> UserName -> c -> Property HasInfo -keyImported keytype user context = combineProperties desc +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 "") ] where - desc = user ++ " has ssh key (" ++ fromKeyType keytype ++ ")" + desc = unwords $ catMaybes + [ Just user + , Just "has ssh key" + , dest + , Just $ "(" ++ fromKeyType keytype ++ ")" + ] installkey p a = withPrivData p context $ \getkey -> property desc $ getkey a install writer ext key = do @@ -168,9 +181,11 @@ keyImported keytype user context = combineProperties desc , File.ownerGroup (takeDirectory f) user user ] ) - keyfile ext = do - home <- homeDirectory <$> getUserEntryForName user - return $ home ".ssh" "id_" ++ fromKeyType keytype ++ ext + keyfile ext = case dest of + Nothing -> do + home <- homeDirectory <$> getUserEntryForName user + return $ home ".ssh" "id_" ++ fromKeyType keytype ++ ext + Just f -> return $ f ++ ext fromKeyType :: SshKeyType -> String fromKeyType SshRsa = "rsa" @@ -178,7 +193,7 @@ fromKeyType SshDsa = "dsa" fromKeyType SshEcdsa = "ecdsa" fromKeyType SshEd25519 = "ed25519" --- | Puts some host's ssh public key(s), as set using 'pubKey', +-- | 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 $ @@ -192,6 +207,7 @@ knownHost hosts hn user = property desc $ , f `File.containsLines` (map (\k -> hn ++ " " ++ k) (M.elems m)) , File.ownerGroup f user user + , File.ownerGroup (takeDirectory f) user user ] go _ = do warningMessage $ "no configred pubKey for " ++ hn @@ -215,12 +231,17 @@ authorizedKeys user context = withPrivData (SshAuthorizedKeys user) context $ \g -- | 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 (user ++ " has autorized_keys line " ++ l) $ do +authorizedKey user l = property desc $ do f <- liftIO $ dotFile "authorized_keys" user - ensureProperty $ - f `File.containsLine` l + 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 + ] + where + desc = user ++ " has autorized_keys line " ++ l -- | 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/Tor.hs b/src/Propellor/Property/Tor.hs index 9a0fe47..8176e64 100644 --- a/src/Propellor/Property/Tor.hs +++ b/src/Propellor/Property/Tor.hs @@ -7,19 +7,78 @@ import qualified Propellor.Property.Service as Service import Utility.FileMode import System.Posix.Files +import Data.Char type HiddenServiceName = String +type NodeName = String + +-- | Sets up a tor bridge. (Not a relay or exit node.) +-- +-- Uses port 443 isBridge :: Property NoInfo -isBridge = setup `requires` Apt.installed ["tor"] +isBridge = isBridge' [] + +isBridge' :: [String] -> Property NoInfo +isBridge' extraconfig = server config `describe` "tor bridge" where - setup = mainConfig `File.hasContent` - [ "SocksPort 0" - , "ORPort 443" - , "BridgeRelay 1" + config = + [ "BridgeRelay 1" , "Exitpolicy reject *:*" - ] `onChange` restarted + , "ORPort 443" + ] ++ extraconfig + +-- | Sets up a tor relay. +-- +-- Uses port 443 +isRelay :: Property NoInfo +isRelay = isRelay' [] + +isRelay' :: [String] -> Property NoInfo +isRelay' extraconfig = server config + `describe` "tor relay" + where + config = + [ "BridgeRelay 0" + , "Exitpolicy reject *:*" + , "ORPort 443" + ] ++ extraconfig + +-- | Converts a property like isBridge' or isRelay' to be a named +-- node, with a known private key. +-- +-- This can be moved to a different IP without needing to wait to +-- accumulate trust. +-- +-- The base property can be used to start out and then upgraded to +-- a named property later. +named :: NodeName -> ([String] -> Property NoInfo) -> Property HasInfo +named n basep = p `describe` (getDesc p ++ " " ++ n) + where + p = basep ["Nickname " ++ saneNickname n] + `requires` torPrivKey (Context ("tor " ++ n)) + +-- | A tor server (bridge, relay, or exit) +-- Don't use if you just want to run tor for personal use. +server :: [String] -> Property NoInfo +server extraconfig = setup + `requires` Apt.installed ["tor", "ntp"] + `describe` "tor server" + where + setup = mainConfig `File.hasContent` config + `onChange` restarted + config = + [ "SocksPort 0" + ] ++ extraconfig + +torPrivKey :: Context -> Property HasInfo +torPrivKey context = f `File.hasPrivContent` context + `onChange` File.ownerGroup f user user + -- install tor first, so the directory exists with right perms + `requires` Apt.installed ["tor"] + where + f = "/var/lib/tor/keys/secret_id_key" hiddenServiceAvailable :: HiddenServiceName -> Int -> Property NoInfo hiddenServiceAvailable hn port = hiddenServiceHostName prop @@ -80,3 +139,14 @@ varRun = "/var/run/tor" user :: UserName user = "debian-tor" + +type NickName = String + +-- | Convert String to a valid tor NickName. +saneNickname :: String -> NickName +saneNickname s + | null n = "unnamed" + | otherwise = n + where + legal c = isNumber c || isAsciiUpper c || isAsciiLower c + n = take 19 $ filter legal s