Implement logging for SSH using monad-logger and fast-logger

This commit is contained in:
fr33domlover 2016-03-09 22:27:25 +00:00
parent 20fb5181cd
commit fc4690324c
3 changed files with 49 additions and 26 deletions

View file

@ -47,6 +47,7 @@ import Network.Wai.Middleware.RequestLogger (Destination (Logger),
mkRequestLogger, outputFormat) mkRequestLogger, outputFormat)
import System.Log.FastLogger (defaultBufSize, newStdoutLoggerSet, import System.Log.FastLogger (defaultBufSize, newStdoutLoggerSet,
toLogStr) toLogStr)
import Yesod.Default.Main (LogFunc)
-- Import all relevant handler modules here. -- Import all relevant handler modules here.
-- Don't forget to add new modules to your cabal file! -- Don't forget to add new modules to your cabal file!
@ -64,6 +65,9 @@ import Vervis.Ssh (runSsh)
-- comments there for more details. -- comments there for more details.
mkYesodDispatch "App" resourcesApp mkYesodDispatch "App" resourcesApp
loggingFunction :: App -> LogFunc
loggingFunction app = messageLoggerSource app (appLogger app)
-- | This function allocates resources (such as a database connection pool), -- | This function allocates resources (such as a database connection pool),
-- performs initialization and returns a foundation datatype value. This is also -- performs initialization and returns a foundation datatype value. This is also
-- the place to put your migrate statements to have automatic database -- the place to put your migrate statements to have automatic database
@ -88,7 +92,7 @@ makeFoundation appSettings = do
-- information, see: -- information, see:
-- https://ocharles.org.uk/blog/posts/2014-12-04-record-wildcards.html -- https://ocharles.org.uk/blog/posts/2014-12-04-record-wildcards.html
tempFoundation = mkFoundation $ error "connPool forced in tempFoundation" tempFoundation = mkFoundation $ error "connPool forced in tempFoundation"
logFunc = messageLoggerSource tempFoundation appLogger logFunc = loggingFunction tempFoundation
-- Create the database connection pool -- Create the database connection pool
pool <- flip runLoggingT logFunc $ createPostgresqlPool pool <- flip runLoggingT logFunc $ createPostgresqlPool
@ -130,9 +134,8 @@ warpSettings foundation =
setPort (appPort $ appSettings foundation) setPort (appPort $ appSettings foundation)
$ setHost (appHost $ appSettings foundation) $ setHost (appHost $ appSettings foundation)
$ setOnException (\_req e -> $ setOnException (\_req e ->
when (defaultShouldDisplayException e) $ messageLoggerSource when (defaultShouldDisplayException e) $ loggingFunction
foundation foundation
(appLogger foundation)
$(qLocation >>= liftLoc) $(qLocation >>= liftLoc)
"yesod" "yesod"
LevelError LevelError
@ -155,6 +158,13 @@ getAppSettings = loadAppSettings [configSettingsYml] [] useEnv
develMain :: IO () develMain :: IO ()
develMain = develMainHelper getApplicationDev develMain = develMainHelper getApplicationDev
sshServer :: App -> IO ()
sshServer foundation =
runSsh
(appSettings foundation)
(appConnPool foundation)
(loggingFunction foundation)
-- | The @main@ function for an executable running this site. -- | The @main@ function for an executable running this site.
appMain :: IO () appMain :: IO ()
appMain = do appMain = do
@ -174,7 +184,7 @@ appMain = do
app <- makeApplication foundation app <- makeApplication foundation
-- [experimental] Run SSH server and pray -- [experimental] Run SSH server and pray
forkIO $ runSsh settings (appConnPool foundation) forkIO $ sshServer foundation
-- Run the application with Warp -- Run the application with Warp
runSettings (warpSettings foundation) app runSettings (warpSettings foundation) app

View file

@ -87,8 +87,5 @@ checkContent =
Left s -> Left $ T.pack s Left s -> Left $ T.pack s
Right b -> Right b Right b -> Right b
--TODO make the above work over ByteString and when passes the check, apply
--base64 conversion. delete my rel4 key from the DB and re-insert...
contentField :: Field Handler ByteString contentField :: Field Handler ByteString
contentField = checkContent bsField contentField = checkContent bsField

View file

@ -22,24 +22,32 @@ import Prelude
import Control.Monad (when) import Control.Monad (when)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
import Control.Monad.Logger
import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Reader (ReaderT (runReaderT), ask) import Control.Monad.Trans.Reader (ReaderT (runReaderT), ask)
import Data.ByteString.Char8 (ByteString, unpack) import Data.ByteString.Char8 (ByteString, unpack)
import Data.ByteString.Lazy (fromStrict) import Data.ByteString.Lazy (fromStrict)
import Data.Foldable (find) import Data.Foldable (find)
import Data.Text (pack) import Data.Text (Text, pack)
import Database.Persist import Database.Persist
import Database.Persist.Sql (SqlBackend, ConnectionPool, runSqlPool) import Database.Persist.Sql (SqlBackend, ConnectionPool, runSqlPool)
import Network.SSH import Network.SSH
import Network.SSH.Channel import Network.SSH.Channel
import Network.SSH.Crypto import Network.SSH.Crypto
import Network.SSH.Session import Network.SSH.Session
import Yesod.Default.Main (LogFunc)
import Vervis.Model import Vervis.Model
import Vervis.Settings import Vervis.Settings
type ChannelB = ReaderT ConnectionPool IO -- TODO:
type SessionB = ReaderT ConnectionPool IO -- [x] Implement serious logging (info, warning, error, etc.) with
-- monad-logger, maybe see how loggin works in the scaffolding
-- [ ] See which git commands darcsden SSH supports and see if I can implement
-- them with Hit (i think it was git upload-pack)
type ChannelB = LoggingT (ReaderT ConnectionPool IO)
type SessionB = LoggingT (ReaderT ConnectionPool IO)
type Backend = SqlBackend type Backend = SqlBackend
type Channel = ChannelT ChannelB type Channel = ChannelT ChannelB
@ -47,14 +55,17 @@ type Session = SessionT SessionB ChannelB
type SshChanDB = ReaderT Backend Channel type SshChanDB = ReaderT Backend Channel
type SshSessDB = ReaderT Backend Session type SshSessDB = ReaderT Backend Session
src :: Text
src = "SSH"
runChanDB :: SshChanDB a -> Channel a runChanDB :: SshChanDB a -> Channel a
runChanDB action = do runChanDB action = do
pool <- lift ask pool <- lift . lift $ ask
runSqlPool action pool runSqlPool action pool
runSessDB :: SshSessDB a -> Session a runSessDB :: SshSessDB a -> Session a
runSessDB action = do runSessDB action = do
pool <- lift ask pool <- lift . lift $ ask
runSqlPool action pool runSqlPool action pool
chanFail :: Bool -> ByteString -> Channel () chanFail :: Bool -> ByteString -> Channel ()
@ -73,7 +84,7 @@ authorize (PublicKey name key) = do
fmap Just $ selectList [SshKeyPerson ==. pid] [] fmap Just $ selectList [SshKeyPerson ==. pid] []
case mkeys of case mkeys of
Nothing -> do Nothing -> do
liftIO $ putStrLn "[SSH] auth failed: invalid user" $logInfoS src "Auth failed: Invalid user"
return False return False
Just keys -> do Just keys -> do
let eValue (Entity _ v) = v let eValue (Entity _ v) = v
@ -81,11 +92,10 @@ authorize (PublicKey name key) = do
(== key) . blobToKey . fromStrict . sshKeyContent . eValue (== key) . blobToKey . fromStrict . sshKeyContent . eValue
case find matches keys of case find matches keys of
Nothing -> do Nothing -> do
liftIO $ $logInfoS src "Auth failed: No matching key found"
putStrLn "[SSH] auth failed: no matching key found"
return False return False
Just match -> do Just match -> do
liftIO $ putStrLn "[SSH] auth succeeded" $logInfoS src "Auth succeeded"
return True return True
handle :: Bool -> ChannelRequest -> Channel () handle :: Bool -> ChannelRequest -> Channel ()
@ -93,28 +103,34 @@ handle wantReply request = do
liftIO $ print request liftIO $ print request
chanFail wantReply "I don't execute any commands yet, come back later" chanFail wantReply "I don't execute any commands yet, come back later"
ready :: IO () ready :: LogFunc -> IO ()
ready = putStrLn "SSH server component running" ready = runLoggingT $ $logInfoS src "SSH server component starting"
mkConfig :: AppSettings -> ConnectionPool -> IO (Config SessionB ChannelB) mkConfig
mkConfig settings pool = do :: AppSettings
-> ConnectionPool
-> LogFunc
-> IO (Config SessionB ChannelB)
mkConfig settings pool logFunc = do
keyPair <- keyPairFromFile $ appSshKeyFile settings keyPair <- keyPairFromFile $ appSshKeyFile settings
return $ Config return $ Config
{ cSession = SessionConfig { cSession = SessionConfig
{ scAuthMethods = ["publickey"] { scAuthMethods = ["publickey"]
, scAuthorize = authorize , scAuthorize = authorize
, scKeyPair = keyPair , scKeyPair = keyPair
, scRunBaseMonad = flip runReaderT pool , scRunBaseMonad =
flip runReaderT pool . flip runLoggingT logFunc
} }
, cChannel = ChannelConfig , cChannel = ChannelConfig
{ ccRequestHandler = handle { ccRequestHandler = handle
, ccRunBaseMonad = flip runReaderT pool , ccRunBaseMonad =
flip runReaderT pool . flip runLoggingT logFunc
} }
, cPort = fromIntegral $ appSshPort settings , cPort = fromIntegral $ appSshPort settings
, cReadyAction = ready , cReadyAction = ready logFunc
} }
runSsh :: AppSettings -> ConnectionPool -> IO () runSsh :: AppSettings -> ConnectionPool -> LogFunc -> IO ()
runSsh settings pool = do runSsh settings pool logFunc = do
config <- mkConfig settings pool config <- mkConfig settings pool logFunc
startConfig config startConfig config