summaryrefslogtreecommitdiff
path: root/src/Propellor
diff options
context:
space:
mode:
authorJoey Hess2014-12-14 15:24:10 -0400
committerJoey Hess2014-12-14 15:24:10 -0400
commit71723ca09f369ccf96462cef1e0200e1615677d1 (patch)
tree9519e6a0e1f2a2353df4ef836118bbf3bf96eef4 /src/Propellor
parent2e2438ae66490a2a00972be16e95f0d9cda2f9ea (diff)
support for crypted passwords in privdata
* Added CryptPassword to PrivDataField, for password hashes as produced by crypt(3). * User.hasPassword and User.hasSomePassword will now use either a CryptPassword or a Password from privdata, depending on which is set.
Diffstat (limited to 'src/Propellor')
-rw-r--r--src/Propellor/PrivData.hs39
-rw-r--r--src/Propellor/Property/User.hs29
-rw-r--r--src/Propellor/Types/PrivData.hs27
3 files changed, 78 insertions, 17 deletions
diff --git a/src/Propellor/PrivData.hs b/src/Propellor/PrivData.hs
index 06438515..b0228b46 100644
--- a/src/Propellor/PrivData.hs
+++ b/src/Propellor/PrivData.hs
@@ -53,18 +53,43 @@ withPrivData
-> c
-> (((PrivData -> Propellor Result) -> Propellor Result) -> Property)
-> Property
-withPrivData field c mkprop = addinfo $ mkprop $ \a ->
- maybe missing a =<< get
+withPrivData field = withPrivData' snd [field]
+
+-- Like withPrivData, but here any of a list of PrivDataFields can be used.
+withSomePrivData
+ :: IsContext c
+ => [PrivDataField]
+ -> c
+ -> ((((PrivDataField, PrivData) -> Propellor Result) -> Propellor Result) -> Property)
+ -> Property
+withSomePrivData = withPrivData' id
+
+withPrivData'
+ :: IsContext c
+ => ((PrivDataField, PrivData) -> v)
+ -> [PrivDataField]
+ -> c
+ -> (((v -> Propellor Result) -> Propellor Result) -> Property)
+ -> Property
+withPrivData' feed fieldlist c mkprop = addinfo $ mkprop $ \a ->
+ maybe missing (a . feed) =<< getM get fieldlist
where
- get = do
+ get field = do
context <- mkHostContext hc <$> asks hostName
- liftIO $ getLocalPrivData field context
+ maybe Nothing (\privdata -> Just (field, privdata))
+ <$> liftIO (getLocalPrivData field context)
missing = do
Context cname <- mkHostContext hc <$> asks hostName
- warningMessage $ "Missing privdata " ++ show field ++ " (for " ++ cname ++ ")"
- liftIO $ putStrLn $ "Fix this by running: propellor --set '" ++ show field ++ "' '" ++ cname ++ "'"
+ warningMessage $ "Missing privdata " ++ intercalate " or " fieldnames ++ " (for " ++ cname ++ ")"
+ liftIO $ putStrLn $ "Fix this by running:"
+ liftIO $ forM_ fieldlist $ \f -> do
+ putStrLn $ " propellor --set '" ++ show f ++ "' '" ++ cname ++ "'"
+ putStrLn $ " < ( " ++ howtoMkPrivDataField f ++ " )"
+ putStrLn ""
return FailedChange
- addinfo p = p { propertyInfo = propertyInfo p <> mempty { _privDataFields = S.singleton (field, hc) } }
+ addinfo p = p { propertyInfo = propertyInfo p <> mempty { _privDataFields = fieldset } }
+ fieldnames = map show fieldlist
+ fieldset = S.fromList $ zip fieldlist (repeat hc)
hc = asHostContext c
addPrivDataField :: (PrivDataField, HostContext) -> Property
diff --git a/src/Propellor/Property/User.hs b/src/Propellor/Property/User.hs
index 69794d84..549aa07f 100644
--- a/src/Propellor/Property/User.hs
+++ b/src/Propellor/Property/User.hs
@@ -23,7 +23,7 @@ nuked user _ = check (isJust <$> catchMaybeIO (homedir user)) $ cmdProperty "use
`describe` ("nuked user " ++ user)
-- | Only ensures that the user has some password set. It may or may
--- not be the password from the PrivData.
+-- not be a password from the PrivData.
hasSomePassword :: UserName -> Property
hasSomePassword user = hasSomePassword' user hostContext
@@ -34,22 +34,31 @@ hasSomePassword' :: IsContext c => UserName -> c -> 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.
+-- | Ensures that a user's password is set to a password from the PrivData.
-- (Will change any existing password.)
+--
+-- A user's password can be stored in the PrivData in either of two forms;
+-- the full cleartext <Password> or a <CryptPassword> hash. The latter
+-- is obviously more secure.
hasPassword :: UserName -> Property
hasPassword user = hasPassword' user hostContext
hasPassword' :: IsContext c => UserName -> c -> Property
hasPassword' user context = go `requires` shadowConfig True
where
- go = withPrivData (Password user) context $
- property (user ++ " has password") . setPassword user
-
-setPassword :: UserName -> ((PrivData -> Propellor Result) -> Propellor Result) -> Propellor Result
-setPassword user getpassword = getpassword $ \password -> makeChange $
- withHandle StdinHandle createProcessSuccess
- (proc "chpasswd" []) $ \h -> do
- hPutStrLn h $ user ++ ":" ++ password
+ go = withSomePrivData [CryptPassword user, Password user] context $
+ property (user ++ " has password") . setPassword
+
+setPassword :: (((PrivDataField, PrivData) -> Propellor Result) -> Propellor Result) -> Propellor Result
+setPassword getpassword = getpassword $ go
+ where
+ go (Password user, password) = set user password []
+ go (CryptPassword user, hash) = set user hash ["--encrypted"]
+ go (f, _) = error $ "Unexpected type of privdata: " ++ show f
+
+ set user v ps = makeChange $ withHandle StdinHandle createProcessSuccess
+ (proc "chpasswd" ps) $ \h -> do
+ hPutStrLn h $ user ++ ":" ++ v
hClose h
lockedPassword :: UserName -> Property
diff --git a/src/Propellor/Types/PrivData.hs b/src/Propellor/Types/PrivData.hs
index 80dad762..ab3e108a 100644
--- a/src/Propellor/Types/PrivData.hs
+++ b/src/Propellor/Types/PrivData.hs
@@ -11,10 +11,29 @@ data PrivDataField
| SshPrivKey SshKeyType UserName
| SshAuthorizedKeys UserName
| Password UserName
+ | CryptPassword UserName
| PrivFile FilePath
| GpgKey
deriving (Read, Show, Ord, Eq)
+-- | Explains how the user can generate a particular PrivDataField.
+howtoMkPrivDataField :: PrivDataField -> String
+howtoMkPrivDataField fld = case fld of
+ DockerAuthentication -> "/root/.dockercfg" `genbycmd` "docker login"
+ SshPubKey keytype _ -> forexample $
+ "sshkey.pub" `genbycmd` keygen keytype
+ SshPrivKey keytype _ -> forexample $
+ "sshkey" `genbycmd` keygen keytype
+ SshAuthorizedKeys _ -> forexample "~/.ssh/id_rsa.pub"
+ Password username -> "a password for " ++ username
+ CryptPassword _ -> "a crypt(3)ed password, which can be generated by, for example: perl -e 'print crypt(shift, q{$6$}.shift)' 'somepassword' 'somesalt'"
+ PrivFile f -> "file contents for " ++ f
+ GpgKey -> "Either a gpg public key, exported with gpg --export -a, or a gpg private key, exported with gpg --export-secret-key -a"
+ where
+ genbycmd f cmd = f ++ " generated by running `" ++ cmd ++ "`"
+ keygen keytype = "ssh-keygen -t " ++ sshKeyTypeParam keytype ++ " -f sshkey"
+ forexample s = "for example, " ++ s
+
-- | A context in which a PrivDataField is used.
--
-- Often this will be a domain name. For example,
@@ -63,3 +82,11 @@ type PrivData = String
data SshKeyType = SshRsa | SshDsa | SshEcdsa | SshEd25519
deriving (Read, Show, Ord, Eq)
+
+-- | Parameter that would be passed to ssh-keygen to generate key of this type
+sshKeyTypeParam :: SshKeyType -> String
+sshKeyTypeParam SshRsa = "RSA"
+sshKeyTypeParam SshDsa = "DSA"
+sshKeyTypeParam SshEcdsa = "ECDSA"
+sshKeyTypeParam SshEd25519 = "ED25519"
+