diff --git a/src/Lhp/Remote.hs b/src/Lhp/Remote.hs index 501dde6..3592530 100644 --- a/src/Lhp/Remote.hs +++ b/src/Lhp/Remote.hs @@ -41,6 +41,7 @@ compileReport h@Types.Host {..} = do _reportKernel <- _mkKernel _hostName kvs _reportDistribution <- _mkDistribution _hostName kvs _reportDockerContainers <- _fetchHostDockerContainers _hostName + _reportSshAuthorizedKeys <- _fetchHostSshAuthorizedKeys _hostName pure Types.Report {..} @@ -104,6 +105,19 @@ _fetchHostDockerContainers h = Right sv -> pure sv +-- | Attempts to find and return all SSH authorized keys on the remote +-- host. +_fetchHostSshAuthorizedKeys + :: MonadIO m + => MonadError LhpError m + => Z.Ssh.Destination + -> m [T.Text] +_fetchHostSshAuthorizedKeys h = + filter (not . T.null . T.strip) . T.lines . Z.Text.unsafeTextFromBL <$> prog + where + prog = _toSshError h (Z.Ssh.runScript h $(embedStringFile "src/scripts/ssh-keys.sh") ["bash"]) + + -- | Smart constructor for remote host cloud information. _mkCloud :: MonadError LhpError m diff --git a/src/Lhp/Types.hs b/src/Lhp/Types.hs index b3a3b1f..fdbf35b 100644 --- a/src/Lhp/Types.hs +++ b/src/Lhp/Types.hs @@ -50,6 +50,7 @@ data Report = Report , _reportKernel :: !Kernel , _reportDistribution :: !Distribution , _reportDockerContainers :: !(Maybe [DockerContainer]) + , _reportSshAuthorizedKeys :: ![T.Text] } deriving (Eq, Generic, Show) deriving (Aeson.FromJSON, Aeson.ToJSON) via (ADC.Autodocodec Report) @@ -68,6 +69,7 @@ instance ADC.HasCodec Report where <*> ADC.requiredField "kernel" "Kernel information." ADC..= _reportKernel <*> ADC.requiredField "distribution" "Distribution information." ADC..= _reportDistribution <*> ADC.requiredField "dockerContainers" "List of Docker containers if the host is a Docker host." ADC..= _reportDockerContainers + <*> ADC.requiredField "sshAuthorizedKeys" "List of SSH authorized keys found on host." ADC..= _reportSshAuthorizedKeys -- * Cloud Information diff --git a/src/scripts/ssh-keys.sh b/src/scripts/ssh-keys.sh new file mode 100644 index 0000000..9036aca --- /dev/null +++ b/src/scripts/ssh-keys.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env sh + +################### +# SHELL BEHAVIOUR # +################### + +# Stop on errors: +set -e + +############# +# PROCEDURE # +############# + +find \ + /etc/ssh/authorized_keys.d/* \ + $(cut -f6 -d ':' /etc/passwd | sort | uniq | xargs -I{} echo "{}/.ssh/authorized_keys") \ + $(cut -f6 -d ':' /etc/passwd | sort | uniq | xargs -I{} echo "{}/.ssh/authorized_keys2") \ + 2>/dev/null | + sort -u | + xargs -I{} cat {} | + xargs -L1 echo | + grep -vE "^#" | + sort -u | + tr -s ' ' diff --git a/website/package-lock.json b/website/package-lock.json index 87c6b4e..e8cabf0 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -15,7 +15,8 @@ "next": "14.1.3", "purify-ts": "^2.0.3", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "react-toastify": "^10.0.5" }, "devDependencies": { "@types/node": "^20", @@ -6486,6 +6487,26 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-toastify": { + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz", + "integrity": "sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==", + "dependencies": { + "clsx": "^2.1.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-toastify/node_modules/clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "engines": { + "node": ">=6" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/website/package.json b/website/package.json index 4c46145..1c21ca3 100644 --- a/website/package.json +++ b/website/package.json @@ -17,7 +17,8 @@ "next": "14.1.3", "purify-ts": "^2.0.3", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "react-toastify": "^10.0.5" }, "devDependencies": { "@types/node": "^20", diff --git a/website/src/app/page.tsx b/website/src/app/page.tsx index 5f1b885..efcae9d 100644 --- a/website/src/app/page.tsx +++ b/website/src/app/page.tsx @@ -1,5 +1,8 @@ import { AppMain } from '@/components/app'; import Header from '@/components/header'; +import { ToastContainer } from 'react-toastify'; + +import 'react-toastify/dist/ReactToastify.css'; export default function Home() { return ( @@ -9,6 +12,8 @@ export default function Home() {
+ + ); } diff --git a/website/src/components/app/-app.tsx b/website/src/components/app/-app.tsx index 953df62..7278c7c 100644 --- a/website/src/components/app/-app.tsx +++ b/website/src/components/app/-app.tsx @@ -6,6 +6,7 @@ import Image from 'next/image'; import Link from 'next/link'; import { Just, Maybe, Nothing } from 'purify-ts/Maybe'; import { useState } from 'react'; +import { toast } from 'react-toastify'; import { LhpData } from './-data'; import { KVBox } from './-ui'; @@ -125,6 +126,9 @@ export function TabulateHosts({ hosts, onHostSelect }: { hosts: LhpData[]; onHos Docker + + SSH Keys + Tags @@ -170,6 +174,7 @@ export function TabulateHosts({ hosts, onHostSelect }: { hosts: LhpData[]; onHos ? '❌' : `${host.dockerContainers.filter((x) => x.running).length} / ${host.dockerContainers.length}`} + {host.sshAuthorizedKeys.length} {(host.host.tags || []).map((x) => ( @@ -186,6 +191,8 @@ export function TabulateHosts({ hosts, onHostSelect }: { hosts: LhpData[]; onHos } export function HostDetails({ host }: { host: LhpData }) { + const sshkeys = host.sshAuthorizedKeys.map((x) => [x, ...x.split(' ')]); + return (

@@ -251,6 +258,32 @@ export function HostDetails({ host }: { host: LhpData }) { />

+
+ + Authorized SSH Keys + + + No authorized SSH keys are found. Sounds weird?} + > + {([sshkey, sshkeyType, _sshkeyContent, ...sshkeyComment]) => ( + { + navigator.clipboard.writeText(sshkey); + toast('SSH Key is copied to clipboard.'); + }} + > + {`${sshkeyType} ${sshkeyComment.join(' ') || ''}`} + + )} + + + +
+
Docker Containers diff --git a/website/src/components/app/-data.tsx b/website/src/components/app/-data.tsx index fb286ed..64348d6 100644 --- a/website/src/components/app/-data.tsx +++ b/website/src/components/app/-data.tsx @@ -120,8 +120,13 @@ export const LHP_PATROL_REPORT_SCHEMA = { required: ['os', 'machine', 'version', 'release', 'name', 'node'], type: 'object', }, + sshAuthorizedKeys: { + $comment: 'List of SSH authorized keys found on host.', + items: { type: 'string' }, + type: 'array', + }, }, - required: ['dockerContainers', 'distribution', 'kernel', 'hardware', 'cloud', 'host'], + required: ['sshAuthorizedKeys', 'dockerContainers', 'distribution', 'kernel', 'hardware', 'cloud', 'host'], type: 'object', } as const satisfies JSONSchema;