From 93b083f3a1204a7cf4452b5ebd589dd77d25dbac Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Fri, 1 Apr 2016 19:34:27 -0400 Subject: setup gpg key in initial setup process --- debian/changelog | 5 +- doc/README.mdwn | 19 ++---- doc/components.mdwn | 8 +-- doc/todo/commandline_to_setup_minimal_repo.mdwn | 2 + src/Propellor/Gpg.hs | 17 +++-- src/wrapper.hs | 88 ++++++++++++++++++++++--- 6 files changed, 104 insertions(+), 35 deletions(-) diff --git a/debian/changelog b/debian/changelog index 14d3f1a9..21c53bf8 100644 --- a/debian/changelog +++ b/debian/changelog @@ -65,8 +65,9 @@ propellor (3.0.0) UNRELEASED; urgency=medium * Added dependency on concurrent-output; removed embedded copy. * Apt.PPA: New module, contributed by Evan Cofsky. * Improved propellor's first run experience; the wrapper program will - now walk the user through setting up ~/.propellor with a choice between - a clone of propellor's git repository, or a minimal config. + now walk the user through setting up ~/.propellor, with a choice between + a clone of propellor's git repository, or a minimal config, and will + configure propellor to use a gpg key. -- Joey Hess Wed, 30 Mar 2016 15:45:08 -0400 diff --git a/doc/README.mdwn b/doc/README.mdwn index b17f8575..fc3c3fd1 100644 --- a/doc/README.mdwn +++ b/doc/README.mdwn @@ -44,18 +44,13 @@ see [configuration for the Haskell newbie](https://propellor.branchable.com/hask `apt-get install propellor` 2. Run `propellor` for the first time. It will set up a `~/.propellor/` git repository for you. -3. If you don't have a gpg private key already, generate one: `gpg --gen-key` -4. Run: `propellor --add-key $KEYID`, which will make propellor trust - your gpg key, and will sign your `~/.propellor` repository using it. -5. Edit `~/.propellor/config.hs`, and add a host you want to manage. +3. Edit `~/.propellor/config.hs`, and add a host you want to manage. You can start by not adding any properties, or only a few. -6. Run: `propellor --spin $HOST` -7. Now you have a simple propellor deployment, but it doesn't do - much to the host yet, besides installing propellor. - So, edit `~/.propellor/config.hs` to configure the host, add some - properties to it, and re-run step 6. - Repeat until happy and move on to the next host. :) -8. Once you have a lot of hosts, and running `propellor --spin HOST` for +4. Run: `propellor --spin $HOST` +5. Now you have a simple propellor deployment to a host. Continue editing + `~/.propellor/config.hs` to further configure the host, add more hosts + etc, and re-run `propellor --spin $HOST` after each change. +6. Once you have a lot of hosts, and running `propellor --spin HOST` for each host becomes tiresome, you can [automate that](http://propellor.branchable.com/automated_spins/). -9. Write some neat new properties and send patches! +7. Write some neat new properties and send patches! diff --git a/doc/components.mdwn b/doc/components.mdwn index 801bb6bf..5b47e106 100644 --- a/doc/components.mdwn +++ b/doc/components.mdwn @@ -28,12 +28,8 @@ then copy in `~/.propellor/src/Propellor/` and it will be used. See ## minimal .propellor repository All that really needs to be in `~/.propellor/` though, is a `config.hs` -file, and a cabal file. To use propellor this way, you can first -install propellor, and then copy the two files from the -[mininalconfig branch](http://source.propellor.branchable.com/?p=source.git;a=tree;h=refs/heads/minimalconfig;hb=refs/heads/minimalconfig), -or clone it: - - git clone git://propellor.branchable.com/ .propellor --branch minimalconfig --single-branch +file, and a cabal file. Running propellor when `~/.propellor/` doesn't exist +will ask you if you want a minimal config, and create those files. In this configuration, when propellor is deploying itself to a new host, it will automatically install the version of the propellor library diff --git a/doc/todo/commandline_to_setup_minimal_repo.mdwn b/doc/todo/commandline_to_setup_minimal_repo.mdwn index 5e82ed0f..2b41d370 100644 --- a/doc/todo/commandline_to_setup_minimal_repo.mdwn +++ b/doc/todo/commandline_to_setup_minimal_repo.mdwn @@ -3,3 +3,5 @@ parameters, like --minimal to clone the minimal config repo instead of the full one, or --stack to set up ~/.propellor to use stack. --[[Joey]] > Or, it could be an interactive setup process. --[[Joey]] + +>> Made it interactive. [[done]] --[[Joey]] diff --git a/src/Propellor/Gpg.hs b/src/Propellor/Gpg.hs index 55d89d29..4e6ceb79 100644 --- a/src/Propellor/Gpg.hs +++ b/src/Propellor/Gpg.hs @@ -32,14 +32,21 @@ getGpgBin = do -- Lists the keys in propellor's keyring. listPubKeys :: IO [KeyId] listPubKeys = do - gpgbin <- getGpgBin keyring <- privDataKeyring - parse . lines <$> readProcess gpgbin (listopts keyring) + map fst <$> listKeys ("--list-public-keys" : useKeyringOpts keyring) + +listSecretKeys :: IO [(KeyId, String)] +listSecretKeys = listKeys ["--list-secret-keys"] + +listKeys :: [String] -> IO [(KeyId, String)] +listKeys ps = do + gpgbin <- getGpgBin + parse . lines <$> readProcess gpgbin listopts where - listopts keyring = useKeyringOpts keyring ++ - ["--with-colons", "--list-public-keys"] + listopts = ps ++ ["--with-colons"] parse = mapMaybe (keyIdField . split ":") - keyIdField ("pub":_:_:_:f:_) = Just f + keyIdField (t:_:_:_:f:_:_:_:_:n:_) + | t == "pub" || t == "sec" = Just (f, n) keyIdField _ = Nothing useKeyringOpts :: FilePath -> [String] diff --git a/src/wrapper.hs b/src/wrapper.hs index 82251dc9..32e036da 100644 --- a/src/wrapper.hs +++ b/src/wrapper.hs @@ -3,8 +3,7 @@ -- Distributions should install this program into PATH. -- (Cabal builds it as dist/build/propellor/propellor). -- --- This is not the propellor main program (that's config.hs) --- +-- This is not the propellor main program (that's config.hs). -- This bootstraps ~/.propellor/config.hs, builds it if -- it's not already built, and runs it. @@ -13,13 +12,16 @@ module Main where import Propellor.Message import Propellor.Bootstrap import Propellor.Git +import Propellor.Gpg import Utility.UserInfo import Utility.Monad import Utility.Process import Utility.SafeCommand import Utility.Exception +import Utility.Path import Data.Char +import Data.List import Control.Monad import Control.Monad.IfElse import System.Directory @@ -97,14 +99,14 @@ welcomeBanner = putStr $ unlines $ map prettify | c == x = y | otherwise = c -prompt :: String -> [(Char, IO ())] -> IO () +prompt :: String -> [(String, IO ())] -> IO () prompt p cs = do - putStr (p ++ " [" ++ map fst cs ++ "] ") + putStr (p ++ " [" ++ intercalate "|" (map fst cs) ++ "] ") hFlush stdout r <- map toLower <$> getLine - if r == "\n" + if null r then snd (head cs) -- default to first choice on return - else case filter (\(c, a) -> [toLower c] == r) cs of + else case filter (\(s, _) -> map toLower s == r) cs of [(_, a)] -> a _ -> do putStrLn "Not a valid choice, try again.. (Or ctrl-c to quit)" @@ -125,23 +127,89 @@ setup dotpropellor = do putStrLn " A: A clone of propellor's git repository (most flexible)" putStrLn " B: The bare minimum files to use propellor (most simple)" prompt "Which would you prefer?" - [ ('A', fullClone dotpropellor), - ('B', minimalConfig dotpropellor) + [ ("A", fullClone dotpropellor) + , ("B", minimalConfig dotpropellor) ] putStrLn "Ok, ~/.propellor/config.hs is set up!" - + changeWorkingDirectory dotpropellor + section putStrLn "Let's try building the propellor configuration, to make sure it will work..." buildPropellor Nothing - putStrLn "Great! Propellor is set up and ready to use." + putStrLn "Great! Propellor is bootstrapped." + + section + putStrLn "Propellor uses gpg to encrypt private data about the systems it manages," + putStrLn "and to sign git commits." + gpg <- getGpgBin + ifM (inPath gpg) + ( setupGpgKey dotpropellor + , do + putStrLn "You don't seem to have gpg installed, so skipping setting it up." + explainManualSetupGpgKey + ) section + putStrLn "Everything is set up ..." putStrLn "Your next step is to edit ~/.propellor/config.hs," putStrLn "and run propellor again to try it out." putStrLn "" putStrLn "For docs, see https://propellor.branchable.com/" putStrLn "Enjoy propellor!" +explainManualSetupGpgKey :: IO () +explainManualSetupGpgKey = do + putStrLn "Propellor can still be used without gpg, but it won't be able to" + putStrLn "manage private data. You can set this up later:" + putStrLn " 1. gpg --gen-key" + putStrLn " 2. propellor --add-key (pass it the key ID generated in step 1)" + +setupGpgKey :: FilePath -> IO () +setupGpgKey dotpropellor = do + ks <- listSecretKeys + putStrLn "" + case ks of + [] -> makeGpgKey dotpropellor + [(k, _)] -> propellorAddKey dotpropellor k + _ -> do + let nks = zip ks (map show ([1..] :: [Integer])) + putStrLn "I see you have several gpg keys:" + forM_ nks $ \((k, d), n) -> + putStrLn $ " " ++ n ++ " " ++ d ++ " (keyid " ++ k ++ ")" + prompt "Which of your gpg keys should propellor use?" + (map (\((k, _), n) -> (n, propellorAddKey dotpropellor k)) nks) + +makeGpgKey :: FilePath -> IO () +makeGpgKey dotpropellor = do + putStrLn "You seem to not have any gpg secret keys." + prompt "Would you like to create one now?" + [("Y", rungpg), ("N", nope)] + where + nope = do + putStrLn "No problem." + explainManualSetupGpgKey + rungpg = do + putStrLn "Running gpg --gen-key ..." + gpg <- getGpgBin + void $ boolSystem gpg [Param "--gen-key"] + ks <- listSecretKeys + case ks of + [] -> do + putStrLn "Hmm, gpg seemed to not set up a secret key." + prompt "Want to try running gpg again?" + [("Y", rungpg), ("N", nope)] + ((k, _):_) -> propellorAddKey dotpropellor k + +propellorAddKey :: FilePath -> String -> IO () +propellorAddKey dotpropellor keyid = do + putStrLn "" + putStrLn $ "Telling propellor to use your gpg key by running: propellor --add-key " ++ keyid + unlessM (boolSystem propellorbin [Param "--add-key", Param keyid]) $ do + putStrLn "Oops, that didn't work! You can retry the same command later." + putStrLn "Continuing onward ..." + where + propellorbin = dotpropellor "propellor" + minimalConfig :: FilePath -> IO () minimalConfig dotpropellor = do createDirectoryIfMissing True dotpropellor -- cgit v1.2.3