defer setting up ssh public key until after confirmation
This commit is contained in:
parent
c49bef1be8
commit
b6a91d7a4d
3 changed files with 74 additions and 71 deletions
|
@ -86,16 +86,15 @@ getAddSshR = sshConfigurator $ do
|
||||||
runFormGet $ renderBootstrap $ sshServerAForm u
|
runFormGet $ renderBootstrap $ sshServerAForm u
|
||||||
case result of
|
case result of
|
||||||
FormSuccess sshserver -> do
|
FormSuccess sshserver -> do
|
||||||
(status, sshserver', pubkey) <- liftIO $ testServer sshserver
|
(status, needspubkey) <- liftIO $ testServer sshserver
|
||||||
if usable status
|
if usable status
|
||||||
then lift $ redirect $ ConfirmSshR $
|
then lift $ redirect $ ConfirmSshR $
|
||||||
SshData
|
SshData
|
||||||
{ sshHostName = fromJust $ hostname sshserver'
|
{ sshHostName = fromJust $ hostname sshserver
|
||||||
, sshUserName = username sshserver'
|
, sshUserName = username sshserver
|
||||||
, sshDirectory = fromMaybe "" $ directory sshserver'
|
, sshDirectory = fromMaybe "" $ directory sshserver
|
||||||
-- use unmangled server for repo name
|
|
||||||
, sshRepoName = genSshRepoName sshserver
|
, sshRepoName = genSshRepoName sshserver
|
||||||
, pubKey = pubkey
|
, needsPubKey = needspubkey
|
||||||
, rsyncOnly = (status == UsableRsyncServer)
|
, rsyncOnly = (status == UsableRsyncServer)
|
||||||
}
|
}
|
||||||
else showform form enctype status
|
else showform form enctype status
|
||||||
|
@ -110,28 +109,24 @@ getAddSshR = sshConfigurator $ do
|
||||||
- Two probe attempts are made. First, try sshing in using the existing
|
- Two probe attempts are made. First, try sshing in using the existing
|
||||||
- configuration, but don't let ssh prompt for any password. If
|
- configuration, but don't let ssh prompt for any password. If
|
||||||
- passwordless login is already enabled, use it. Otherwise,
|
- passwordless login is already enabled, use it. Otherwise,
|
||||||
- a special ssh key is generated just for this server.
|
- a special ssh key will need to be generated just for this server.
|
||||||
-
|
-
|
||||||
- Once logged into the server, probe to see if git-annex-shell is
|
- Once logged into the server, probe to see if git-annex-shell is
|
||||||
- available, or rsync.
|
- available, or rsync.
|
||||||
-}
|
-}
|
||||||
testServer :: SshServer -> IO (ServerStatus, SshServer, Maybe PubKey)
|
testServer :: SshServer -> IO (ServerStatus, Bool)
|
||||||
testServer sshserver@(SshServer { hostname = Nothing }) = return
|
testServer (SshServer { hostname = Nothing }) = return
|
||||||
(UnusableServer "Please enter a host name.", sshserver, Nothing)
|
(UnusableServer "Please enter a host name.", False)
|
||||||
testServer sshserver = do
|
testServer sshserver = do
|
||||||
home <- myHomeDir
|
status <- probe sshserver [sshopt "NumberOfPasswordPrompts" "0"]
|
||||||
let sshdir = home </> ".ssh"
|
|
||||||
status <- probe sshdir sshserver [sshopt "NumberOfPasswordPrompts" "0"]
|
|
||||||
if usable status
|
if usable status
|
||||||
then return (status, sshserver, Nothing)
|
then return (status, False)
|
||||||
else do
|
else do
|
||||||
(pubkey, sshserver') <- genSshKey sshdir sshserver
|
status' <- probe sshserver []
|
||||||
status' <- probe sshdir sshserver' []
|
return (status', True)
|
||||||
return (status', sshserver', Just pubkey)
|
|
||||||
where
|
where
|
||||||
probe sshdir s extraopts = do
|
probe s extraopts = do
|
||||||
{- This checks the unmangled server name in sshserver. -}
|
knownhost <- knownHost sshserver
|
||||||
knownhost <- knownHost sshdir sshserver
|
|
||||||
let remotecommand = join ";" $
|
let remotecommand = join ";" $
|
||||||
[ report "loggedin"
|
[ report "loggedin"
|
||||||
, checkcommand "git-annex-shell"
|
, checkcommand "git-annex-shell"
|
||||||
|
@ -162,6 +157,11 @@ testServer sshserver = do
|
||||||
report r = "echo " ++ token r
|
report r = "echo " ++ token r
|
||||||
sshopt k v = concat ["-o", k, "=", v]
|
sshopt k v = concat ["-o", k, "=", v]
|
||||||
|
|
||||||
|
sshDir :: IO FilePath
|
||||||
|
sshDir = do
|
||||||
|
home <- myHomeDir
|
||||||
|
return $ home </> ".ssh"
|
||||||
|
|
||||||
{- user@host or host -}
|
{- user@host or host -}
|
||||||
genSshHost :: Text -> Maybe Text -> String
|
genSshHost :: Text -> Maybe Text -> String
|
||||||
genSshHost host user = maybe "" (\v -> T.unpack v ++ "@") user ++ T.unpack host
|
genSshHost host user = maybe "" (\v -> T.unpack v ++ "@") user ++ T.unpack host
|
||||||
|
@ -189,41 +189,11 @@ sshTranscript opts = do
|
||||||
ok <- checkSuccessProcess pid
|
ok <- checkSuccessProcess pid
|
||||||
return (transcript, ok)
|
return (transcript, ok)
|
||||||
|
|
||||||
{- Returns the public key content, and SshServer with a mangled hostname
|
|
||||||
- to use that will enable use of the key. This way we avoid changing the
|
|
||||||
- user's regular ssh experience at all. -}
|
|
||||||
genSshKey :: FilePath -> SshServer -> IO (PubKey, SshServer)
|
|
||||||
genSshKey _ (SshServer { hostname = Nothing }) = undefined
|
|
||||||
genSshKey sshdir sshserver@(SshServer { hostname = Just h }) = do
|
|
||||||
createDirectoryIfMissing True sshdir
|
|
||||||
unlessM (doesFileExist $ sshdir </> sshprivkeyfile) $
|
|
||||||
unlessM genkey $
|
|
||||||
error "ssh-keygen failed"
|
|
||||||
unlessM (catchBoolIO $ isInfixOf mangledhost <$> readFile configfile) $
|
|
||||||
appendFile configfile $ unlines
|
|
||||||
[ ""
|
|
||||||
, "# Added automatically by git-annex"
|
|
||||||
, "Host " ++ mangledhost
|
|
||||||
, "\tHostname " ++ T.unpack h
|
|
||||||
, "\tIdentityFile ~/.ssh/" ++ sshprivkeyfile
|
|
||||||
]
|
|
||||||
pubkey <- readFile $ sshdir </> sshpubkeyfile
|
|
||||||
return (pubkey, sshserver { hostname = Just $ T.pack mangledhost })
|
|
||||||
where
|
|
||||||
configfile = sshdir </> "config"
|
|
||||||
sshprivkeyfile = "key." ++ mangledhost
|
|
||||||
sshpubkeyfile = sshprivkeyfile ++ ".pub"
|
|
||||||
mangledhost = "git-annex-" ++ T.unpack h ++ user
|
|
||||||
user = maybe "" (\u -> "-" ++ T.unpack u) (username sshserver)
|
|
||||||
genkey = boolSystem "ssh-keygen"
|
|
||||||
[ Param "-P", Param "" -- no password
|
|
||||||
, Param "-f", File $ sshdir </> sshprivkeyfile
|
|
||||||
]
|
|
||||||
|
|
||||||
{- Does ssh have known_hosts data for a hostname? -}
|
{- Does ssh have known_hosts data for a hostname? -}
|
||||||
knownHost :: FilePath -> SshServer -> IO Bool
|
knownHost :: SshServer -> IO Bool
|
||||||
knownHost _ (SshServer { hostname = Nothing }) = return False
|
knownHost (SshServer { hostname = Nothing }) = return False
|
||||||
knownHost sshdir (SshServer { hostname = Just h }) =
|
knownHost (SshServer { hostname = Just h }) = do
|
||||||
|
sshdir <- sshDir
|
||||||
ifM (doesFileExist $ sshdir </> "known_hosts")
|
ifM (doesFileExist $ sshdir </> "known_hosts")
|
||||||
( not . null <$> readProcess "ssh-keygen" ["-F", T.unpack h]
|
( not . null <$> readProcess "ssh-keygen" ["-F", T.unpack h]
|
||||||
, return False
|
, return False
|
||||||
|
@ -232,7 +202,6 @@ knownHost sshdir (SshServer { hostname = Just h }) =
|
||||||
getConfirmSshR :: SshData -> Handler RepHtml
|
getConfirmSshR :: SshData -> Handler RepHtml
|
||||||
getConfirmSshR sshdata = sshConfigurator $ do
|
getConfirmSshR sshdata = sshConfigurator $ do
|
||||||
let authtoken = webAppFormAuthToken
|
let authtoken = webAppFormAuthToken
|
||||||
let haspubkey = isJust $ pubKey sshdata
|
|
||||||
$(widgetFile "configurators/confirmssh")
|
$(widgetFile "configurators/confirmssh")
|
||||||
|
|
||||||
getMakeSshGitR :: SshData -> Handler RepHtml
|
getMakeSshGitR :: SshData -> Handler RepHtml
|
||||||
|
@ -242,7 +211,14 @@ getMakeSshRsyncR :: SshData -> Handler RepHtml
|
||||||
getMakeSshRsyncR = makeSsh True
|
getMakeSshRsyncR = makeSsh True
|
||||||
|
|
||||||
makeSsh :: Bool -> SshData -> Handler RepHtml
|
makeSsh :: Bool -> SshData -> Handler RepHtml
|
||||||
makeSsh rsync sshdata = do
|
makeSsh rsync sshdata
|
||||||
|
| needsPubKey sshdata = do
|
||||||
|
(pubkey, sshdata') <- liftIO $ genSshKey sshdata
|
||||||
|
makeSsh' rsync sshdata' (Just pubkey)
|
||||||
|
| otherwise = makeSsh' rsync sshdata Nothing
|
||||||
|
|
||||||
|
makeSsh' :: Bool -> SshData -> Maybe String -> Handler RepHtml
|
||||||
|
makeSsh' rsync sshdata pubkey = do
|
||||||
(transcript, ok) <- liftIO $ sshTranscript [sshhost, remoteCommand]
|
(transcript, ok) <- liftIO $ sshTranscript [sshhost, remoteCommand]
|
||||||
if ok
|
if ok
|
||||||
then do
|
then do
|
||||||
|
@ -258,7 +234,7 @@ makeSsh rsync sshdata = do
|
||||||
, Just $ "cd " ++ shellEscape remotedir
|
, Just $ "cd " ++ shellEscape remotedir
|
||||||
, if rsync then Nothing else Just $ "git init --bare --shared"
|
, if rsync then Nothing else Just $ "git init --bare --shared"
|
||||||
, if rsync then Nothing else Just $ "git annex init"
|
, if rsync then Nothing else Just $ "git annex init"
|
||||||
, makeAuthorizedKeys sshdata
|
, maybe Nothing (makeAuthorizedKeys sshdata) pubkey
|
||||||
]
|
]
|
||||||
showerr msg = sshConfigurator $
|
showerr msg = sshConfigurator $
|
||||||
$(widgetFile "configurators/makessherror")
|
$(widgetFile "configurators/makessherror")
|
||||||
|
@ -291,27 +267,56 @@ makeRsyncRemote name location = makeRemote name location $ const $ do
|
||||||
, ("type", "rsync")
|
, ("type", "rsync")
|
||||||
]
|
]
|
||||||
|
|
||||||
makeAuthorizedKeys :: SshData -> Maybe String
|
makeAuthorizedKeys :: SshData -> String -> Maybe String
|
||||||
makeAuthorizedKeys sshdata
|
makeAuthorizedKeys sshdata pubkey
|
||||||
| pubKey sshdata == Nothing = Nothing
|
| needsPubKey sshdata = Just $ join "&&" $
|
||||||
| otherwise = Just $ join "&&" $
|
|
||||||
[ "mkdir -p ~/.ssh"
|
[ "mkdir -p ~/.ssh"
|
||||||
, "touch ~/.ssh/authorized_keys"
|
, "touch ~/.ssh/authorized_keys"
|
||||||
, "chmod 600 ~/.ssh/authorized_keys"
|
, "chmod 600 ~/.ssh/authorized_keys"
|
||||||
, unwords
|
, unwords
|
||||||
[ "echo"
|
[ "echo"
|
||||||
, shellEscape $ authorizedKeysLine sshdata
|
, shellEscape $ authorizedKeysLine sshdata pubkey
|
||||||
, ">>~/.ssh/authorized_keys"
|
, ">>~/.ssh/authorized_keys"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
| otherwise = Nothing
|
||||||
|
|
||||||
authorizedKeysLine :: SshData -> String
|
authorizedKeysLine :: SshData -> String -> String
|
||||||
authorizedKeysLine sshdata@(SshData { pubKey = Just pubkey })
|
authorizedKeysLine sshdata pubkey
|
||||||
{- TODO: Locking down rsync is difficult, requiring a rather
|
{- TODO: Locking down rsync is difficult, requiring a rather
|
||||||
- long perl script. -}
|
- long perl script. -}
|
||||||
| rsyncOnly sshdata = pubkey
|
| rsyncOnly sshdata = pubkey
|
||||||
| otherwise = limitcommand "git-annex-shell -c" ++ pubkey
|
| otherwise = limitcommand "git-annex-shell -c" ++ pubkey
|
||||||
where
|
where
|
||||||
limitcommand c = "command=\"perl -e 'exec qw(" ++ c ++ "), $ENV{SSH_ORIGINAL_COMMAND}'\",no-agent-forwarding,no-port-forwarding,no-X11-forwarding "
|
limitcommand c = "command=\"perl -e 'exec qw(" ++ c ++ "), $ENV{SSH_ORIGINAL_COMMAND}'\",no-agent-forwarding,no-port-forwarding,no-X11-forwarding "
|
||||||
authorizedKeysLine _ = ""
|
|
||||||
|
|
||||||
|
{- Returns the public key content, and a modified SshData with a
|
||||||
|
- mangled hostname that will enable use of the key.
|
||||||
|
- This way we avoid changing the user's regular ssh experience at all. -}
|
||||||
|
genSshKey :: SshData -> IO (String, SshData)
|
||||||
|
genSshKey sshdata = do
|
||||||
|
sshdir <- sshDir
|
||||||
|
let configfile = sshdir </> "config"
|
||||||
|
createDirectoryIfMissing True sshdir
|
||||||
|
unlessM (doesFileExist $ sshdir </> sshprivkeyfile) $ do
|
||||||
|
ok <- boolSystem "ssh-keygen"
|
||||||
|
[ Param "-P", Param "" -- no password
|
||||||
|
, Param "-f", File $ sshdir </> sshprivkeyfile
|
||||||
|
]
|
||||||
|
unless ok $
|
||||||
|
error "ssh-keygen failed"
|
||||||
|
unlessM (catchBoolIO $ isInfixOf mangledhost <$> readFile configfile) $
|
||||||
|
appendFile configfile $ unlines
|
||||||
|
[ ""
|
||||||
|
, "# Added automatically by git-annex"
|
||||||
|
, "Host " ++ mangledhost
|
||||||
|
, "\tHostname " ++ T.unpack (sshHostName sshdata)
|
||||||
|
, "\tIdentityFile ~/.ssh/" ++ sshprivkeyfile
|
||||||
|
]
|
||||||
|
pubkey <- readFile $ sshdir </> sshpubkeyfile
|
||||||
|
return (pubkey, sshdata { sshHostName = T.pack mangledhost })
|
||||||
|
where
|
||||||
|
sshprivkeyfile = "key." ++ mangledhost
|
||||||
|
sshpubkeyfile = sshprivkeyfile ++ ".pub"
|
||||||
|
mangledhost = "git-annex-" ++ T.unpack (sshHostName sshdata) ++ user
|
||||||
|
user = maybe "" (\u -> "-" ++ T.unpack u) (sshUserName sshdata)
|
||||||
|
|
|
@ -68,14 +68,12 @@ data WebAppState = WebAppState
|
||||||
{ showIntro :: Bool
|
{ showIntro :: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type PubKey = String
|
|
||||||
|
|
||||||
data SshData = SshData
|
data SshData = SshData
|
||||||
{ sshHostName :: Text
|
{ sshHostName :: Text
|
||||||
, sshUserName :: Maybe Text
|
, sshUserName :: Maybe Text
|
||||||
, sshDirectory :: Text
|
, sshDirectory :: Text
|
||||||
, sshRepoName :: String
|
, sshRepoName :: String
|
||||||
, pubKey :: Maybe PubKey
|
, needsPubKey :: Bool
|
||||||
, rsyncOnly :: Bool
|
, rsyncOnly :: Bool
|
||||||
}
|
}
|
||||||
deriving (Read, Show, Eq)
|
deriving (Read, Show, Eq)
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div .row-fluid>
|
<div .row-fluid>
|
||||||
<div .span8>
|
<div .span8>
|
||||||
<p>
|
<p>
|
||||||
The server has been verified to be usable.
|
The server #{sshHostName sshdata} has been verified to be usable.
|
||||||
$if not (rsyncOnly sshdata)
|
$if not (rsyncOnly sshdata)
|
||||||
<p>
|
<p>
|
||||||
You have two options for how to use the server.
|
You have two options for how to use the server.
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
server. The server will not store other information about your #
|
server. The server will not store other information about your #
|
||||||
git repository.
|
git repository.
|
||||||
<div .span4>
|
<div .span4>
|
||||||
$if haspubkey
|
$if needsPubKey sshdata
|
||||||
<div .alert .alert-info>
|
<div .alert .alert-info>
|
||||||
<i .icon-info-sign></i> #
|
<i .icon-info-sign></i> #
|
||||||
A ssh key will be installed on the server, allowing git-annex to #
|
A ssh key will be installed on the server, allowing git-annex to #
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
<div .modal-body>
|
<div .modal-body>
|
||||||
<p>
|
<p>
|
||||||
Setting up repository on the remote server. This could take a minute.
|
Setting up repository on the remote server. This could take a minute.
|
||||||
$if haspubkey
|
$if needsPubKey sshdata
|
||||||
<p>
|
<p>
|
||||||
You will be prompted once more for your ssh password. A ssh key #
|
You will be prompted once more for your ssh password. A ssh key #
|
||||||
is being installed on the server, allowing git-annex to access it #
|
is being installed on the server, allowing git-annex to access it #
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue