ScanopyScanopy
Integrations

Set up SSH script discovery

Run your own script on a host over SSH and record the system details and interfaces it prints.

An SSH credential logs in to a host, runs a script you write, and records the JSON object the script prints as the host's system details and interfaces.

The script runs as an ordinary SSH session, so it can do any work the account is allowed to do on each scan, such as clearing a cache or restarting a service, as well as reporting details. A script that reports nothing prints {}.

Beta

The SSH integration is in beta. Data collection may be incomplete and the credential fields may change in a future release. Please report anything that looks wrong.

Before you start

SSH credentials are created under Assets > Credentials and can be pointed at NetworkRemote hosts.

Creating a credential, assigning it, and overriding it on an individual host work the same way for every integration — see Creating a credential, Where a credential applies, and Auto-assignment. This guide covers only what is specific to SSH.

What gets discovered

Your script sets what Scanopy records. It prints one JSON object, and Scanopy applies each key in this table to the host or interface field it names:

KeyTypeFillsDescriptionExample
hostnamestringhostnameThe host's own hostname."nas-01"
sys_namestringsys_nameAdministrative name, as SNMP sysName reports it."nas-01.example.lan"
sys_descrstringsys_descrFree-text system description, typically OS and version."Debian GNU/Linux 12 (bookworm) 6.1.0-18-amd64"
sys_object_idstringsys_object_idVendor object identifier, as SNMP sysObjectID reports it."1.3.6.1.4.1.8072.3.2.10"
sys_locationstringsys_locationPhysical location."Rack 2, shelf 3"
sys_contactstringsys_contactContact person or team."[email protected]"
chassis_idstringchassis_idChassis identifier, usually the base MAC address."3c:ec:ef:12:34:56"
manufacturerstringmanufacturerHardware manufacturer."Supermicro"
modelstringmodelHardware model."X11SCL-F"
serial_numberstringserial_numberHardware serial number."ZM19AS012345"
firmware_revisionstringfirmware_revisionFirmware or BIOS version."2.1"
software_revisionstringsoftware_revisionOperating system or software version."12.5"
management_urlstringmanagement_urlURL of the host's management interface."https://nas-01.example.lan:5001"
interfaces[].namestringif_nameInterface name. Interfaces are matched to ones discovery already holds by name, then by MAC."eth0"
interfaces[].descrstringif_descrInterface description, as SNMP ifDescr reports it."Intel Corporation I210 Gigabit"
interfaces[].aliasstringif_aliasOperator-assigned interface label, as SNMP ifAlias reports it."uplink to core-sw-01"
interfaces[].macstringmac_addressInterface MAC address."3c:ec:ef:12:34:57"
interfaces[].speed_bpsintegerspeed_bpsLink speed in bits per second.1000000000
interfaces[].admin_statusstring (enum)admin_statusConfigured state: Up, Down or Testing."Up"
interfaces[].oper_statusstring (enum)oper_statusOperational state: Up, Down, Testing, Unknown, Dormant, NotPresent or LowerLayerDown."Up"

This script reads OS and hardware details on a Linux host from /etc/os-release and DMI:

#!/bin/sh
. /etc/os-release
dmi() { cat "/sys/class/dmi/id/$1" 2>/dev/null; }
printf '{"hostname":"%s","sys_descr":"%s %s","manufacturer":"%s","model":"%s","serial_number":"%s","firmware_revision":"%s","software_revision":"%s"}\n' \
  "$(hostname -f)" "$PRETTY_NAME" "$(uname -r)" \
  "$(dmi sys_vendor)" "$(dmi product_name)" "$(sudo -n cat /sys/class/dmi/id/product_serial)" \
  "$(dmi bios_version)" "$VERSION_ID"

On a Debian 12 server it prints:

{"hostname":"nas-01.example.lan","sys_descr":"Debian GNU/Linux 12 (bookworm) 6.1.0-18-amd64","manufacturer":"Supermicro","model":"X11SCL-F","serial_number":"ZM19AS012345","firmware_revision":"2.1","software_revision":"12"}

To report interfaces, add an interfaces array:

{
  "hostname": "nas-01.example.lan",
  "interfaces": [
    { "name": "eth0", "mac": "3c:ec:ef:12:34:57", "speed_bps": 1000000000, "oper_status": "Up" },
    { "name": "eth1", "alias": "backup network", "oper_status": "Down" }
  ]
}

Top-level keys describe the host. Interface keys sit in objects inside the top-level interfaces array. Scanopy matches each interface to one it already holds by name, then by MAC, and leaves interfaces the script does not print in place.

Scanopy reports keys outside the table under Not applied on the run and does not store them. A blank string counts as no value.

A value from the script outranks what SNMP, LLDP and the other discovery protocols report for the same field, and never replaces a value someone entered in Scanopy. Each value shows SSH script as its source.

Output rules

  • Exactly one JSON object on standard output, at most 64 KiB. Write diagnostics to standard error, which the run shows when the script fails.
  • Exit status 0. Any other status discards the output.
  • Each key has the type the table gives it. speed_bps is a number, not a string, and the status values are case-sensitive (Up, not up). One wrong type rejects the whole document.

Prerequisites

  • A dedicated account on each host. The script runs with that account's permissions, so give it only what the script needs. The Linux example script needs root for one file: /sys/class/dmi/id/product_serial. Grant that single command with visudo -f /etc/sudoers.d/scanopy:

    scanopy ALL=(root) NOPASSWD: /usr/bin/cat /sys/class/dmi/id/product_serial

    Without the rule, sudo -n fails, the serial prints as a blank string, and Scanopy skips it.

  • A private key or a password for that account. For a key, the public half goes in the account's ~/.ssh/authorized_keys. On Windows, a key for an account in the Administrators group goes in C:\ProgramData\ssh\administrators_authorized_keys instead. For a password, the SSH server must allow password authentication.

  • On Windows hosts, OpenSSH Server installed and running. It is an optional Windows feature.

  • The SSH port reachable over TCP from the daemon host. The default is 22.

Choosing a credential type

Credential typeHow it connectsCan be targeted atRequires daemon
SSH KeyBetaConnects over SSH with a private key.NetworkRemote hosts0.17.19 or later
SSH PasswordBetaConnects over SSH with a username and password.NetworkRemote hosts0.17.19 or later

Both types run the same script and apply the same output. SSH Key avoids storing a reusable password and works on servers that turn password login off.

Assign the credential to the hosts the script is written for. Assign it to a whole network only when the account, its key or password, and the script's commands exist on every host there that answers SSH. Each host that refuses the login reports Login refused on every run.

Writing and deploying the script

Deploy the script to each scanned host

Script takes one of three sources:

SourceWhat Scanopy storesWhere the script lives
File on scanned host (default)The pathOn each scanned host
File on daemon hostThe pathOn the daemon host. The daemon reads it at each scan and sends its text
Enter valueThe script textIn the Scanopy server database

Use File on scanned host. Deploy the script to each host with your configuration management tool, such as Ansible, Puppet or Group Policy, and enter its path, for example /usr/local/bin/scanopy-inventory.sh or C:\Scanopy\scanopy-inventory.ps1. Scanopy stores only the path, and a script update goes out with the rest of the host's configuration. The path must be absolute for the Scanned Host OS, and a Windows path cannot contain ".

A File on daemon host path must be absolute for the credential's Daemon OS, and ~ is not expanded.

Scanned Host OS sets the shell the script runs in

Set Scanned Host OS to the OS of the hosts the credential is assigned to. The daemon sends one of these commands over SSH:

Scanned Host OSScript sourceCommand the daemon sends
Linux, macOS, BSDFile on scanned hostThe path in single quotes, which the login shell runs as a program
Linux, macOS, BSDFile on daemon host, or Enter valueThe script text, which the login shell runs
WindowsFile on scanned hostpowershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "<path>"
WindowsFile on daemon host, or Enter valuepowershell -NoProfile -NonInteractive -EncodedCommand <text>, with the text base64-encoded as UTF-16LE

The Windows commands run under either default shell of the Windows OpenSSH server, cmd.exe or PowerShell. A credential covers hosts of one OS. Create a second credential for hosts of the other.

Writing the script for Linux, macOS and BSD

A File on scanned host script runs as a program, so it needs a #!/bin/sh first line, like the example above, and the execute bit (chmod 755). Text from the other two sources runs in the account's login shell, the same as ssh scanopy@HOST 'script'. A #! first line is a comment to that shell, so write the text in the login shell's language.

printf '%s' inserts a value as-is, so a value containing " or \ produces invalid JSON. Build the object with jq -n --arg on hosts that have jq.

Writing the script for Windows

