Skip to content

Commit

Permalink
Merge pull request #64 from vst/vst/various-improvements-fixes-and-ch…
Browse files Browse the repository at this point in the history
…ores

Various Improvements, Fixes and Chores
  • Loading branch information
vst authored Apr 13, 2024
2 parents e782b49 + 096dda8 commit 993fc48
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 60 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ plain host name. The configuration file looks like as follows:
## config.yaml
## List of known SSH public keys for all hosts.
knownSshKeys:
- gh:some-github-user
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKq9bpy0IIfDnlgaTCQk0YhKyKFqInRjoqeIPlBuiFwS test-key-admin

## List of hosts to patrol
Expand All @@ -119,6 +120,7 @@ hosts:
cost: 50
## List of known SSH public keys for the host (optional)
knownSshKeys:
- gh:another-github-user
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGmlBxUagOqtWcW6B77TUL8li85ZNYx0tphd3TSx4SEB test-key-tenant
- name: otherhost
url: https://internal.documentation/hosts/otherhost
Expand Down
2 changes: 2 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## List of known SSH public keys for all hosts.
knownSshKeys:
- gh:some-github-user
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKq9bpy0IIfDnlgaTCQk0YhKyKFqInRjoqeIPlBuiFwS test-key-admin

## List of hosts to patrol
Expand All @@ -26,6 +27,7 @@ hosts:
cost: 50
## List of known SSH public keys for the host (optional)
knownSshKeys:
- gh:another-github-user
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGmlBxUagOqtWcW6B77TUL8li85ZNYx0tphd3TSx4SEB test-key-tenant
- name: otherhost
url: https://internal.documentation/hosts/otherhost
Expand Down
80 changes: 67 additions & 13 deletions src/Lhp/Remote.hs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ compileReport
-> m Types.Report
compileReport par Config.Config {..} = do
_reportHosts <- reporter _configHosts
_reportKnownSshKeys <- mapM parseSshPublicKey _configKnownSshKeys
_reportKnownSshKeys <- concat <$> mapM parseSshPublicKeys _configKnownSshKeys
pure Types.Report {..}
where
reporter = bool (fmap catMaybes . mapM go) (MP.mapM compileHostReport) par
Expand Down Expand Up @@ -74,7 +74,8 @@ compileHostReport ch = do
_hostReportKernel <- _mkKernel _hostName kvs
_hostReportDistribution <- _mkDistribution _hostName kvs
_hostReportDockerContainers <- _fetchHostDockerContainers h
_hostReportAuthorizedSshKeys <- _fetchHostAuthorizedSshKeys h >>= mapM parseSshPublicKey
_hostReportPublicSshHostKeys <- _fetchHostPublicSshHostKeys h >>= fmap concat . mapM parseSshPublicKeys
_hostReportAuthorizedSshKeys <- _fetchHostAuthorizedSshKeys h >>= fmap concat . mapM parseSshPublicKeys
_hostReportSystemdServices <- _fetchHostSystemdServices h
_hostReportSystemdTimers <- _fetchHostSystemdTimers h
pure Types.HostReport {..}
Expand All @@ -94,7 +95,7 @@ _makeHostFromConfigHostSpec Config.HostSpec {..} =
_hostTags = _hostSpecTags
_hostData = _hostSpecData
in do
_hostKnownSshKeys <- mapM parseSshPublicKey _hostSpecKnownSshKeys
_hostKnownSshKeys <- concat <$> mapM parseSshPublicKeys _hostSpecKnownSshKeys
pure Types.Host {..}


Expand Down Expand Up @@ -169,6 +170,19 @@ _fetchHostDockerContainers [email protected] {..} =
Right sv -> pure sv


