Netmiko

Multi-vendor library to simplify SSH connections to network devices. Utilizes the Python library Netmiko for connections. Supports SSH Key authentication and username / password.

Utilities · Netmiko

Details

IDNetmiko
ProviderOpen Source
CategoryUtilities
From Version6.0.0
Docker Imagedemisto/netmiko:1.0.0.9067966
Supported ModulesAgentix XSIAM

README

Netmiko SSH module integration

This integration provides ssh-based access to network devices, servers, and other appliances that support this method of configuration. For a complete list of supported platforms, please visit the below URL:

Netmiko Platforms.md on Github

Configure Netmiko Integration in Cortex XSOAR

  1. Navigate to Settings - Integrations
  2. Search for Netmiko
  3. Click Add instance to create and configure a new integration instance.
  • Name: a name for the integration instance.
  • Platform: the platform identifier taken from the above SSH or Telnet platform name lists (e.g., linux_ssh, paloalto_panos, etc.)
    NOTE: Platform names are taken from the supported
    SSH or Telnet device type lists on GitHub.
    • Hostname: The IP address, hostname, or FQDN for the device to connect to via SSH.
    • Port: The port to connect to via SSH
    • Credentials: The username/password, or XSOAR credential object, to be used for the connection
    • Override the default timeout value: Override the timeout value (in seconds) for a given integration instance. This is useful for devices that are slow in responding with requested output over SSH.
  1. Click Test to validate the new instance. This performs a simple connection to the system hosting the SSH server.

Commands

The Netmiko integration currently only supports the netmiko-cmds command for SSH. This command can be used via the XSOAR CLI, as part of an automation, or as a task in an XSOAR playbook. Like other XSOAR commands, this object can be passed a single command, a list of commands, or an array of commands to execute in a single session.

  1. Executes a command, or series of commands, over an SSH connection: netmiko-cmds

netmiko-cmds

Executes a command, or series of commands, over an SSH connection. Outputs from the executed commands are returned to the incident/playground context.

Base Command

netmiko-cmds

Input


Argument Name Description Required
cmds The command, or commands, to execute. When commands are manually specified and executed via the XSOAR CLI or in a task, place each command after the first on a new line (no comma required) Required
disable_context The package ID. Package ID or package name is required. When both exist, ID is used. Optional
exit_argument The optional exit command to be executed after the cmds parameter. This is tied to the requires_exit optional parameter. (Default: q) Optional
isConfig Specifies whether or not the commands being executed require a configure command to be executed first (e.g., conf t for Cisco IOS). The specific configure command is handled by the Netmiko Python module, and is associated with the Platform parameter specified in the integration instance. (Default: False) Optional
override_host If specified, uses this host in place of the one specified in the instance configuration. Optional
override_password If specified, uses this password in place of the one specified in the instance configuration. Optional
override_platform If specified, uses this platform name in place of the one specified in the instance configuration. Optional
override_port If specified, uses this port in place of the one specified in the instance configuration. Optional
override_username If specified, uses this username in place of the one specified in the instance configuration. Optional
raw_print Prints the raw output directly to the war room (Default: False) Optional
require_enable Specifies whether or not the enable command must be executed before the commands specified in the cmds parameter. (Default: False) Optional
require_exit Specifies an optional command that must be executed upon completion of the cmds parameter being executed. (Default: False) Optional

Context Output

Path Type Description
Netmiko.Command String The executed command(s)
Netmiko.DateTimeUTC DateTime The datetime at which the command(s) were executed (in UTC)
Netmiko.Hostname String The hostname used for this execution of the integration
Netmiko.Output String The results of the command(s) that were executed

Command Example (Single command)

!netmiko-cmds cmds="whoami"

Context Example

{

 “Command”: “whoami”,

 “DateTimeUTC”: “2023-04-24T21:40:21.755985”,

 “Hostname”: “192.168.0.1”,

 “Output”: “[someuser@someserver ~]$ root”

}

Human Readable Output

Command(s) against 192.168.0.1 (linux)

Command DateTimeUTC Hostname Output
whoami 2023-04-24T21:40:21.755985 192.168.0.1 root

Command Example (Multiple commands)

As multiple commands via CLI or task

!netmiko-cmds cmds="whoami
who"

As multiple commands via CLI or task using an array

