From d8624e2e5df9baf6883eb91b4be8282fb4b62304 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 23 Nov 2014 15:42:22 -0400 Subject: [PATCH 1/2] WIP --- src/Propellor/Property/OS.hs | 108 +++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/Propellor/Property/OS.hs diff --git a/src/Propellor/Property/OS.hs b/src/Propellor/Property/OS.hs new file mode 100644 index 0000000..914fb9f --- /dev/null +++ b/src/Propellor/Property/OS.hs @@ -0,0 +1,108 @@ +module Propellor.Property.OS ( + cleanInstallOnce, + Confirmation + confirm, + fixupNetworkAddresses, + fixupRootSsh, + oldOSRemoved, +) where + +import Propellor +import qualified Propellor.Property.Chroot as Chroot +import qualified Propellor.Property.Debootstrap as Debootstrap + +-- | Replaces whatever OS was installed before with a clean installation +-- of the OS that the Host is configured to have. +-- +-- This can replace one Linux distribution with different one. +-- But, it can also fail and leave the system in an unbootable state. +-- +-- This property only runs once. The cleanly installed system will have +-- a file /etc/propellor-cleaninstall, which indicates it was cleanly +-- installed. +-- +-- You will typically want to run some more properties after the clean +-- install, to bootstrap from the cleanly installed system to a fully +-- working system. For example: +-- +-- > & os (System (Debian Unstable) "amd64") +-- > & cleanInstall (confirm "com.example.foo") (BackupOldOS <> UseOldKernel) +-- > `onChange` propertyList "fixing up after clean install" +-- > [ fixupNetworkInterfaces +-- > , fixupRootSsh +-- > -- , installDistroKernel +-- > -- , installGrub +-- > ] +-- > & Apt.installed ["ssh"] +-- > -- rest of system properties here +cleanInstallOnce :: Confirmation -> Exceptions -> Property +cleanInstallOnce c = check (not <$> doesFileExist flagfile) $ + Property "OS cleanly installed" $ do + confirm c + error "TODO" + -- debootstrap /new-os chroot; avoid running + -- propellor inside the chroot yet + -- unmount all mounts + -- move all directories to /old-os, + -- except for /boot and /lib/modules + -- move /new-os to / + -- touch /etc/propellor-cleaninstall + -- re-bootstrap propellor in /usr/local/propellor, + -- (using git repo bundle, privdata file, and possibly + -- git repo url, which all need to be arranged to + -- be present in /old-os's /usr/local/propellor) + -- return MadeChange + where + flagfile = "/etc/propellor-cleaninstall" + +-- | To confirm you really intend to apply a dangerous Property to a +-- system, and have not copied and pasted it in by accident, you must +-- provide as confirmation, the hostname of the system you intend +-- to apply the Property to, written in the form form "com.example.somehost" +newtype Confirmation = Confirmation String + +confirm :: String -> Confirmation +confirm (Confirmation c) h + | h ==(intercalate "." $ reverse $ split "." c) = return () + | otherwise = error "Bad confirmation of dangerous Property; see the documentation to fix this." + +-- | Sometimes you want an almost clean install, but with some exceptions. +data Exceptions + = UseOldKernel -- ^ Leave /boot and /lib/modules from old OS, so the system can boot using them as before + | BackupOldOS -- ^ Back up old OS to /old-os, to avoid losing any important files + | NoExceptions + | CombinedExceptions Exceptions Exceptions + +instance Monoid Exceptions where + mempty = NoExceptions + mappend = CombinedExceptions + +-- /etc/network/interfaces is configured to bring up all interfaces that +-- are currently up, using the same IP addresses. +-- +-- This property only does anything if it comes after cleanInstall, +-- in the same propellor run where cleanInstall has made a change. +fixupNetworkInterfaces :: Property +fixupNetworkInterfaces = undefined + +-- /root/.ssh/authorized_keys is copied from the old os +fixupRootSsh :: Property +fixupRootSsh = undefined + +-- Installs an appropriate kernel from the distribution. +installDistroKernel :: Property +installDistroKernel = undefined + +-- Installs grub to boot the system. +installGrub :: Property +installGrub = undefined + +-- Removes the old OS's backup from /old-os +oldOSRemoved :: Property +oldOSRemoved = check (doesDirectoryExist oldOsDir) $ + Property "old OS backup removed" $ liftIO $ do + removeDirectoryRecursive oldOsDir + return MadeChange + +oldOsDir :: FilePath +oldOsDir = "/old-os" From baba668033f86b7c91f6b15c58002ea4bdbf3da2 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 23 Nov 2014 16:39:49 -0400 Subject: [PATCH 2/2] hasSomePassword and hasPassword now default to using the name of the host as the Context for the password. To specify a different context, use hasSomePassword' and hasPassword' (API change) --- config-joey.hs | 4 ++-- config-simple.hs | 2 +- debian/changelog | 5 ++++- propellor.cabal | 2 +- src/Propellor/Property/OS.hs | 35 +++++++++++++++++----------------- src/Propellor/Property/User.hs | 25 +++++++++++++++++++----- 6 files changed, 45 insertions(+), 28 deletions(-) diff --git a/config-joey.hs b/config-joey.hs index b6152f1..67986ff 100644 --- a/config-joey.hs +++ b/config-joey.hs @@ -366,9 +366,9 @@ standardSystemUnhardened hn suite arch motd = host hn & Apt.installed ["etckeeper"] & Apt.installed ["ssh"] & GitHome.installedFor "root" - & User.hasSomePassword "root" (Context hn) + & User.hasSomePassword "root" & User.accountFor "joey" - & User.hasSomePassword "joey" (Context hn) + & User.hasSomePassword "joey" & Sudo.enabledFor "joey" & GitHome.installedFor "joey" & Apt.installed ["vim", "screen", "less"] diff --git a/config-simple.hs b/config-simple.hs index fb02e27..c03149e 100644 --- a/config-simple.hs +++ b/config-simple.hs @@ -29,7 +29,7 @@ hosts = & Apt.unattendedUpgrades & Apt.installed ["etckeeper"] & Apt.installed ["ssh"] - & User.hasSomePassword "root" (Context "mybox.example.com") + & User.hasSomePassword "root" & Network.ipv6to4 & File.dirExists "/var/www" & Docker.docked webserverContainer diff --git a/debian/changelog b/debian/changelog index 6a1d60d..e70e6a9 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -propellor (1.0.1) UNRELEASED; urgency=medium +propellor (1.1.0) UNRELEASED; urgency=medium * propellor --spin can now deploy propellor to hosts that do not have git, ghc, or apt-get. This is accomplished by uploading a fairly @@ -12,6 +12,9 @@ propellor (1.0.1) UNRELEASED; urgency=medium find the full hostname. * Added group-related properties. Thanks, Félix Sipma. * Added Git.barerepo. Thanks, Félix Sipma. + * hasSomePassword and hasPassword now default to using the name of the + host as the Context for the password. To specify a different context, + use hasSomePassword' and hasPassword' (API change) -- Joey Hess Sat, 22 Nov 2014 00:12:35 -0400 diff --git a/propellor.cabal b/propellor.cabal index cd34d4b..617a1fc 100644 --- a/propellor.cabal +++ b/propellor.cabal @@ -1,5 +1,5 @@ Name: propellor -Version: 1.0.0 +Version: 1.1.0 Cabal-Version: >= 1.6 License: BSD3 Maintainer: Joey Hess diff --git a/src/Propellor/Property/OS.hs b/src/Propellor/Property/OS.hs index 914fb9f..c96e20b 100644 --- a/src/Propellor/Property/OS.hs +++ b/src/Propellor/Property/OS.hs @@ -17,6 +17,10 @@ import qualified Propellor.Property.Debootstrap as Debootstrap -- This can replace one Linux distribution with different one. -- But, it can also fail and leave the system in an unbootable state. -- +-- To avoid this property being accidentially used, you have to provide +-- a Context containing the name of the host that you intend to apply the +-- property to. +-- -- This property only runs once. The cleanly installed system will have -- a file /etc/propellor-cleaninstall, which indicates it was cleanly -- installed. @@ -26,7 +30,7 @@ import qualified Propellor.Property.Debootstrap as Debootstrap -- working system. For example: -- -- > & os (System (Debian Unstable) "amd64") --- > & cleanInstall (confirm "com.example.foo") (BackupOldOS <> UseOldKernel) +-- > & cleanInstall (Context "foo.example.com") (BackupOldOS <> UseOldKernel) -- > `onChange` propertyList "fixing up after clean install" -- > [ fixupNetworkInterfaces -- > , fixupRootSsh @@ -34,38 +38,33 @@ import qualified Propellor.Property.Debootstrap as Debootstrap -- > -- , installGrub -- > ] -- > & Apt.installed ["ssh"] +-- > & User.hasSomePassword "root" +-- > & User.accountFor "joey" +-- > & User.hasSomePassword "joey" -- > -- rest of system properties here -cleanInstallOnce :: Confirmation -> Exceptions -> Property -cleanInstallOnce c = check (not <$> doesFileExist flagfile) $ +cleanInstallOnce :: Context -> Exceptions -> Property +cleanInstallOnce (Context c) = check (not <$> doesFileExist flagfile) $ Property "OS cleanly installed" $ do - confirm c + hostname <- asks hostName + when (hostname /= c) $ + error "Run with bad context, not matching hostname. Not running cleanInstalOnce!" error "TODO" - -- debootstrap /new-os chroot; avoid running - -- propellor inside the chroot yet + -- debootstrap /new-os chroot, but don't run propellor + -- inside the chroot. -- unmount all mounts -- move all directories to /old-os, -- except for /boot and /lib/modules -- move /new-os to / - -- touch /etc/propellor-cleaninstall + -- touch flagfile -- re-bootstrap propellor in /usr/local/propellor, -- (using git repo bundle, privdata file, and possibly -- git repo url, which all need to be arranged to -- be present in /old-os's /usr/local/propellor) + -- enable shadow passwords (to avoid foot-shooting) -- return MadeChange where flagfile = "/etc/propellor-cleaninstall" --- | To confirm you really intend to apply a dangerous Property to a --- system, and have not copied and pasted it in by accident, you must --- provide as confirmation, the hostname of the system you intend --- to apply the Property to, written in the form form "com.example.somehost" -newtype Confirmation = Confirmation String - -confirm :: String -> Confirmation -confirm (Confirmation c) h - | h ==(intercalate "." $ reverse $ split "." c) = return () - | otherwise = error "Bad confirmation of dangerous Property; see the documentation to fix this." - -- | Sometimes you want an almost clean install, but with some exceptions. data Exceptions = UseOldKernel -- ^ Leave /boot and /lib/modules from old OS, so the system can boot using them as before diff --git a/src/Propellor/Property/User.hs b/src/Propellor/Property/User.hs index 6a51703..434a92a 100644 --- a/src/Propellor/Property/User.hs +++ b/src/Propellor/Property/User.hs @@ -24,12 +24,27 @@ nuked user _ = check (isJust <$> catchMaybeIO (homedir user)) $ cmdProperty "use -- | Only ensures that the user has some password set. It may or may -- not be the password from the PrivData. -hasSomePassword :: UserName -> Context -> Property -hasSomePassword user context = check ((/= HasPassword) <$> getPasswordStatus user) $ - hasPassword user context +hasSomePassword :: UserName -> Property +hasSomePassword user = property (user ++ "has password") $ do + hostname <- asks hostName + ensureProperty $ hasSomePassword' user (Context hostname) -hasPassword :: UserName -> Context -> Property -hasPassword user context = withPrivData (Password user) context $ \getpassword -> +-- | 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' :: UserName -> Context -> Property +hasSomePassword' user context = check ((/= HasPassword) <$> getPasswordStatus user) $ + hasPassword' user context + +-- | Ensures that a user's password is set to the password from the PrivData. +-- (Will change any existing password.) +hasPassword :: UserName -> Property +hasPassword user = property (user ++ "has password") $ do + hostname <- asks hostName + ensureProperty $ hasPassword' user (Context hostname) + +hasPassword' :: UserName -> Context -> Property +hasPassword' user context = withPrivData (Password user) context $ \getpassword -> property (user ++ " has password") $ getpassword $ \password -> makeChange $ withHandle StdinHandle createProcessSuccess