PowerShell runs the script with -NoProfile, so profile scripts add nothing to its output. This script reads the same details as the Linux example from CIM and the network adapter list, and prints them with ConvertTo-Json -Compress, which escapes quotes and backslashes in values:

$os   = Get-CimInstance Win32_OperatingSystem
$cs   = Get-CimInstance Win32_ComputerSystem
$bios = Get-CimInstance Win32_BIOS
$interfaces = @(Get-NetAdapter -Physical | ForEach-Object {
    $nic = [ordered]@{
        name        = $_.Name
        descr       = $_.InterfaceDescription
        mac         = $_.MacAddress -replace '-', ':'
        oper_status = if ($_.Status -eq 'Up') { 'Up' } else { 'Down' }
    }
    if ($_.Status -eq 'Up') { $nic.speed_bps = [int64]$_.Speed }
    $nic
})
[ordered]@{
    hostname          = [System.Net.Dns]::GetHostName()
    sys_descr         = "$($os.Caption) $($os.Version)"
    manufacturer      = $cs.Manufacturer
    model             = $cs.Model
    serial_number     = $bios.SerialNumber
    firmware_revision = $bios.SMBIOSBIOSVersion
    software_revision = $os.Version
    interfaces        = $interfaces
} | ConvertTo-Json -Compress -Depth 3

Get-NetAdapter writes MAC addresses with dashes, and the script converts them to colons. Speed is the link speed in bits per second, the number behind the LinkSpeed text such as 1 Gbps. The script reports it only for adapters that are up.

Testing the script

Run the script yourself as the same account before you save the credential. These commands send it the way the daemon does:

# File on scanned host, Linux, macOS, BSD
ssh scanopy@HOST /usr/local/bin/scanopy-inventory.sh | jq .

# File on daemon host or Enter value, Linux, macOS, BSD
ssh scanopy@HOST "$(cat scanopy-inventory.sh)" | jq .

# File on scanned host, Windows
ssh scanopy@HOST 'powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\Scanopy\scanopy-inventory.ps1"' | jq .

Host key checking

Scanopy checks the host key on every run, before it sends any credentials:

  • Host Key Fingerprint left blank. The first successful login pins the key the host presented, in the daemon's config file under ssh_known_host_keys, keyed by ip:port. A later run that sees a different key refuses the host and runs nothing.
  • Host Key Fingerprint set. Scanopy accepts only that key. Get it on the host with ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub, or on Windows with ssh-keygen -lf C:\ProgramData\ssh\ssh_host_ed25519_key.pub. A fingerprint suits a credential assigned to one host, because every host has its own key.

SSH Key

Create a key pair for Scanopy, without a passphrase or with one you enter in Key Passphrase:

ssh-keygen -t ed25519 -f scanopy_ed25519 -C scanopy

Append scanopy_ed25519.pub to the account's authorized keys file on each host, and paste the contents of scanopy_ed25519, including its BEGIN and END lines, into Private Key. To keep the key out of Scanopy, choose File on daemon host and enter the key file's path. The path must be absolute for the credential's Daemon OS, and ~ is not expanded. Scanopy reads OpenSSH keys, the format ssh-keygen writes by default, and PEM keys.

