2012-09-10 19:20:18 +00:00
|
|
|
|
{- git-annex assistant ssh utilities
|
|
|
|
|
-
|
2015-01-21 16:50:09 +00:00
|
|
|
|
- Copyright 2012-2013 Joey Hess <id@joeyh.name>
|
2012-09-10 19:20:18 +00:00
|
|
|
|
-
|
|
|
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
module Assistant.Ssh where
|
|
|
|
|
|
2016-01-20 20:36:33 +00:00
|
|
|
|
import Annex.Common
|
2013-05-12 23:19:28 +00:00
|
|
|
|
import Utility.Tmp
|
2013-02-13 18:30:04 +00:00
|
|
|
|
import Utility.Shell
|
2013-10-01 20:08:01 +00:00
|
|
|
|
import Utility.Rsync
|
2013-11-12 18:54:02 +00:00
|
|
|
|
import Utility.FileMode
|
2013-12-21 00:58:36 +00:00
|
|
|
|
import Utility.SshConfig
|
2012-10-31 19:17:00 +00:00
|
|
|
|
import Git.Remote
|
2012-09-10 19:20:18 +00:00
|
|
|
|
|
|
|
|
|
import Data.Text (Text)
|
|
|
|
|
import qualified Data.Text as T
|
2012-09-10 21:53:51 +00:00
|
|
|
|
import Data.Char
|
2013-06-25 19:23:46 +00:00
|
|
|
|
import Network.URI
|
2012-09-10 19:20:18 +00:00
|
|
|
|
|
|
|
|
|
data SshData = SshData
|
|
|
|
|
{ sshHostName :: Text
|
|
|
|
|
, sshUserName :: Maybe Text
|
|
|
|
|
, sshDirectory :: Text
|
|
|
|
|
, sshRepoName :: String
|
2012-12-06 21:09:08 +00:00
|
|
|
|
, sshPort :: Int
|
2012-09-10 19:20:18 +00:00
|
|
|
|
, needsPubKey :: Bool
|
2013-09-29 18:39:10 +00:00
|
|
|
|
, sshCapabilities :: [SshServerCapability]
|
2015-07-20 22:38:23 +00:00
|
|
|
|
, sshRepoUrl :: Maybe String
|
2012-09-10 19:20:18 +00:00
|
|
|
|
}
|
|
|
|
|
deriving (Read, Show, Eq)
|
|
|
|
|
|
2015-07-20 22:38:23 +00:00
|
|
|
|
data SshServerCapability
|
|
|
|
|
= GitAnnexShellCapable -- server has git-annex-shell installed
|
|
|
|
|
| GitCapable -- server has git installed
|
|
|
|
|
| RsyncCapable -- server supports raw rsync access (not only via git-annex-shell)
|
|
|
|
|
| PushCapable -- repo on server is set up already, and ready to accept pushes
|
2013-09-29 18:39:10 +00:00
|
|
|
|
deriving (Read, Show, Eq)
|
|
|
|
|
|
|
|
|
|
hasCapability :: SshData -> SshServerCapability -> Bool
|
|
|
|
|
hasCapability d c = c `elem` sshCapabilities d
|
|
|
|
|
|
2015-07-20 22:38:23 +00:00
|
|
|
|
addCapability :: SshData -> SshServerCapability -> SshData
|
|
|
|
|
addCapability d c = d { sshCapabilities = c : sshCapabilities d }
|
|
|
|
|
|
2013-10-01 17:43:35 +00:00
|
|
|
|
onlyCapability :: SshData -> SshServerCapability -> Bool
|
|
|
|
|
onlyCapability d c = all (== c) (sshCapabilities d)
|
|
|
|
|
|
2015-07-20 22:38:23 +00:00
|
|
|
|
type SshPubKey = String
|
|
|
|
|
type SshPrivKey = String
|
|
|
|
|
|
2012-09-10 19:20:18 +00:00
|
|
|
|
data SshKeyPair = SshKeyPair
|
2015-07-20 22:38:23 +00:00
|
|
|
|
{ sshPubKey :: SshPubKey
|
|
|
|
|
, sshPrivKey :: SshPrivKey
|
2012-09-10 19:20:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-09-11 19:06:29 +00:00
|
|
|
|
instance Show SshKeyPair where
|
|
|
|
|
show = sshPubKey
|
|
|
|
|
|
2012-09-10 19:20:18 +00:00
|
|
|
|
{- ssh -ofoo=bar command-line option -}
|
|
|
|
|
sshOpt :: String -> String -> String
|
|
|
|
|
sshOpt k v = concat ["-o", k, "=", v]
|
|
|
|
|
|
2012-09-11 01:55:59 +00:00
|
|
|
|
{- user@host or host -}
|
|
|
|
|
genSshHost :: Text -> Maybe Text -> String
|
|
|
|
|
genSshHost host user = maybe "" (\v -> T.unpack v ++ "@") user ++ T.unpack host
|
|
|
|
|
|
2013-10-01 20:08:01 +00:00
|
|
|
|
{- Generates a ssh or rsync url from a SshData. -}
|
|
|
|
|
genSshUrl :: SshData -> String
|
2015-07-20 22:38:23 +00:00
|
|
|
|
genSshUrl sshdata = case sshRepoUrl sshdata of
|
|
|
|
|
Just repourl -> repourl
|
|
|
|
|
Nothing -> addtrailingslash $ T.unpack $ T.concat $
|
|
|
|
|
if (onlyCapability sshdata RsyncCapable)
|
|
|
|
|
then [u, h, T.pack ":", sshDirectory sshdata]
|
|
|
|
|
else [T.pack "ssh://", u, h, d]
|
2013-10-01 20:08:01 +00:00
|
|
|
|
where
|
|
|
|
|
u = maybe (T.pack "") (\v -> T.concat [v, T.pack "@"]) $ sshUserName sshdata
|
|
|
|
|
h = sshHostName sshdata
|
|
|
|
|
d
|
|
|
|
|
| T.pack "/" `T.isPrefixOf` sshDirectory sshdata = sshDirectory sshdata
|
|
|
|
|
| T.pack "~/" `T.isPrefixOf` sshDirectory sshdata = T.concat [T.pack "/", sshDirectory sshdata]
|
|
|
|
|
| otherwise = T.concat [T.pack "/~/", sshDirectory sshdata]
|
|
|
|
|
addtrailingslash s
|
|
|
|
|
| "/" `isSuffixOf` s = s
|
|
|
|
|
| otherwise = s ++ "/"
|
|
|
|
|
|
|
|
|
|
{- Reverses genSshUrl -}
|
|
|
|
|
parseSshUrl :: String -> Maybe SshData
|
|
|
|
|
parseSshUrl u
|
|
|
|
|
| "ssh://" `isPrefixOf` u = fromssh (drop (length "ssh://") u)
|
|
|
|
|
| otherwise = fromrsync u
|
|
|
|
|
where
|
|
|
|
|
mkdata (userhost, dir) = Just $ SshData
|
|
|
|
|
{ sshHostName = T.pack host
|
|
|
|
|
, sshUserName = if null user then Nothing else Just $ T.pack user
|
|
|
|
|
, sshDirectory = T.pack dir
|
|
|
|
|
, sshRepoName = genSshRepoName host dir
|
|
|
|
|
-- dummy values, cannot determine from url
|
|
|
|
|
, sshPort = 22
|
|
|
|
|
, needsPubKey = True
|
|
|
|
|
, sshCapabilities = []
|
2015-07-20 22:38:23 +00:00
|
|
|
|
, sshRepoUrl = Nothing
|
2013-10-01 20:08:01 +00:00
|
|
|
|
}
|
|
|
|
|
where
|
2014-10-09 18:53:13 +00:00
|
|
|
|
(user, host) = if '@' `elem` userhost
|
2013-10-01 20:08:01 +00:00
|
|
|
|
then separate (== '@') userhost
|
|
|
|
|
else ("", userhost)
|
|
|
|
|
fromrsync s
|
|
|
|
|
| not (rsyncUrlIsShell u) = Nothing
|
|
|
|
|
| otherwise = mkdata $ separate (== ':') s
|
|
|
|
|
fromssh = mkdata . break (== '/')
|
|
|
|
|
|
2012-10-31 19:17:00 +00:00
|
|
|
|
{- Generates a git remote name, like host_dir or host -}
|
2012-09-10 21:53:51 +00:00
|
|
|
|
genSshRepoName :: String -> FilePath -> String
|
|
|
|
|
genSshRepoName host dir
|
2012-10-31 19:17:00 +00:00
|
|
|
|
| null dir = makeLegalName host
|
|
|
|
|
| otherwise = makeLegalName $ host ++ "_" ++ dir
|
2012-09-10 21:53:51 +00:00
|
|
|
|
|
2012-09-10 19:20:18 +00:00
|
|
|
|
{- The output of ssh, including both stdout and stderr. -}
|
2013-02-26 17:04:37 +00:00
|
|
|
|
sshTranscript :: [String] -> (Maybe String) -> IO (String, Bool)
|
|
|
|
|
sshTranscript opts input = processTranscript "ssh" opts input
|
2012-09-10 19:20:18 +00:00
|
|
|
|
|
2012-09-10 22:18:55 +00:00
|
|
|
|
{- Ensure that the ssh public key doesn't include any ssh options, like
|
2015-02-04 18:05:27 +00:00
|
|
|
|
- command=foo, or other weirdness.
|
|
|
|
|
-
|
|
|
|
|
- The returned version of the key has its comment removed.
|
|
|
|
|
-}
|
|
|
|
|
validateSshPubKey :: SshPubKey -> Either String SshPubKey
|
2013-06-12 03:12:01 +00:00
|
|
|
|
validateSshPubKey pubkey
|
2015-02-04 18:05:27 +00:00
|
|
|
|
| length (lines pubkey) == 1 = check $ words pubkey
|
|
|
|
|
| otherwise = Left "too many lines in ssh public key"
|
2012-10-31 06:34:03 +00:00
|
|
|
|
where
|
2015-02-04 18:05:27 +00:00
|
|
|
|
check (prefix:key:_) = checkprefix prefix (unwords [prefix, key])
|
2012-10-31 06:34:03 +00:00
|
|
|
|
check _ = err "wrong number of words in ssh public key"
|
2012-10-18 04:29:27 +00:00
|
|
|
|
|
2012-10-31 06:34:03 +00:00
|
|
|
|
err msg = Left $ unwords [msg, pubkey]
|
2012-10-18 04:29:27 +00:00
|
|
|
|
|
2015-02-04 18:05:27 +00:00
|
|
|
|
checkprefix prefix validpubkey
|
|
|
|
|
| ssh == "ssh" && all isAlphaNum keytype = Right validpubkey
|
2012-10-31 06:34:03 +00:00
|
|
|
|
| otherwise = err "bad ssh public key prefix"
|
|
|
|
|
where
|
|
|
|
|
(ssh, keytype) = separate (== '-') prefix
|
2012-10-18 04:29:27 +00:00
|
|
|
|
|
2012-11-05 16:21:13 +00:00
|
|
|
|
addAuthorizedKeys :: Bool -> FilePath -> SshPubKey -> IO Bool
|
2013-10-01 17:43:35 +00:00
|
|
|
|
addAuthorizedKeys gitannexshellonly dir pubkey = boolSystem "sh"
|
|
|
|
|
[ Param "-c" , Param $ addAuthorizedKeysCommand gitannexshellonly dir pubkey ]
|
2012-09-11 04:23:34 +00:00
|
|
|
|
|
2014-03-05 17:53:21 +00:00
|
|
|
|
{- Should only be used within the same process that added the line;
|
|
|
|
|
- the layout of the line is not kepy stable across versions. -}
|
2012-11-05 16:21:13 +00:00
|
|
|
|
removeAuthorizedKeys :: Bool -> FilePath -> SshPubKey -> IO ()
|
2013-10-01 17:43:35 +00:00
|
|
|
|
removeAuthorizedKeys gitannexshellonly dir pubkey = do
|
|
|
|
|
let keyline = authorizedKeysLine gitannexshellonly dir pubkey
|
2012-09-11 04:23:34 +00:00
|
|
|
|
sshdir <- sshDir
|
2013-01-03 20:11:19 +00:00
|
|
|
|
let keyfile = sshdir </> "authorized_keys"
|
2012-09-11 04:23:34 +00:00
|
|
|
|
ls <- lines <$> readFileStrict keyfile
|
2014-01-03 21:44:12 +00:00
|
|
|
|
viaTmp writeSshConfig keyfile $ unlines $ filter (/= keyline) ls
|
2012-09-10 21:53:51 +00:00
|
|
|
|
|
2012-09-10 19:20:18 +00:00
|
|
|
|
{- Implemented as a shell command, so it can be run on remote servers over
|
2012-09-26 22:59:18 +00:00
|
|
|
|
- ssh.
|
|
|
|
|
-
|
|
|
|
|
- The ~/.ssh/git-annex-shell wrapper script is created if not already
|
|
|
|
|
- present.
|
|
|
|
|
-}
|
2012-11-05 16:21:13 +00:00
|
|
|
|
addAuthorizedKeysCommand :: Bool -> FilePath -> SshPubKey -> String
|
2013-10-01 17:43:35 +00:00
|
|
|
|
addAuthorizedKeysCommand gitannexshellonly dir pubkey = intercalate "&&"
|
2012-09-10 21:53:51 +00:00
|
|
|
|
[ "mkdir -p ~/.ssh"
|
2013-04-23 00:24:53 +00:00
|
|
|
|
, intercalate "; "
|
2012-09-26 22:59:18 +00:00
|
|
|
|
[ "if [ ! -e " ++ wrapper ++ " ]"
|
2013-04-23 00:24:53 +00:00
|
|
|
|
, "then (" ++ intercalate ";" (map echoval script) ++ ") > " ++ wrapper
|
2012-09-26 22:59:18 +00:00
|
|
|
|
, "fi"
|
|
|
|
|
]
|
|
|
|
|
, "chmod 700 " ++ wrapper
|
2012-09-10 21:53:51 +00:00
|
|
|
|
, "touch ~/.ssh/authorized_keys"
|
|
|
|
|
, "chmod 600 ~/.ssh/authorized_keys"
|
|
|
|
|
, unwords
|
|
|
|
|
[ "echo"
|
2013-10-01 17:43:35 +00:00
|
|
|
|
, shellEscape $ authorizedKeysLine gitannexshellonly dir pubkey
|
2012-09-10 21:53:51 +00:00
|
|
|
|
, ">>~/.ssh/authorized_keys"
|
2012-09-10 19:20:18 +00:00
|
|
|
|
]
|
2012-09-10 21:53:51 +00:00
|
|
|
|
]
|
2012-10-31 06:34:03 +00:00
|
|
|
|
where
|
|
|
|
|
echoval v = "echo " ++ shellEscape v
|
|
|
|
|
wrapper = "~/.ssh/git-annex-shell"
|
|
|
|
|
script =
|
2013-05-06 19:58:13 +00:00
|
|
|
|
[ shebang_portable
|
2012-10-31 06:34:03 +00:00
|
|
|
|
, "set -e"
|
2013-03-12 11:12:39 +00:00
|
|
|
|
, "if [ \"x$SSH_ORIGINAL_COMMAND\" != \"x\" ]; then"
|
|
|
|
|
, runshell "$SSH_ORIGINAL_COMMAND"
|
|
|
|
|
, "else"
|
|
|
|
|
, runshell "$@"
|
|
|
|
|
, "fi"
|
2012-10-31 06:34:03 +00:00
|
|
|
|
]
|
2013-03-12 11:12:39 +00:00
|
|
|
|
runshell var = "exec git-annex-shell -c \"" ++ var ++ "\""
|
2012-09-10 21:53:51 +00:00
|
|
|
|
|
2012-11-05 16:21:13 +00:00
|
|
|
|
authorizedKeysLine :: Bool -> FilePath -> SshPubKey -> String
|
2013-10-01 17:43:35 +00:00
|
|
|
|
authorizedKeysLine gitannexshellonly dir pubkey
|
|
|
|
|
| gitannexshellonly = limitcommand ++ pubkey
|
2012-09-10 19:20:18 +00:00
|
|
|
|
{- TODO: Locking down rsync is difficult, requiring a rather
|
|
|
|
|
- long perl script. -}
|
2013-10-01 17:43:35 +00:00
|
|
|
|
| otherwise = pubkey
|
2012-10-31 06:34:03 +00:00
|
|
|
|
where
|
2014-04-02 19:30:10 +00:00
|
|
|
|
limitcommand = "command=\"env GIT_ANNEX_SHELL_DIRECTORY="++shellEscape dir++" ~/.ssh/git-annex-shell\",no-agent-forwarding,no-port-forwarding,no-X11-forwarding,no-pty "
|
2012-09-10 19:20:18 +00:00
|
|
|
|
|
|
|
|
|
{- Generates a ssh key pair. -}
|
|
|
|
|
genSshKeyPair :: IO SshKeyPair
|
2013-05-12 23:19:28 +00:00
|
|
|
|
genSshKeyPair = withTmpDir "git-annex-keygen" $ \dir -> do
|
2012-09-10 19:20:18 +00:00
|
|
|
|
ok <- boolSystem "ssh-keygen"
|
|
|
|
|
[ Param "-P", Param "" -- no password
|
|
|
|
|
, Param "-f", File $ dir </> "key"
|
|
|
|
|
]
|
|
|
|
|
unless ok $
|
|
|
|
|
error "ssh-keygen failed"
|
|
|
|
|
SshKeyPair
|
|
|
|
|
<$> readFile (dir </> "key.pub")
|
|
|
|
|
<*> readFile (dir </> "key")
|
|
|
|
|
|
|
|
|
|
{- Installs a ssh key pair, and sets up ssh config with a mangled hostname
|
|
|
|
|
- that will enable use of the key. This way we avoid changing the user's
|
|
|
|
|
- regular ssh experience at all. Returns a modified SshData containing the
|
2013-04-14 19:34:59 +00:00
|
|
|
|
- mangled hostname.
|
|
|
|
|
-
|
2013-04-15 00:00:32 +00:00
|
|
|
|
- Note that the key files are put in ~/.ssh/git-annex/, rather than directly
|
2013-04-14 19:34:59 +00:00
|
|
|
|
- in ssh because of an **INSANE** behavior of gnome-keyring: It loads
|
2013-07-27 17:00:49 +00:00
|
|
|
|
- ~/.ssh/ANYTHING.pub, and uses them indiscriminately. But using this key
|
2013-04-14 19:34:59 +00:00
|
|
|
|
- for a normal login to the server will force git-annex-shell to run,
|
|
|
|
|
- and locks the user out. Luckily, it does not recurse into subdirectories.
|
2013-07-31 17:30:49 +00:00
|
|
|
|
-
|
|
|
|
|
- Similarly, IdentitiesOnly is set in the ssh config to prevent the
|
|
|
|
|
- ssh-agent from forcing use of a different key.
|
2013-12-21 00:58:36 +00:00
|
|
|
|
-
|
|
|
|
|
- Force strict host key checking to avoid repeated prompts
|
|
|
|
|
- when git-annex and git try to access the remote, if its
|
|
|
|
|
- host key has changed.
|
2013-04-14 19:34:59 +00:00
|
|
|
|
-}
|
2015-07-20 22:38:23 +00:00
|
|
|
|
installSshKeyPair :: SshKeyPair -> SshData -> IO SshData
|
|
|
|
|
installSshKeyPair sshkeypair sshdata = do
|
2012-09-10 19:20:18 +00:00
|
|
|
|
sshdir <- sshDir
|
2015-07-20 22:38:23 +00:00
|
|
|
|
createDirectoryIfMissing True $ parentDir $ sshdir </> sshPrivKeyFile sshdata
|
2012-09-10 19:20:18 +00:00
|
|
|
|
|
2015-07-20 22:38:23 +00:00
|
|
|
|
unlessM (doesFileExist $ sshdir </> sshPrivKeyFile sshdata) $
|
|
|
|
|
writeFileProtected (sshdir </> sshPrivKeyFile sshdata) (sshPrivKey sshkeypair)
|
|
|
|
|
unlessM (doesFileExist $ sshdir </> sshPubKeyFile sshdata) $
|
|
|
|
|
writeFile (sshdir </> sshPubKeyFile sshdata) (sshPubKey sshkeypair)
|
2012-09-10 19:20:18 +00:00
|
|
|
|
|
2012-12-06 21:09:08 +00:00
|
|
|
|
setSshConfig sshdata
|
2015-07-20 22:38:23 +00:00
|
|
|
|
[ ("IdentityFile", "~/.ssh/" ++ sshPrivKeyFile sshdata)
|
2013-07-31 17:30:49 +00:00
|
|
|
|
, ("IdentitiesOnly", "yes")
|
2013-12-21 00:58:36 +00:00
|
|
|
|
, ("StrictHostKeyChecking", "yes")
|
2013-07-31 17:30:49 +00:00
|
|
|
|
]
|
2015-07-20 22:38:23 +00:00
|
|
|
|
|
|
|
|
|
sshPrivKeyFile :: SshData -> FilePath
|
|
|
|
|
sshPrivKeyFile sshdata = "git-annex" </> "key." ++ mangleSshHostName sshdata
|
|
|
|
|
|
|
|
|
|
sshPubKeyFile :: SshData -> FilePath
|
|
|
|
|
sshPubKeyFile sshdata = sshPrivKeyFile sshdata ++ ".pub"
|
|
|
|
|
|
|
|
|
|
{- Generates an installs a new ssh key pair if one is not already
|
|
|
|
|
- installed. Returns the modified SshData that will use the key pair,
|
|
|
|
|
- and the key pair. -}
|
|
|
|
|
setupSshKeyPair :: SshData -> IO (SshData, SshKeyPair)
|
|
|
|
|
setupSshKeyPair sshdata = do
|
|
|
|
|
sshdir <- sshDir
|
|
|
|
|
mprivkey <- catchMaybeIO $ readFile (sshdir </> sshPrivKeyFile sshdata)
|
|
|
|
|
mpubkey <- catchMaybeIO $ readFile (sshdir </> sshPubKeyFile sshdata)
|
|
|
|
|
keypair <- case (mprivkey, mpubkey) of
|
|
|
|
|
(Just privkey, Just pubkey) -> return $ SshKeyPair
|
|
|
|
|
{ sshPubKey = pubkey
|
|
|
|
|
, sshPrivKey = privkey
|
|
|
|
|
}
|
|
|
|
|
_ -> genSshKeyPair
|
|
|
|
|
sshdata' <- installSshKeyPair keypair sshdata
|
|
|
|
|
return (sshdata', keypair)
|
2012-12-06 21:09:08 +00:00
|
|
|
|
|
webapp: Improve handling of remotes whose setup has stalled.
This includes recovery from the ssh-agent problem that led to many reporting
http://git-annex.branchable.com/bugs/Internal_Server_Error:_Unknown_UUID/
(Including fixing up .ssh/config to set IdentitiesOnly.)
Remotes that have no known uuid are now displayed in the webapp as
"unfinished". There's a link to check their status, and if the remote
has been set annex-ignore, a retry button can be used to unset that and
try again to set up the remote.
As this bug has shown, the process of adding a ssh remote has some failure
modes that are not really ideal. It would certianly be better if, when
setting up a ssh remote it would detect if it's failed to get the UUID,
and handle that in the remote setup process, rather than waiting until
later and handling it this way.
However, that's hard to do, particularly for local pairing, since the
PairListener runs as a background thread. The best it could do is pop up an
alert if there's a problem. This solution is not much different.
Also, this solution handles cases where the user has gotten their repo into
a mess manually and let's the assistant help with cleaning it up.
This commit was sponsored by Chia Shee Liang. Thanks!
2013-07-31 20:01:20 +00:00
|
|
|
|
{- Fixes git-annex ssh key pairs configured in .ssh/config
|
2013-12-21 00:58:36 +00:00
|
|
|
|
- by old versions to set IdentitiesOnly.
|
|
|
|
|
-
|
|
|
|
|
- Strategy: Search for IdentityFile lines with key.git-annex
|
webapp: Improve handling of remotes whose setup has stalled.
This includes recovery from the ssh-agent problem that led to many reporting
http://git-annex.branchable.com/bugs/Internal_Server_Error:_Unknown_UUID/
(Including fixing up .ssh/config to set IdentitiesOnly.)
Remotes that have no known uuid are now displayed in the webapp as
"unfinished". There's a link to check their status, and if the remote
has been set annex-ignore, a retry button can be used to unset that and
try again to set up the remote.
As this bug has shown, the process of adding a ssh remote has some failure
modes that are not really ideal. It would certianly be better if, when
setting up a ssh remote it would detect if it's failed to get the UUID,
and handle that in the remote setup process, rather than waiting until
later and handling it this way.
However, that's hard to do, particularly for local pairing, since the
PairListener runs as a background thread. The best it could do is pop up an
alert if there's a problem. This solution is not much different.
Also, this solution handles cases where the user has gotten their repo into
a mess manually and let's the assistant help with cleaning it up.
This commit was sponsored by Chia Shee Liang. Thanks!
2013-07-31 20:01:20 +00:00
|
|
|
|
- in their names. These are for git-annex ssh key pairs.
|
|
|
|
|
- Add the IdentitiesOnly line immediately after them, if not already
|
2013-12-21 00:58:36 +00:00
|
|
|
|
- present.
|
|
|
|
|
-}
|
|
|
|
|
fixSshKeyPairIdentitiesOnly :: IO ()
|
|
|
|
|
fixSshKeyPairIdentitiesOnly = changeUserSshConfig $ unlines . go [] . lines
|
webapp: Improve handling of remotes whose setup has stalled.
This includes recovery from the ssh-agent problem that led to many reporting
http://git-annex.branchable.com/bugs/Internal_Server_Error:_Unknown_UUID/
(Including fixing up .ssh/config to set IdentitiesOnly.)
Remotes that have no known uuid are now displayed in the webapp as
"unfinished". There's a link to check their status, and if the remote
has been set annex-ignore, a retry button can be used to unset that and
try again to set up the remote.
As this bug has shown, the process of adding a ssh remote has some failure
modes that are not really ideal. It would certianly be better if, when
setting up a ssh remote it would detect if it's failed to get the UUID,
and handle that in the remote setup process, rather than waiting until
later and handling it this way.
However, that's hard to do, particularly for local pairing, since the
PairListener runs as a background thread. The best it could do is pop up an
alert if there's a problem. This solution is not much different.
Also, this solution handles cases where the user has gotten their repo into
a mess manually and let's the assistant help with cleaning it up.
This commit was sponsored by Chia Shee Liang. Thanks!
2013-07-31 20:01:20 +00:00
|
|
|
|
where
|
2014-10-09 18:53:13 +00:00
|
|
|
|
go c [] = reverse c
|
webapp: Improve handling of remotes whose setup has stalled.
This includes recovery from the ssh-agent problem that led to many reporting
http://git-annex.branchable.com/bugs/Internal_Server_Error:_Unknown_UUID/
(Including fixing up .ssh/config to set IdentitiesOnly.)
Remotes that have no known uuid are now displayed in the webapp as
"unfinished". There's a link to check their status, and if the remote
has been set annex-ignore, a retry button can be used to unset that and
try again to set up the remote.
As this bug has shown, the process of adding a ssh remote has some failure
modes that are not really ideal. It would certianly be better if, when
setting up a ssh remote it would detect if it's failed to get the UUID,
and handle that in the remote setup process, rather than waiting until
later and handling it this way.
However, that's hard to do, particularly for local pairing, since the
PairListener runs as a background thread. The best it could do is pop up an
alert if there's a problem. This solution is not much different.
Also, this solution handles cases where the user has gotten their repo into
a mess manually and let's the assistant help with cleaning it up.
This commit was sponsored by Chia Shee Liang. Thanks!
2013-07-31 20:01:20 +00:00
|
|
|
|
go c (l:[])
|
|
|
|
|
| all (`isInfixOf` l) indicators = go (fixedline l:l:c) []
|
|
|
|
|
| otherwise = go (l:c) []
|
|
|
|
|
go c (l:next:rest)
|
|
|
|
|
| all (`isInfixOf` l) indicators && not ("IdentitiesOnly" `isInfixOf` next) =
|
|
|
|
|
go (fixedline l:l:c) (next:rest)
|
|
|
|
|
| otherwise = go (l:c) (next:rest)
|
2014-10-09 18:53:13 +00:00
|
|
|
|
indicators = ["IdentityFile", "key.git-annex"]
|
webapp: Improve handling of remotes whose setup has stalled.
This includes recovery from the ssh-agent problem that led to many reporting
http://git-annex.branchable.com/bugs/Internal_Server_Error:_Unknown_UUID/
(Including fixing up .ssh/config to set IdentitiesOnly.)
Remotes that have no known uuid are now displayed in the webapp as
"unfinished". There's a link to check their status, and if the remote
has been set annex-ignore, a retry button can be used to unset that and
try again to set up the remote.
As this bug has shown, the process of adding a ssh remote has some failure
modes that are not really ideal. It would certianly be better if, when
setting up a ssh remote it would detect if it's failed to get the UUID,
and handle that in the remote setup process, rather than waiting until
later and handling it this way.
However, that's hard to do, particularly for local pairing, since the
PairListener runs as a background thread. The best it could do is pop up an
alert if there's a problem. This solution is not much different.
Also, this solution handles cases where the user has gotten their repo into
a mess manually and let's the assistant help with cleaning it up.
This commit was sponsored by Chia Shee Liang. Thanks!
2013-07-31 20:01:20 +00:00
|
|
|
|
fixedline tmpl = takeWhile isSpace tmpl ++ "IdentitiesOnly yes"
|
|
|
|
|
|
2013-12-21 00:58:36 +00:00
|
|
|
|
{- Add StrictHostKeyChecking to any ssh config stanzas that were written
|
|
|
|
|
- by git-annex. -}
|
|
|
|
|
fixUpSshRemotes :: IO ()
|
|
|
|
|
fixUpSshRemotes = modifyUserSshConfig (map go)
|
|
|
|
|
where
|
|
|
|
|
go c@(HostConfig h _)
|
|
|
|
|
| "git-annex-" `isPrefixOf` h = fixupconfig c
|
|
|
|
|
| otherwise = c
|
|
|
|
|
go other = other
|
|
|
|
|
|
|
|
|
|
fixupconfig c = case findHostConfigKey c "StrictHostKeyChecking" of
|
|
|
|
|
Nothing -> addToHostConfig c "StrictHostKeyChecking" "yes"
|
|
|
|
|
Just _ -> c
|
|
|
|
|
|
2012-12-06 21:09:08 +00:00
|
|
|
|
{- Setups up a ssh config with a mangled hostname.
|
|
|
|
|
- Returns a modified SshData containing the mangled hostname. -}
|
|
|
|
|
setSshConfig :: SshData -> [(String, String)] -> IO SshData
|
|
|
|
|
setSshConfig sshdata config = do
|
|
|
|
|
sshdir <- sshDir
|
|
|
|
|
createDirectoryIfMissing True sshdir
|
|
|
|
|
let configfile = sshdir </> "config"
|
2014-01-03 21:44:12 +00:00
|
|
|
|
unlessM (catchBoolIO $ isInfixOf mangledhost <$> readFile configfile) $ do
|
2012-12-06 21:09:08 +00:00
|
|
|
|
appendFile configfile $ unlines $
|
2012-09-10 19:20:18 +00:00
|
|
|
|
[ ""
|
|
|
|
|
, "# Added automatically by git-annex"
|
|
|
|
|
, "Host " ++ mangledhost
|
2012-12-06 21:09:08 +00:00
|
|
|
|
] ++ map (\(k, v) -> "\t" ++ k ++ " " ++ v)
|
|
|
|
|
(settings ++ config)
|
2014-01-03 21:44:12 +00:00
|
|
|
|
setSshConfigMode configfile
|
|
|
|
|
|
2015-07-27 15:03:58 +00:00
|
|
|
|
return $ sshdata
|
|
|
|
|
{ sshHostName = T.pack mangledhost
|
|
|
|
|
, sshRepoUrl = replace orighost mangledhost
|
|
|
|
|
<$> sshRepoUrl sshdata
|
|
|
|
|
}
|
2012-10-31 06:34:03 +00:00
|
|
|
|
where
|
2015-07-27 15:03:58 +00:00
|
|
|
|
orighost = T.unpack $ sshHostName sshdata
|
2012-12-06 21:09:08 +00:00
|
|
|
|
mangledhost = mangleSshHostName sshdata
|
|
|
|
|
settings =
|
2015-07-27 15:03:58 +00:00
|
|
|
|
[ ("Hostname", orighost)
|
2012-12-06 21:09:08 +00:00
|
|
|
|
, ("Port", show $ sshPort sshdata)
|
|
|
|
|
]
|
2012-09-13 20:47:44 +00:00
|
|
|
|
|
2013-04-16 17:49:39 +00:00
|
|
|
|
{- This hostname is specific to a given repository on the ssh host,
|
|
|
|
|
- so it is based on the real hostname, the username, and the directory.
|
2013-06-25 19:23:46 +00:00
|
|
|
|
-
|
2016-07-19 15:37:03 +00:00
|
|
|
|
- The mangled hostname has the form:
|
|
|
|
|
- "git-annex-realhostname-username_port_dir"
|
|
|
|
|
- Note that "-" is only used in the realhostname and as a separator;
|
|
|
|
|
- this is necessary to allow unMangleSshHostName to work.
|
|
|
|
|
-
|
|
|
|
|
- Unusual characters are url encoded, but using "." rather than "%"
|
2013-06-25 19:23:46 +00:00
|
|
|
|
- (the latter has special meaning to ssh).
|
2016-07-19 15:37:03 +00:00
|
|
|
|
-
|
|
|
|
|
- In the username and directory, unusual characters are any
|
|
|
|
|
- non-alphanumerics, other than "_"
|
|
|
|
|
-
|
|
|
|
|
- The real hostname is not normally encoded at all. This is done for
|
|
|
|
|
- backwards compatability and to avoid unnecessary ugliness in the
|
|
|
|
|
- filename. However, when it contains special characters
|
|
|
|
|
- (notably ":" which cannot be used on some filesystems), it is url
|
|
|
|
|
- encoded. To indicate it was encoded, the mangled hostname
|
|
|
|
|
- has the form
|
|
|
|
|
- "git-annex-.encodedhostname-username_port_dir"
|
2013-04-16 17:49:39 +00:00
|
|
|
|
-}
|
2012-12-06 21:09:08 +00:00
|
|
|
|
mangleSshHostName :: SshData -> String
|
2016-07-19 15:37:03 +00:00
|
|
|
|
mangleSshHostName sshdata = intercalate "-"
|
|
|
|
|
[ "git-annex"
|
|
|
|
|
, escapehostname (T.unpack (sshHostName sshdata))
|
|
|
|
|
, escape extra
|
|
|
|
|
]
|
2012-12-06 21:09:08 +00:00
|
|
|
|
where
|
2013-04-23 00:24:53 +00:00
|
|
|
|
extra = intercalate "_" $ map T.unpack $ catMaybes
|
2013-04-16 17:49:39 +00:00
|
|
|
|
[ sshUserName sshdata
|
2014-06-05 16:50:15 +00:00
|
|
|
|
, Just $ T.pack $ show $ sshPort sshdata
|
2013-04-16 17:49:39 +00:00
|
|
|
|
, Just $ sshDirectory sshdata
|
|
|
|
|
]
|
|
|
|
|
safe c
|
|
|
|
|
| isAlphaNum c = True
|
|
|
|
|
| c == '_' = True
|
|
|
|
|
| otherwise = False
|
2013-06-25 19:23:46 +00:00
|
|
|
|
escape s = replace "%" "." $ escapeURIString safe s
|
2016-07-19 15:37:03 +00:00
|
|
|
|
escapehostname s
|
|
|
|
|
| all (\c -> c == '.' || safe c) s = s
|
|
|
|
|
| otherwise = '.' : escape s
|
2012-09-13 20:47:44 +00:00
|
|
|
|
|
2013-04-16 17:49:39 +00:00
|
|
|
|
{- Extracts the real hostname from a mangled ssh hostname. -}
|
2012-09-13 20:47:44 +00:00
|
|
|
|
unMangleSshHostName :: String -> String
|
2017-01-31 22:40:42 +00:00
|
|
|
|
unMangleSshHostName h = case splitc '-' h of
|
2016-07-19 15:37:03 +00:00
|
|
|
|
("git":"annex":rest) -> unescape (intercalate "-" (beginning rest))
|
2013-04-16 17:49:39 +00:00
|
|
|
|
_ -> h
|
2016-07-19 15:37:03 +00:00
|
|
|
|
where
|
|
|
|
|
unescape ('.':s) = unEscapeString (replace "." "%" s)
|
|
|
|
|
unescape s = s
|
2012-09-11 01:55:59 +00:00
|
|
|
|
|
|
|
|
|
{- Does ssh have known_hosts data for a hostname? -}
|
|
|
|
|
knownHost :: Text -> IO Bool
|
|
|
|
|
knownHost hostname = do
|
|
|
|
|
sshdir <- sshDir
|
|
|
|
|
ifM (doesFileExist $ sshdir </> "known_hosts")
|
2012-09-27 15:27:16 +00:00
|
|
|
|
( not . null <$> checkhost
|
2012-09-11 01:55:59 +00:00
|
|
|
|
, return False
|
|
|
|
|
)
|
2012-10-31 06:34:03 +00:00
|
|
|
|
where
|
|
|
|
|
{- ssh-keygen -F can crash on some old known_hosts file -}
|
|
|
|
|
checkhost = catchDefaultIO "" $
|
|
|
|
|
readProcess "ssh-keygen" ["-F", T.unpack hostname]
|