array context key = ["whoami", "who"]
!netmiko-cmds cmds=${array}

Context Example

{

 “Netmiko”: [{

  “Command”: “whoami”,

  “DateTimeUTC”: “2023-04-24T21:59:02.177240”,

  “Hostname”: “192.168.0.1”,

  “Output”: “[someuser@somehost ~]$ root”

 },

 {

  “Command”: “who”,

  “DateTimeUTC”: “2023-04-24T21:59:04.882842”,

  “Hostname”: “192.168.0.1”,

  “Output”: “[someuser@somehost ~]$ root pts/0 2023-04-24 17:58 (192.168.0.1)”

 }]

}

Human Readable Output

Command(s) against 192.168.0.1 (linux)

Command DateTimeUTC Hostname Output
whoami 2023-04-24T21:59:02.177240 192.168.0.1 root
who 2023-04-24T21:59:04.882842 192.168.0.1 [someuser@somehost ~]$ root pts/0 2023-04-24 17:58 (192.168.0.1)

Configuration parameters

  • hostname — Hostname (required)
  • port — Port (required)
  • platform — Platform (required)
  • credentials — Credentials (required)
  • TimeoutOverride — Override the default timeout value

Commands (1)

  • netmiko-cmds

    Execute commands using Netmiko.

import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401

""" IMPORTS """

# Logging only needed for netmiko debugging
# import logging
import io
import sys
from datetime import datetime

import paramiko
from netmiko import ConnectHandler

# value for Netmiko last_read parameter
LAST_READ_TIMEOUT = 15.0

""" HELPER FUNCTIONS """

# Return only specific keys from dictionary


def include_keys(dictionary, keys):
    key_set = set(keys) & set(dictionary.keys())
    return {key: dictionary[key] for key in key_set}


class Client:  # pragma: no cover
    def __init__(self, platform, hostname, username, password, port, keys, timeout):
        self.platform = platform
        self.hostname = hostname
        self.username = username
        self.password = password
        self.timeout = int(timeout)
        self.port = port
        self.keys = keys
        self.net_connect = ConnectHandler

    def connect(self):
        if self.keys:
            try:
                self.net_connect = ConnectHandler(  # type: ignore[assignment]
                    device_type=self.platform,
                    host=self.hostname,
                    port=self.port,
                    pkey=self.keys,
                    username=self.username,
                    read_timeout_override=self.timeout,
                )
            except Exception as err:
                return_error(err)
        else:
            try:
                self.net_connect = ConnectHandler(  # type: ignore[assignment]
                    device_type=self.platform,
                    host=self.hostname,
                    port=self.port,
                    use_keys=False,
                    username=self.username,
                    password=self.password,
                    read_timeout_override=self.timeout,
                )
            except Exception as err:
                return_error(err)

    def disconnect(self):
        try:
            if self.net_connect:  # type: ignore[truthy-function]
                self.net_connect.disconnect()  # type: ignore[attr-defined]
        except Exception as err:
            return_error(err)

    def cmds(self, require_exit, exit_argument, commands, enable, isConfig):
        try:
            output = {"Hostname": self.hostname, "Platform": self.platform, "Commands": []}
            self.connect()
            if enable:
                self.net_connect.enable()  # type: ignore[attr-defined]
            if isConfig:
                output["Commands"].append(
                    {
                        "Hostname": self.hostname,
                        "DateTimeUTC": datetime.utcnow().isoformat(),
                        "Config": self.net_connect.send_config_set(commands, read_timeout=self.timeout),  # type: ignore[attr-defined]
                    }
                )
            if not isConfig:
                for cmd in commands:
                    prompt = self.net_connect.find_prompt()  # type: ignore[attr-defined]

                    pre_out = self.net_connect.send_command_timing(  # type: ignore[attr-defined]
                        cmd, read_timeout=self.timeout, strip_prompt=False, last_read=LAST_READ_TIMEOUT
                    )

                    pattern_to_keep = re.escape(prompt)

                    out = re.sub(pattern_to_keep, "", pre_out, count=len(re.findall(pattern_to_keep, pre_out))).strip()

                    c = {
                        "Hostname": self.hostname,
                        "DateTimeUTC": datetime.utcnow().isoformat(),
                        "Command": cmd,
                        "Output": f"{prompt} {out}",
                    }
                    output["Commands"].append(c)

        except Exception as err:
            return_error(err)
        finally:
            self.disconnect()
        return output


