add credential storage support for external special remotes & update example
This commit is contained in:
parent
8c6fd00476
commit
445b7b41b9
5 changed files with 96 additions and 22 deletions
24
Creds.hs
24
Creds.hs
|
@ -35,25 +35,27 @@ data CredPairStorage = CredPairStorage
|
||||||
{- Stores creds in a remote's configuration, if the remote allows
|
{- Stores creds in a remote's configuration, if the remote allows
|
||||||
- that. Otherwise, caches them locally. -}
|
- that. Otherwise, caches them locally. -}
|
||||||
setRemoteCredPair :: RemoteConfig -> CredPairStorage -> Annex RemoteConfig
|
setRemoteCredPair :: RemoteConfig -> CredPairStorage -> Annex RemoteConfig
|
||||||
setRemoteCredPair c storage = go =<< getRemoteCredPair c storage
|
setRemoteCredPair c storage =
|
||||||
where
|
maybe (return c) (setRemoteCredPair' c storage)
|
||||||
go (Just creds)
|
=<< getRemoteCredPair c storage
|
||||||
| embedCreds c = case credPairRemoteKey storage of
|
|
||||||
Nothing -> localcache creds
|
|
||||||
Just key -> storeconfig creds key =<< remoteCipher c
|
|
||||||
| otherwise = localcache creds
|
|
||||||
go Nothing = return c
|
|
||||||
|
|
||||||
localcache creds = do
|
setRemoteCredPair' :: RemoteConfig -> CredPairStorage -> CredPair -> Annex RemoteConfig
|
||||||
|
setRemoteCredPair' c storage creds
|
||||||
|
| embedCreds c = case credPairRemoteKey storage of
|
||||||
|
Nothing -> localcache
|
||||||
|
Just key -> storeconfig key =<< remoteCipher c
|
||||||
|
| otherwise = localcache
|
||||||
|
where
|
||||||
|
localcache = do
|
||||||
writeCacheCredPair creds storage
|
writeCacheCredPair creds storage
|
||||||
return c
|
return c
|
||||||
|
|
||||||
storeconfig creds key (Just cipher) = do
|
storeconfig key (Just cipher) = do
|
||||||
s <- liftIO $ encrypt [] cipher
|
s <- liftIO $ encrypt [] cipher
|
||||||
(feedBytes $ L.pack $ encodeCredPair creds)
|
(feedBytes $ L.pack $ encodeCredPair creds)
|
||||||
(readBytes $ return . L.unpack)
|
(readBytes $ return . L.unpack)
|
||||||
return $ M.insert key (toB64 s) c
|
return $ M.insert key (toB64 s) c
|
||||||
storeconfig creds key Nothing =
|
storeconfig key Nothing =
|
||||||
return $ M.insert key (toB64 $ encodeCredPair creds) c
|
return $ M.insert key (toB64 $ encodeCredPair creds) c
|
||||||
|
|
||||||
{- Gets a remote's credpair, from the environment if set, otherwise
|
{- Gets a remote's credpair, from the environment if set, otherwise
|
||||||
|
|
|
@ -22,6 +22,7 @@ import Config.Cost
|
||||||
import Annex.Content
|
import Annex.Content
|
||||||
import Annex.UUID
|
import Annex.UUID
|
||||||
import Annex.Exception
|
import Annex.Exception
|
||||||
|
import Creds
|
||||||
|
|
||||||
import Control.Concurrent.STM
|
import Control.Concurrent.STM
|
||||||
import System.Process (std_in, std_out, std_err)
|
import System.Process (std_in, std_out, std_err)
|
||||||
|
@ -39,7 +40,7 @@ remote = RemoteType {
|
||||||
|
|
||||||
gen :: Git.Repo -> UUID -> RemoteConfig -> RemoteGitConfig -> Annex (Maybe Remote)
|
gen :: Git.Repo -> UUID -> RemoteConfig -> RemoteGitConfig -> Annex (Maybe Remote)
|
||||||
gen r u c gc = do
|
gen r u c gc = do
|
||||||
external <- newExternal externaltype c
|
external <- newExternal externaltype u c
|
||||||
Annex.addCleanup (fromUUID u) $ stopExternal external
|
Annex.addCleanup (fromUUID u) $ stopExternal external
|
||||||
cst <- getCost external r gc
|
cst <- getCost external r gc
|
||||||
return $ Just $ encryptableRemote c
|
return $ Just $ encryptableRemote c
|
||||||
|
@ -76,7 +77,7 @@ externalSetup mu c = do
|
||||||
M.lookup "externaltype" c
|
M.lookup "externaltype" c
|
||||||
c' <- encryptionSetup c
|
c' <- encryptionSetup c
|
||||||
|
|
||||||
external <- newExternal externaltype c'
|
external <- newExternal externaltype u c'
|
||||||
handleRequest external INITREMOTE Nothing $ \resp -> case resp of
|
handleRequest external INITREMOTE Nothing $ \resp -> case resp of
|
||||||
INITREMOTE_SUCCESS -> Just noop
|
INITREMOTE_SUCCESS -> Just noop
|
||||||
INITREMOTE_FAILURE errmsg -> Just $ error errmsg
|
INITREMOTE_FAILURE errmsg -> Just $ error errmsg
|
||||||
|
@ -201,7 +202,7 @@ handleRequest' lck external req mp responsehandler = do
|
||||||
handleRemoteRequest (PROGRESS bytesprocessed) =
|
handleRemoteRequest (PROGRESS bytesprocessed) =
|
||||||
maybe noop (\a -> liftIO $ a bytesprocessed) mp
|
maybe noop (\a -> liftIO $ a bytesprocessed) mp
|
||||||
handleRemoteRequest (DIRHASH k) =
|
handleRemoteRequest (DIRHASH k) =
|
||||||
sendMessage lck external (VALUE $ hashDirMixed k)
|
sendMessage lck external $ VALUE $ hashDirMixed k
|
||||||
handleRemoteRequest (SETCONFIG setting value) =
|
handleRemoteRequest (SETCONFIG setting value) =
|
||||||
liftIO $ atomically $ do
|
liftIO $ atomically $ do
|
||||||
let v = externalConfig external
|
let v = externalConfig external
|
||||||
|
@ -210,12 +211,30 @@ handleRequest' lck external req mp responsehandler = do
|
||||||
handleRemoteRequest (GETCONFIG setting) = do
|
handleRemoteRequest (GETCONFIG setting) = do
|
||||||
value <- fromMaybe "" . M.lookup setting
|
value <- fromMaybe "" . M.lookup setting
|
||||||
<$> liftIO (atomically $ readTMVar $ externalConfig external)
|
<$> liftIO (atomically $ readTMVar $ externalConfig external)
|
||||||
sendMessage lck external (VALUE value)
|
sendMessage lck external $ VALUE value
|
||||||
|
handleRemoteRequest (SETCREDS setting login password) = do
|
||||||
|
c <- liftIO $ atomically $ readTMVar $ externalConfig external
|
||||||
|
c' <- setRemoteCredPair' c (credstorage setting)
|
||||||
|
(login, password)
|
||||||
|
void $ liftIO $ atomically $ swapTMVar (externalConfig external) c'
|
||||||
|
handleRemoteRequest (GETCREDS setting) = do
|
||||||
|
c <- liftIO $ atomically $ readTMVar $ externalConfig external
|
||||||
|
creds <- fromMaybe ("", "") <$>
|
||||||
|
getRemoteCredPair c (credstorage setting)
|
||||||
|
sendMessage lck external $ CREDS (fst creds) (snd creds)
|
||||||
handleRemoteRequest (VERSION _) =
|
handleRemoteRequest (VERSION _) =
|
||||||
sendMessage lck external (ERROR "too late to send VERSION")
|
sendMessage lck external $ ERROR "too late to send VERSION"
|
||||||
|
|
||||||
handleAsyncMessage (ERROR err) = error $ "external special remote error: " ++ err
|
handleAsyncMessage (ERROR err) = error $ "external special remote error: " ++ err
|
||||||
|
|
||||||
|
credstorage setting = CredPairStorage
|
||||||
|
{ credPairFile = base
|
||||||
|
, credPairEnvironment = (base ++ "login", base ++ "password")
|
||||||
|
, credPairRemoteKey = Just setting
|
||||||
|
}
|
||||||
|
where
|
||||||
|
base = replace "/" "_" $ fromUUID (externalUUID external) ++ "-" ++ setting
|
||||||
|
|
||||||
sendMessage :: Sendable m => ExternalLock -> External -> m -> Annex ()
|
sendMessage :: Sendable m => ExternalLock -> External -> m -> Annex ()
|
||||||
sendMessage lck external m =
|
sendMessage lck external m =
|
||||||
fromExternal lck external externalSend $ \h ->
|
fromExternal lck external externalSend $ \h ->
|
||||||
|
|
12
Remote/External/Types.hs
vendored
12
Remote/External/Types.hs
vendored
|
@ -44,6 +44,7 @@ import Control.Concurrent.STM
|
||||||
-- The
|
-- The
|
||||||
data External = External
|
data External = External
|
||||||
{ externalType :: ExternalType
|
{ externalType :: ExternalType
|
||||||
|
, externalUUID :: UUID
|
||||||
-- Empty until the remote is running.
|
-- Empty until the remote is running.
|
||||||
, externalState :: TMVar ExternalState
|
, externalState :: TMVar ExternalState
|
||||||
-- Empty when a remote is in use.
|
-- Empty when a remote is in use.
|
||||||
|
@ -52,9 +53,10 @@ data External = External
|
||||||
, externalConfig :: TMVar RemoteConfig
|
, externalConfig :: TMVar RemoteConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
newExternal :: ExternalType -> RemoteConfig -> Annex External
|
newExternal :: ExternalType -> UUID -> RemoteConfig -> Annex External
|
||||||
newExternal externaltype c = liftIO $ External
|
newExternal externaltype u c = liftIO $ External
|
||||||
<$> pure externaltype
|
<$> pure externaltype
|
||||||
|
<*> pure u
|
||||||
<*> atomically newEmptyTMVar
|
<*> atomically newEmptyTMVar
|
||||||
<*> atomically (newTMVar ExternalLock)
|
<*> atomically (newTMVar ExternalLock)
|
||||||
<*> atomically (newTMVar c)
|
<*> atomically (newTMVar c)
|
||||||
|
@ -157,6 +159,8 @@ data RemoteRequest
|
||||||
| DIRHASH Key
|
| DIRHASH Key
|
||||||
| SETCONFIG Setting String
|
| SETCONFIG Setting String
|
||||||
| GETCONFIG Setting
|
| GETCONFIG Setting
|
||||||
|
| SETCREDS Setting String String
|
||||||
|
| GETCREDS Setting
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
instance Receivable RemoteRequest where
|
instance Receivable RemoteRequest where
|
||||||
|
@ -165,15 +169,19 @@ instance Receivable RemoteRequest where
|
||||||
parseCommand "DIRHASH" = parse1 DIRHASH
|
parseCommand "DIRHASH" = parse1 DIRHASH
|
||||||
parseCommand "SETCONFIG" = parse2 SETCONFIG
|
parseCommand "SETCONFIG" = parse2 SETCONFIG
|
||||||
parseCommand "GETCONFIG" = parse1 GETCONFIG
|
parseCommand "GETCONFIG" = parse1 GETCONFIG
|
||||||
|
parseCommand "SETCREDS" = parse3 SETCREDS
|
||||||
|
parseCommand "GETCREDS" = parse1 GETCREDS
|
||||||
parseCommand _ = parseFail
|
parseCommand _ = parseFail
|
||||||
|
|
||||||
-- Responses to RemoteRequest.
|
-- Responses to RemoteRequest.
|
||||||
data RemoteResponse
|
data RemoteResponse
|
||||||
= VALUE String
|
= VALUE String
|
||||||
|
| CREDS String String
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
instance Sendable RemoteResponse where
|
instance Sendable RemoteResponse where
|
||||||
formatMessage (VALUE s) = [ "VALUE", serialize s ]
|
formatMessage (VALUE s) = [ "VALUE", serialize s ]
|
||||||
|
formatMessage (CREDS login password) = [ "CREDS", serialize login, serialize password ]
|
||||||
|
|
||||||
-- Messages that can be sent at any time by either git-annex or the remote.
|
-- Messages that can be sent at any time by either git-annex or the remote.
|
||||||
data AsyncMessage
|
data AsyncMessage
|
||||||
|
|
|
@ -189,6 +189,23 @@ in control.
|
||||||
can have been set by a previous SETCONFIG. Can be run at any time.
|
can have been set by a previous SETCONFIG. Can be run at any time.
|
||||||
(git-annex replies with VALUE followed by the value. If the setting is
|
(git-annex replies with VALUE followed by the value. If the setting is
|
||||||
not set, the value will be empty.)
|
not set, the value will be empty.)
|
||||||
|
* `SETCREDS Setting User Password`
|
||||||
|
When some form of user and password is needed to access a special remote,
|
||||||
|
this can be used to securely store them for later use.
|
||||||
|
(Like SETCONFIG, this is normally sent only during INITREMOTE.)
|
||||||
|
The Setting indicates which value in a remote's configuration can be
|
||||||
|
used to store the creds.
|
||||||
|
Note that creds are normally only stored in the remote's configuration
|
||||||
|
when it's surely safe to do so; when gpg encryption is used, in which
|
||||||
|
case the creds will be encrypted using it. If creds are not stored in
|
||||||
|
the configuration, they'll only be stored in a local file.
|
||||||
|
(embedcreds can be set to yes by the user or by SETCONFIG to force
|
||||||
|
the creds to be stored in the remote's configuration).
|
||||||
|
* `GETCREDS Setting`
|
||||||
|
Gets any creds that were previously stored in the remote's configuration
|
||||||
|
or a file.
|
||||||
|
(git-annex replies with "CREDS User Password". If no creds are found,
|
||||||
|
User and Password are both empty.)
|
||||||
|
|
||||||
## general messages
|
## general messages
|
||||||
|
|
||||||
|
|
36
doc/special_remotes/external/example.sh
vendored
36
doc/special_remotes/external/example.sh
vendored
|
@ -48,6 +48,32 @@ ask () {
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# This remote doesn't need credentials to access it,
|
||||||
|
# but many of them will. Here's how to handle requiring the user
|
||||||
|
# set MYPASSWORD and MYLOGIN when running initremote. The creds
|
||||||
|
# will be stored securely for later use, so the user only needs
|
||||||
|
# to provide them once.
|
||||||
|
setupcreds () {
|
||||||
|
if [ -z "$MYPASSWORD" ] || [ -z "$MYLOGIN" ]; then
|
||||||
|
echo INITREMOTE-FAILURE "You need to set MYPASSWORD and MYLOGIN environment variables when running initremote."
|
||||||
|
else
|
||||||
|
echo SETCREDS mycreds "$MYLOGIN" "$MYPASSWORD"
|
||||||
|
echo INITREMOTE-SUCCESS
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
getcreds () {
|
||||||
|
echo GETCREDS mycreds
|
||||||
|
read resp
|
||||||
|
case "${resp%% *}" in
|
||||||
|
CREDS)
|
||||||
|
MYLOGIN="$(echo "$resp" | sed 's/^CREDS \([^ ]*\) .*/\1/')"
|
||||||
|
MYPASSWORD="$(echo "$resp" | sed 's/^CREDS [^ ]* //')"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
# This has to come first, to get the protocol started.
|
# This has to come first, to get the protocol started.
|
||||||
echo VERSION 1
|
echo VERSION 1
|
||||||
|
|
||||||
|
@ -66,16 +92,17 @@ while read line; do
|
||||||
# git annex initremote or git annex enableremote is
|
# git annex initremote or git annex enableremote is
|
||||||
# run.)
|
# run.)
|
||||||
|
|
||||||
|
# The directory provided by the user
|
||||||
|
# could be relative; make it absolute,
|
||||||
|
# and store that.
|
||||||
getconfig directory
|
getconfig directory
|
||||||
# Input directory could be relative; make it
|
mydirectory="$(readlink -f "$RET")" || true
|
||||||
# absolute, and store that.
|
|
||||||
mydirectory="$(readlink -f "$RET")"
|
|
||||||
setconfig directory "$mydirectory"
|
setconfig directory "$mydirectory"
|
||||||
if [ -z "$mydirectory" ]; then
|
if [ -z "$mydirectory" ]; then
|
||||||
echo INITREMOTE-FAILURE "You need to set directory="
|
echo INITREMOTE-FAILURE "You need to set directory="
|
||||||
else
|
else
|
||||||
if mkdir -p "$mydirectory"; then
|
if mkdir -p "$mydirectory"; then
|
||||||
echo INITREMOTE-SUCCESS
|
setupcreds
|
||||||
else
|
else
|
||||||
echo INITREMOTE-FAILURE "Failed to write to $mydirectory"
|
echo INITREMOTE-FAILURE "Failed to write to $mydirectory"
|
||||||
fi
|
fi
|
||||||
|
@ -87,6 +114,7 @@ while read line; do
|
||||||
# special remote here.
|
# special remote here.
|
||||||
getconfig directory
|
getconfig directory
|
||||||
mydirectory="$RET"
|
mydirectory="$RET"
|
||||||
|
getcreds
|
||||||
echo PREPARE-SUCCESS
|
echo PREPARE-SUCCESS
|
||||||
;;
|
;;
|
||||||
TRANSFER)
|
TRANSFER)
|
||||||
|
|
Loading…
Reference in a new issue