FieldRequiredDefaultDescription
Connection
SSH PortOptional22
Authentication
UsernameRequiredNoneAccount the script runs as. Use a dedicated account with only the access the script needs.
Private KeySecretRequiredNoneOpenSSH (ssh-keygen's default) or PEM private key whose public key is in the account's authorized_keys.
Key PassphraseSecretOptionalNoneOnly if the private key is encrypted.
Script
Scanned Host OSRequiredLinux, macOS, BSDSets how the script runs, in the login shell on Linux, macOS and BSD or in PowerShell on Windows, and the path format for a file on the scanned host. One of: Linux, macOS, BSD, Windows.
ScriptRequiredNoneRuns at each scan. Must print one JSON object of host fields.
Timeout (seconds)Optional60The script is stopped and the run reports a failure after this long.
Host Key FingerprintOptionalNoneLeave blank to trust the key each host presents the first time and refuse it if it later changes. Enter a fingerprint (ssh-keygen -lf) to require that key instead.

SSH Password

Enter the account's password in Password, or choose File on daemon host and enter the path of a file on the daemon host that holds it. The path must be absolute for the credential's Daemon OS, and ~ is not expanded.

FieldRequiredDefaultDescription
Connection
SSH PortOptional22
Authentication
UsernameRequiredNoneAccount the script runs as. Use a dedicated account with only the access the script needs.
PasswordSecretRequiredNonePassword for that account. The server must allow password authentication.
Script
Scanned Host OSRequiredLinux, macOS, BSDSets how the script runs, in the login shell on Linux, macOS and BSD or in PowerShell on Windows, and the path format for a file on the scanned host. One of: Linux, macOS, BSD, Windows.
ScriptRequiredNoneRuns at each scan. Must print one JSON object of host fields.
Timeout (seconds)Optional60The script is stopped and the run reports a failure after this long.
Host Key FingerprintOptionalNoneLeave blank to trust the key each host presents the first time and refuse it if it later changes. Enter a fingerprint (ssh-keygen -lf) to require that key instead.

Verifying it works

  1. Run the network's discovery from Discover > Scans, or wait for its next scheduled run
  2. Open the finished run under Discover > Scans > Historical and select the Credentials tab
  3. Find the SSH credential's row. It lists each host the script ran on with its outcome. Applied means the script ran and Scanopy stored its output. Fields applied lists the keys Scanopy stored, and Not applied lists keys it did not.
  4. Check the row's warning count. Any warning appears on the run's Issues tab.
  5. Open the host. Its Details tab shows each value the script set with SSH script as the source, and its Interfaces tab shows the interfaces the script printed.

Troubleshooting

Each failed run appears in the SSH credential's row on the run's Credentials tab, with its outcome and the end of the script's standard error or the error message. It also raises a credential warning on the run's Issues tab. A failed run applies nothing, and the host keeps the values from its last successful run.

Login refused

The host rejected the username, key or password, and the script did not run. Test the same login from the daemon host:

ssh -i scanopy_ed25519 -o IdentitiesOnly=yes scanopy@HOST true

For SSH Password, check that the server's sshd_config allows PasswordAuthentication. A per-host credential overrides the network default, so check which credential reached this host.

Host key changed

The host presented a different key from the pinned key, or from the credential's Host Key Fingerprint, and Scanopy did not log in. The run's detail names the key the host presented.

A reinstalled host or regenerated host keys produce this outcome. So does a different machine answering at that address. Confirm which it is before you trust the new key.

To trust the new key on a pinned host:

  1. Stop the daemon
  2. Open its config file (locations are listed under Configuration Methods) and delete the host's ip:port entry from ssh_known_host_keys
  3. Start the daemon

The next run pins the key the host presents. Stop the daemon before you edit the file, because a running daemon writes its config back and restores the entry.

For a credential with Host Key Fingerprint set, update the fingerprint instead.

Connection failed

The daemon reached the SSH port during the scan and then could not set up a session. The detail gives the reason. "could not read the private key" means the key is in a format Scanopy does not read, or it is encrypted and Key Passphrase is empty or wrong.

Timed out

The script did not finish within Timeout (seconds), 60 by default. Scanopy stops it and applies nothing. Raise the timeout, or check for a command waiting on input, such as sudo without -n asking for a password.

Exited with an error

The script exited with a status other than 0, and Scanopy discarded its output. The run shows the exit code and the end of standard error. A command that fails under set -e produces this outcome, and so does a missing command or a File on scanned host path that does not exist. A Linux, macOS or BSD script file without the execute bit also fails here. Check that Scanned Host OS matches the host.

Output not valid JSON

The script exited 0, but its output is not one JSON object with the types the table above gives. The detail gives the parser's error with its line and column. Common causes:

  • A value containing an unescaped " or \
  • Extra text on standard output, such as a login banner or a message from /etc/profile or ~/.bashrc
  • A number in quotes for speed_bps, or a status value in the wrong case
  • No output at all, from a script that does other work and reports nothing. Print {}

Run the script with a command from Testing the script to see the exact output.

Output too large

The script printed more than 64 KiB. Scanopy stops reading at that limit and applies nothing. Print only the keys in the table, and limit interfaces to the interfaces you need.

Keys listed under Not applied

The run applied every other key and skipped these. A key appears here when it is not in the table above, such as interfaces[0].vlan, or when an interface mac is not a valid MAC address. Rename the key to one from the table or remove it.

Credential set up for another OS

The credential reads its key, password or script from a file on the daemon host, and the daemon runs a different OS from the credential's Daemon OS, so the daemon skipped the credential. Set Daemon OS to the daemon's OS, or scan with a daemon on the OS the credential names.

For credential loading problems, such as unreadable files and the per-session assignment summary, see Credential troubleshooting.

On this page