def test_command(client):  # pragma: no cover
    client.connect()
    client.disconnect()
    demisto.results("ok")
    sys.exit(0)


def cmds_command(client, args):
    # Parse the commands
    cmds = args.get("cmds")
    if not isinstance(cmds, list):  # pragma: no cover
        try:
            cmds = cmds.split("\n")
        except Exception as err:
            return_error("The 'cmds' input needs to be a JSON array or carriage return (SHIFT+ENTER) separated" + f"text - {err}")
    cmds[:] = [x for x in cmds if len(x) > 0]

    # Parse the remaining arguments
    isConfig = args.get("isConfig", "false") == "true"
    enable = args.get("require_enable", "false") == "true"
    require_exit = args.get("require_exit", "false") == "true"
    exit_argument = args.get("exit_argument", None)
    raw_print = args.get("raw_print", "false") == "true"
    disable_context = args.get("disable_context", "false") == "true"
    override_host = args.get("override_host", None)
    override_port = args.get("override_port", None)
    override_platform = args.get("override_platform", None)
    override_username = args.get("override_username", None)
    override_password = args.get("override_password", None)

    client.hostname = override_host if override_host else client.hostname
    client.port = override_port if override_port else client.port
    client.platform = override_platform if override_platform else client.platform
    client.username = override_username if override_username else client.username
    client.password = override_password if override_password else client.password

    # Execute the commands
    output = client.cmds(require_exit, exit_argument, cmds, enable, isConfig)
    raw_print_list = []

    # Output the results
    if raw_print:
        md = ""
        try:
            for command in output.get("Commands"):
                raw_print_list.append(command.get("Output"))
            md = "\n".join(raw_print_list)
        except Exception as err:
            md = "Error parsing raw print output"
            demisto.error(f"Error with raw print output - {err}")

    else:
        hdrs = ["Hostname", "DateTimeUTC", "Command", "Output"]
        data = []

        # Single command
        if len(cmds) == 1:
            data.append(output["Commands"][0])

        # Multiple commands
        else:
            for item in output["Commands"]:
                data.append(include_keys(item, hdrs))

        md = tableToMarkdown(f"Command(s) against {client.hostname} ({client.platform}):", data, headers=hdrs)
    outputs_key_field = None
    outputs_prefix = None
    outputs = None
    if not disable_context:
        outputs_prefix = "Netmiko"
        outputs_key_field = "DateTimeUTC"
        outputs = output

    command_results = CommandResults(
        outputs_prefix=outputs_prefix, outputs_key_field=outputs_key_field, outputs=outputs, readable_output=md
    )

    return command_results


def main():  # pragma: no cover
    # Uncomment the logging.getLogger line to turn on netmiko debugging (shown in integration-instance.log when debug mode is on)
    # Be sure to uncomment the import logging command at the top of the integration
    # Helpful in troubleshooting incorrect command outputs from remote devices

    # logging.getLogger("netmiko").setLevel(logging.DEBUG)
    params = demisto.params()
    args = demisto.args()
    command = demisto.command()

    platform = params.get("platform")
    hostname = params.get("hostname")
    port = params.get("port") or "22"
    try:
        port = int(port)
    except Exception as err:
        return_error(f"Please ensure the port number is a number - {err}")
    username = params.get("credentials", {}).get("identifier")
    password = params.get("credentials", {}).get("password")
    ssh_key = params.get("credentials", {}).get("credentials", {}).get("sshkey")
    timeout = params.get("TimeoutOverride", 60)

    keys = None
    if ssh_key:
        if password:
            try:
                keys = paramiko.RSAKey.from_private_key(io.StringIO(ssh_key), password=password)
            except Exception as err:
                return_error(f"There was an error - {err} - Did you provide the correct password?")
        else:
            keys = paramiko.RSAKey.from_private_key(io.StringIO(ssh_key))

    client = Client(platform, hostname, username, password, port, keys, timeout)

    if command == "test-module":
        test_command(client)
    elif command == "netmiko-cmds":
        results = cmds_command(client, args)
        return_results(results)


if __name__ in ["__main__", "builtin", "builtins"]:  # pragma: no cover
    main()