diff --git a/README.md b/README.md index 0b193e4..643161f 100644 --- a/README.md +++ b/README.md @@ -91,12 +91,9 @@ plain host name. The configuration file looks like as follows: ```yaml ## config.yaml -## List of known SSH public keys to be added to the report. -## -## These can be then used by external programs of lhp Web UI to -## highlight if a host has an unknown authorized SSH public key. +## List of known SSH public keys for all hosts. knownSshKeys: - - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKq9bpy0IIfDnlgaTCQk0YhKyKFqInRjoqeIPlBuiFwS testing + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKq9bpy0IIfDnlgaTCQk0YhKyKFqInRjoqeIPlBuiFwS test-key-admin ## List of hosts to patrol hosts: @@ -120,6 +117,9 @@ hosts: data: owner: Client-1 cost: 50 + ## List of known SSH public keys for the host (optional) + knownSshKeys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGmlBxUagOqtWcW6B77TUL8li85ZNYx0tphd3TSx4SEB test-key-tenant - name: otherhost url: https://internal.documentation/hosts/otherhost tags: diff --git a/config.yaml b/config.yaml index 820d705..b5ca4ec 100644 --- a/config.yaml +++ b/config.yaml @@ -1,9 +1,6 @@ -## List of known SSH public keys to be added to the report. -## -## These can be then used by external programs of lhp Web UI to -## highlight if a host has an unknown authorized SSH public key. +## List of known SSH public keys for all hosts. knownSshKeys: - - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKq9bpy0IIfDnlgaTCQk0YhKyKFqInRjoqeIPlBuiFwS testing + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKq9bpy0IIfDnlgaTCQk0YhKyKFqInRjoqeIPlBuiFwS test-key-admin ## List of hosts to patrol hosts: @@ -27,6 +24,9 @@ hosts: data: owner: Client-1 cost: 50 + ## List of known SSH public keys for the host (optional) + knownSshKeys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGmlBxUagOqtWcW6B77TUL8li85ZNYx0tphd3TSx4SEB test-key-tenant - name: otherhost url: https://internal.documentation/hosts/otherhost tags: diff --git a/src/Lhp/Cli.hs b/src/Lhp/Cli.hs index e59c790..761056a 100644 --- a/src/Lhp/Cli.hs +++ b/src/Lhp/Cli.hs @@ -16,7 +16,6 @@ import qualified Lhp.Config as Config import qualified Lhp.Meta as Meta import Lhp.Remote (compileReport) import Lhp.Types (Report) -import qualified Lhp.Types as Types import Options.Applicative ((<|>)) import qualified Options.Applicative as OA import System.Exit (ExitCode (..)) @@ -82,13 +81,14 @@ doCompile cpath dests par = do Right sr -> BLC.putStrLn (Aeson.encode sr) >> pure ExitSuccess where _mkHost d = - Types.Host - { Types._hostName = d - , Types._hostSsh = Nothing - , Types._hostId = Nothing - , Types._hostUrl = Nothing - , Types._hostTags = [] - , Types._hostData = Aeson.Null + Config.HostSpec + { Config._hostSpecName = d + , Config._hostSpecSsh = Nothing + , Config._hostSpecId = Nothing + , Config._hostSpecUrl = Nothing + , Config._hostSpecTags = [] + , Config._hostSpecData = Aeson.Null + , Config._hostSpecKnownSshKeys = [] } diff --git a/src/Lhp/Config.hs b/src/Lhp/Config.hs index 5f42703..d2c2df0 100644 --- a/src/Lhp/Config.hs +++ b/src/Lhp/Config.hs @@ -11,12 +11,12 @@ import qualified Data.Aeson as Aeson import qualified Data.Text as T import qualified Data.Yaml as Yaml import GHC.Generics (Generic) -import qualified Lhp.Types as Types +import Zamazingo.Ssh (SshConfig) -- | Data definition for application configuration. data Config = Config - { _configHosts :: ![Types.Host] + { _configHosts :: ![HostSpec] , _configKnownSshKeys :: ![T.Text] } deriving (Eq, Generic, Show) @@ -31,7 +31,37 @@ instance ADC.HasCodec Config where ADC.object "Config" $ Config <$> ADC.optionalFieldWithDefault "hosts" [] "List of hosts." ADC..= _configHosts - <*> ADC.optionalFieldWithDefault "knownSshKeys" [] "List of hosts." ADC..= _configKnownSshKeys + <*> ADC.optionalFieldWithDefault "knownSshKeys" [] "Known SSH public keys for all hosts." ADC..= _configKnownSshKeys + + +-- | Data definition for host specification. +data HostSpec = HostSpec + { _hostSpecName :: !T.Text + , _hostSpecSsh :: !(Maybe SshConfig) + , _hostSpecId :: !(Maybe T.Text) + , _hostSpecUrl :: !(Maybe T.Text) + , _hostSpecTags :: ![T.Text] + , _hostSpecData :: !Aeson.Value + , _hostSpecKnownSshKeys :: ![T.Text] + } + deriving (Eq, Generic, Show) + deriving (Aeson.FromJSON, Aeson.ToJSON) via (ADC.Autodocodec HostSpec) + + +instance ADC.HasCodec HostSpec where + codec = + _codec ADC.> "Host Specification" + where + _codec = + ADC.object "HostSpec" $ + HostSpec + <$> ADC.requiredField "name" "Name of the host." ADC..= _hostSpecName + <*> ADC.optionalField "ssh" "SSH configuration." ADC..= _hostSpecSsh + <*> ADC.optionalField "id" "External identifier of the host." ADC..= _hostSpecId + <*> ADC.optionalField "url" "URL to external host information." ADC..= _hostSpecUrl + <*> ADC.optionalFieldWithDefault "tags" [] "Arbitrary tags for the host." ADC..= _hostSpecTags + <*> ADC.optionalFieldWithDefault "data" Aeson.Null "Arbitrary data for the host." ADC..= _hostSpecData + <*> ADC.optionalFieldWithDefault "knownSshKeys" [] "Known SSH public keys for the host." ADC..= _hostSpecKnownSshKeys -- | Attempts to read a configuration file and return 'Config'. diff --git a/src/Lhp/Remote.hs b/src/Lhp/Remote.hs index 1c5fca9..dba0220 100644 --- a/src/Lhp/Remote.hs +++ b/src/Lhp/Remote.hs @@ -48,8 +48,8 @@ compileReport par Config.Config {..} = do pure Types.Report {..} where reporter = bool (fmap catMaybes . mapM go) (MP.mapM compileHostReport) par - go h@Types.Host {..} = do - liftIO (hPutStrLn stderr ("Patrolling " <> T.unpack _hostName)) + go h@Config.HostSpec {..} = do + liftIO (hPutStrLn stderr ("Patrolling " <> T.unpack _hostSpecName)) res <- runExceptT (compileHostReport h) case res of Left err -> liftIO (BLC.hPutStrLn stderr (Aeson.encode err) >> pure Nothing) @@ -61,9 +61,10 @@ compileReport par Config.Config {..} = do compileHostReport :: MonadIO m => MonadError LhpError m - => Types.Host + => Config.HostSpec -> m Types.HostReport -compileHostReport h@Types.Host {..} = do +compileHostReport ch = do + h@Types.Host {..} <- _makeHostFromConfigHostSpec ch kvs <- (++) <$> _fetchHostInfo h <*> _fetchHostCloudInfo h let _hostReportHost = h _hostReportHostname <- _toParseError _hostName $ _getParse pure "LHP_GENERAL_HOSTNAME" kvs @@ -79,6 +80,24 @@ compileHostReport h@Types.Host {..} = do pure Types.HostReport {..} +-- | Consumes a 'Config.HostSpec' and produces a 'Types.Host'. +_makeHostFromConfigHostSpec + :: MonadError LhpError m + => MonadIO m + => Config.HostSpec + -> m Types.Host +_makeHostFromConfigHostSpec Config.HostSpec {..} = + let _hostName = _hostSpecName + _hostSsh = _hostSpecSsh + _hostId = _hostSpecId + _hostUrl = _hostSpecUrl + _hostTags = _hostSpecTags + _hostData = _hostSpecData + in do + _hostKnownSshKeys <- mapM parseSshPublicKey _hostSpecKnownSshKeys + pure Types.Host {..} + + -- * Errors diff --git a/src/Lhp/Types.hs b/src/Lhp/Types.hs index 240d476..2ec7166 100644 --- a/src/Lhp/Types.hs +++ b/src/Lhp/Types.hs @@ -50,6 +50,7 @@ data Host = Host , _hostUrl :: !(Maybe T.Text) , _hostTags :: ![T.Text] , _hostData :: !Aeson.Value + , _hostKnownSshKeys :: ![SshPublicKey] } deriving (Eq, Generic, Show) deriving (Aeson.FromJSON, Aeson.ToJSON) via (ADC.Autodocodec Host) @@ -68,6 +69,7 @@ instance ADC.HasCodec Host where <*> ADC.optionalField "url" "URL to external host information." ADC..= _hostUrl <*> ADC.optionalFieldWithDefault "tags" [] "Arbitrary tags for the host." ADC..= _hostTags <*> ADC.optionalFieldWithDefault "data" Aeson.Null "Arbitrary data for the host." ADC..= _hostData + <*> ADC.optionalFieldWithDefault "knownSshKeys" [] "Known SSH public keys for the host." ADC..= _hostKnownSshKeys -- * Host Report diff --git a/website/src/components/report/App.tsx b/website/src/components/report/App.tsx index e8ce4ea..74a4d29 100644 --- a/website/src/components/report/App.tsx +++ b/website/src/components/report/App.tsx @@ -84,7 +84,7 @@ export function TabShowHostDetails({