-- | Attempts to find and return all public SSH host keys on the remote
-- host.
_fetchHostPublicSshHostKeys
:: MonadIO m
=> MonadError LhpError m
=> Types.Host
-> m [T.Text]
_fetchHostPublicSshHostKeys h@Types.Host {..} =
filter (not . T.null) . fmap T.strip . T.lines . Z.Text.unsafeTextFromBL <$> prog
where
prog = _toSshError _hostName (Z.Ssh.runScript (getHostSshConfig h) $(embedStringFile "src/scripts/ssh-host-keys.sh") ["bash"])


-- | Attempts to find and return all SSH authorized keys on the remote
-- host.
_fetchHostAuthorizedSshKeys
Expand Down Expand Up @@ -417,16 +431,38 @@ _toSshError h =
_modifyError (LhpErrorSsh h)


-- | Creates 'Types.SshPublicKey' from given 'T.Text' using ssh-keygen.
-- | Creates list of 'Types.SshPublicKey' from given 'T.Text' using @ssh-keygen@.
--
-- If the given 'T.Text' is a GitHub username, it will attempt to
-- fetch keys from GitHub and then parse them using @ssh-keygen@.
--
-- >>> runExceptT $ parseSshPublicKey "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdd2ubdTn5LPsN0zaxylrpkQTW+1Vr/uWQaEQXoGkd3"
-- Right (SshPublicKey {_sshPublicKeyData = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdd2ubdTn5LPsN0zaxylrpkQTW+1Vr/uWQaEQXoGkd3", _sshPublicKeyType = "ED25519", _sshPublicKeyLength = 256, _sshPublicKeyComment = "no comment", _sshPublicKeyFingerprint = "MD5:ec:4b:ff:8d:c7:43:a9:ab:16:9f:0d:fa:8f:e2:6f:6c"})
-- >>> runExceptT $ parseSshPublicKey "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdd2ubdTn5LPsN0zaxylrpkQTW+1Vr/uWQaEQXoGkd3 comment"
-- Right (SshPublicKey {_sshPublicKeyData = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdd2ubdTn5LPsN0zaxylrpkQTW+1Vr/uWQaEQXoGkd3 comment", _sshPublicKeyType = "ED25519", _sshPublicKeyLength = 256, _sshPublicKeyComment = "comment", _sshPublicKeyFingerprint = "MD5:ec:4b:ff:8d:c7:43:a9:ab:16:9f:0d:fa:8f:e2:6f:6c"})
-- >>> runExceptT $ parseSshPublicKey "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdd2ubdTn5LPsN0zaxylrpkQTW+1Vr/uWQaEQXoGkd3 some more comment"
-- Right (SshPublicKey {_sshPublicKeyData = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdd2ubdTn5LPsN0zaxylrpkQTW+1Vr/uWQaEQXoGkd3 some more comment", _sshPublicKeyType = "ED25519", _sshPublicKeyLength = 256, _sshPublicKeyComment = "some more comment", _sshPublicKeyFingerprint = "MD5:ec:4b:ff:8d:c7:43:a9:ab:16:9f:0d:fa:8f:e2:6f:6c"})
-- >>> runExceptT $ parseSshPublicKey "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdd2ubdTn5LPsN0zaxylrpkQTW+1Vr/uWQaEQXoGkd3 some more comment"
-- Right (SshPublicKey {_sshPublicKeyData = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdd2ubdTn5LPsN0zaxylrpkQTW+1Vr/uWQaEQXoGkd3 some more comment", _sshPublicKeyType = "ED25519", _sshPublicKeyLength = 256, _sshPublicKeyComment = "some more comment", _sshPublicKeyFingerprint = "MD5:ec:4b:ff:8d:c7:43:a9:ab:16:9f:0d:fa:8f:e2:6f:6c"})
-- >>> runExceptT $ parseSshPublicKeys "gh:vst"
-- Right [SshPublicKey {_sshPublicKeyData = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJIQtEmoHu44pUDwX5GEw20JLmfZaI+xVXin74GI396z", _sshPublicKeyType = "ED25519", _sshPublicKeyLength = 256, _sshPublicKeyComment = "gh:vst", _sshPublicKeyFingerprint = "MD5:01:6d:4f:ca:c9:ca:dc:f1:cb:a3:fc:74:8e:34:77:16"},SshPublicKey {_sshPublicKeyData = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdd2ubdTn5LPsN0zaxylrpkQTW+1Vr/uWQaEQXoGkd3", _sshPublicKeyType = "ED25519", _sshPublicKeyLength = 256, _sshPublicKeyComment = "gh:vst", _sshPublicKeyFingerprint = "MD5:ec:4b:ff:8d:c7:43:a9:ab:16:9f:0d:fa:8f:e2:6f:6c"}]
-- >>> runExceptT $ parseSshPublicKeys "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdd2ubdTn5LPsN0zaxylrpkQTW+1Vr/uWQaEQXoGkd3"
-- Right [SshPublicKey {_sshPublicKeyData = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdd2ubdTn5LPsN0zaxylrpkQTW+1Vr/uWQaEQXoGkd3", _sshPublicKeyType = "ED25519", _sshPublicKeyLength = 256, _sshPublicKeyComment = "no comment", _sshPublicKeyFingerprint = "MD5:ec:4b:ff:8d:c7:43:a9:ab:16:9f:0d:fa:8f:e2:6f:6c"}]
-- >>> runExceptT $ parseSshPublicKeys "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdd2ubdTn5LPsN0zaxylrpkQTW+1Vr/uWQaEQXoGkd3 comment"
-- Right [SshPublicKey {_sshPublicKeyData = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdd2ubdTn5LPsN0zaxylrpkQTW+1Vr/uWQaEQXoGkd3 comment", _sshPublicKeyType = "ED25519", _sshPublicKeyLength = 256, _sshPublicKeyComment = "comment", _sshPublicKeyFingerprint = "MD5:ec:4b:ff:8d:c7:43:a9:ab:16:9f:0d:fa:8f:e2:6f:6c"}]
-- >>> runExceptT $ parseSshPublicKeys "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdd2ubdTn5LPsN0zaxylrpkQTW+1Vr/uWQaEQXoGkd3 some more comment"
-- Right [SshPublicKey {_sshPublicKeyData = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdd2ubdTn5LPsN0zaxylrpkQTW+1Vr/uWQaEQXoGkd3 some more comment", _sshPublicKeyType = "ED25519", _sshPublicKeyLength = 256, _sshPublicKeyComment = "some more comment", _sshPublicKeyFingerprint = "MD5:ec:4b:ff:8d:c7:43:a9:ab:16:9f:0d:fa:8f:e2:6f:6c"}]
-- >>> runExceptT $ parseSshPublicKeys "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdd2ubdTn5LPsN0zaxylrpkQTW+1Vr/uWQaEQXoGkd3 some more comment"
-- Right [SshPublicKey {_sshPublicKeyData = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdd2ubdTn5LPsN0zaxylrpkQTW+1Vr/uWQaEQXoGkd3 some more comment", _sshPublicKeyType = "ED25519", _sshPublicKeyLength = 256, _sshPublicKeyComment = "some more comment", _sshPublicKeyFingerprint = "MD5:ec:4b:ff:8d:c7:43:a9:ab:16:9f:0d:fa:8f:e2:6f:6c"}]
parseSshPublicKeys
:: MonadError LhpError m
=> MonadIO m
=> T.Text
-> m [Types.SshPublicKey]
parseSshPublicKeys s = do
let gh = "gh:"
if T.isPrefixOf gh s
then do
let u = T.drop (T.length gh) s
ks <- listGitHubSshKeys u
fmap (\x -> x {Types._sshPublicKeyComment = s}) <$> mapM parseSshPublicKey ks
else List.singleton <$> parseSshPublicKey s


-- | Attempts to create 'Types.SshPublicKey' from given SSH public key
-- represented as 'T.Text' using @ssh-keygen@.
parseSshPublicKey
:: MonadError LhpError m
=> MonadIO m
Expand All @@ -435,7 +471,7 @@ parseSshPublicKey
parseSshPublicKey s = do
(ec, out, err) <- TP.readProcess process
case ec of
ExitFailure _ -> throwUnknown (Z.Text.unsafeTextFromBL err)
ExitFailure _ -> throwUnknown (Z.Text.unsafeTextFromBL err <> ". Input was: " <> s)
ExitSuccess -> case T.words (Z.Text.unsafeTextFromBL out) of
(l : fp : r) ->
pure $
Expand All @@ -451,3 +487,21 @@ parseSshPublicKey s = do
throwUnknown = throwError . LhpErrorUnknown
stdin = TP.byteStringInput (Z.Text.blFromText s)
process = TP.setStdin stdin (TP.proc "ssh-keygen" ["-E", "md5", "-l", "-f", "-"])


-- | Attempts to get the list of SSH public keys from GitHub for a
-- given GitHub username.
listGitHubSshKeys
:: MonadError LhpError m
=> MonadIO m
=> T.Text
-> m [T.Text]
listGitHubSshKeys u = do
(ec, out, err) <- TP.readProcess process
case ec of
ExitFailure _ -> throwUnknown (Z.Text.unsafeTextFromBL err)
ExitSuccess -> pure (toKeys out)
where
throwUnknown = throwError . LhpErrorUnknown
process = TP.proc "curl" ["-s", "https://github.com/" <> T.unpack u <> ".keys"]
toKeys = filter (not . T.null) . T.lines . Z.Text.unsafeTextFromBL
2 changes: 2 additions & 0 deletions src/Lhp/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ data HostReport = HostReport
, _hostReportKernel :: !Kernel
, _hostReportDistribution :: !Distribution
, _hostReportDockerContainers :: !(Maybe [DockerContainer])
, _hostReportPublicSshHostKeys :: ![SshPublicKey]
, _hostReportAuthorizedSshKeys :: ![SshPublicKey]
, _hostReportSystemdServices :: ![T.Text]
, _hostReportSystemdTimers :: ![T.Text]
Expand All @@ -108,6 +109,7 @@ instance ADC.HasCodec HostReport where
<*> ADC.requiredField "kernel" "Kernel information." ADC..= _hostReportKernel
<*> ADC.requiredField "distribution" "Distribution information." ADC..= _hostReportDistribution
<*> ADC.requiredField "dockerContainers" "List of Docker containers if the host is a Docker host." ADC..= _hostReportDockerContainers
<*> ADC.requiredField "publicSshHostKeys" "List of public SSH host keys found on host." ADC..= _hostReportPublicSshHostKeys
<*> ADC.requiredField "authorizedSshKeys" "List of authorized SSH public keys found on host." ADC..= _hostReportAuthorizedSshKeys
<*> ADC.requiredField "systemdServices" "List of systemd services found on host." ADC..= _hostReportSystemdServices
<*> ADC.requiredField "systemdTimers" "List of systemd timers found on host." ADC..= _hostReportSystemdTimers
Expand Down
14 changes: 14 additions & 0 deletions src/scripts/ssh-host-keys.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env sh

###################
# SHELL BEHAVIOUR #
###################

# Stop on errors:
set -e

#############
# PROCEDURE #
#############

find "/etc/ssh" -iname 'ssh_host_*.pub' -exec cat {} \;
4 changes: 3 additions & 1 deletion website/src/components/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ export function BigSpinner({ label }: { label?: string }) {
export function KVBox({
title,
kvs,
...rest
}: {
title: string;
kvs: { key: string; value: React.ReactNode | string | number | null | undefined }[];
[prop: string]: any;
}) {
return (
<Card radius="sm" shadow="sm">
<Card radius="sm" shadow="sm" {...rest}>
<CardHeader className="text-lg font-bold">{title}</CardHeader>

<CardBody>
Expand Down
Loading

0 comments on commit 993fc48

Please sign in to comment.