RemoteAccess v2
This integration transfers files between Cortex XSOAR and a remote machine and executes commands on the remote machine.
Utilities · Remote Access
Details
| ID | RemoteAccess v2 |
|---|---|
| Provider | Axis Communications |
| Category | Utilities |
| From Version | 6.0.0 |
| Docker Image | demisto/netmiko:1.0.0.9067966 |
| Supported Modules | Agentix XSIAM EDR Cortex Cloud Attack Surface Management Cloud Runtime Security Cloud Posture Security Exposure Management |
README
This integration transfers files between Cortex XSOAR and a remote machine and executes commands on the remote machine.
Some changes have been made that might affect your existing content.
If you are upgrading from a previous version of this integration, see Breaking Changes.
Note: This integration was integrated and tested on a remote machine with Centos-7 operating system. It does not work with Windows operation system.
Configure RemoteAccess v2 in Cortex
| Parameter | Description | Required |
|---|---|---|
| Default Hostname or IP Address | If not provided, “host” or “system” should be provided in the command’s arguments. | False |
| User | For example, “root”. | False |
| Password | The password of the remote machine. | False |
| sshKey | The private RSA key used to authenticate with the remote machine should be configured in the Credentials tab under Settings > Integrations > Credentials. See here for more information. | False |
| Additional Password | Requires an additional password as an argument to run any command of this module. | False |
| Ciphers | A comma-separated list of ciphers to use. If none of the specified ciphers are agreed to by the server, an error message specifying the supported ciphers is returned. | False |
| Key Algorithms | A comma-separated list of key algorithms to use. If none of the specified key algorithms are agreed to by the server, an error message specifying the supported key algorithms is returned. | False |
Configure SSH From Remote
For login using root:
- Edit the /etc/ssh/sshd_config file.
- set
PermitRootLogintoyes - set
PasswordAuthenticationtoyes
- Restart the sshd server:
service sshd restart
Configure the instance with SSH certificate
Currently, the only type of certificate that is supported is RSA private keys (.PEM) files.
In case access is required to an instance in the cloud, use the PEM file provided by the cloud provider.
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
ssh
Run the specified command on the remote system with SSH.
Base Command
ssh
Input
| Argument Name | Description | Required |
|---|---|---|
| cmd | Command to run on the remote machine. To combine commands and run them in a single SSH session, concatenate them using semicolons (;) e.g., ‘echo 1;echo 2’. | Required |
| additional_password | Password required to match the Additional Password parameter if it was supplied to run the command. | Optional |
| timeout | Timeout for command in seconds. | Optional |
| system | System to run the command on. | Optional |
| host | Host name to run the command on. | Optional |
| port | Port to run the command on. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| RemoteAccess.Command.output | String | Standard output of the specified command. |
| RemoteAccess.Command.error | String | Standard error output of the specified command. |
| RemoteAccess.Command.success | Boolean | Whether the operation was successful. |
| RemoteAccess.Command.command | String | Command that was run. |
Command Example
!ssh command="echo test"
Context Example
{
"RemoteAccess": {
"Command": [
{
"command": "echo test",
"error": "",
"output": "test\n",
"success": true
}
]
}
}
Human Readable Output
Command echo test Outputs
command output success echo test test true
copy-to
Copies the given file from Cortex XSOAR to the remote machine.
Base Command
copy-to
Input
| Argument Name | Description | Required |
|---|---|---|
| entry_id | Entry ID of the file to be copied from Cortex XSOAR to the remote machine. | Optional |
| destination_path | Destination of the path of the copied file in the remote machine. Defaults to the entry_id file name if not specified. |
Optional |
| additional_password | Password. Required to match the Additional Password parameter if it was supplied in order to run the command. | Optional |
| timeout | Timeout for command in seconds. Default is 10.0 seconds. | Optional |
| dest-dir | Destination of the directory to copy the file to in the remote machine. The file name of the entry_id will be used as the file name in the destination directory. Creates the destination directory in the remote machine if it does not exist. |
Optional |
| entry | This input is deprecated. Please use the entry_id input instead. |
Optional |
| system | System to run the command on. | Optional |
| host | Host name to run the command on. | Optional |
| port | Port to run the command on. | Optional |
Context Output
There is no context output for this command.
Command Example
!copy-to entry_id=104@49493d71-eef6-4bb4-8075-4be38d9bc340 destination_path="test/cortex_copied_file"
Human Readable Output
The file corresponding to entry ID: 104@49493d71-eef6-4bb4-8075-4be38d9bc340 was copied to remote host
copy-from
Copies the given file from the remote machine to Cortex XSOAR.
Base Command
copy-from
Input
| Argument Name | Description | Required |
|---|---|---|
| file_path | Path of the file in the remote machine to be copied to Cortex XSOAR. | Optional |
| file_name | Name of the file to be copied to Cortex XSOAR. Defaults to the file name in file_path if not specified. For example, if file_path is “a/b/c.txt”, the file name will be c.txt. |
Optional |
| additional_password | Password required to match the Additional Password parameter if it was supplied to run the command. | Optional |
| timeout | Timeout for command, in seconds. Default is 10.0 seconds. | Optional |
| file | This input is deprecated. Please use the file_path input instead. |
Optional |
| system | System to run the command on. | Optional |
| host | Host name to run the command on. | Optional |
| port | Port to run the command on. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| File.Name | String | The full file name (including the file extension). |
| File.EntryID | String | The ID for locating the file in the War Room. |
| File.Size | Number | The size of the file in bytes. |
| File.MD5 | String | The MD5 hash of the file. |
| File.SHA1 | String | The SHA1 hash of the file. |
| File.SHA256 | String | The SHA256 hash of the file. |
| File.SHA512 | String | The SHA512 hash of the file. |
| File.Extension | String | The file extension. For example: “xls”. |
| File.Type | String | The file type, as determined by libmagic (same as displayed in file entries). |
Command Example
!copy-from file_path="test/remote_file.txt" file_name="CopiedRemoteFile"
Context Example
{
"File": {
"EntryID": "165@49493d71-eef6-4bb4-8075-4be38d9bc340",
"Info": "text/plain",
"MD5": "c5253b90e791d18439a84511c382616b",
"Name": "CopiedRemoteFile",
"SHA1": "98c94e6e64b7a52576870fc07a0da5f33243c505",
"SHA256": "bf98cd7cda320c300218397d9ee1df263415aac7f0f41c8f57dee7944e68fba0",
"SHA512": "6f2199d786a13c7b8cd6d268166a26f4423d4aa1e4ba59565e9130da01555d83d190870dade6098fe7992425e0d2d9128841b92f419dbf4193a6112c4cf7264f",
"SSDeep": "3:9bLbEin:6i",
"Size": 16,
"Type": "ASCII text, with no line terminators"
}
}
Human Readable Output
Breaking changes from the previous version of this integration - RemoteAccess v2
- Removed the Interactive terminal mode instance parameter.
- Removed the Terminal Type instance parameter.
Commands
Arguments
The following argument names were changed, added, or removed in this version
| Remote Access Command Name | Old Command Argument Name | New Command Name |
|---|---|---|
| copy-to | fileID | Argument was removed |
| copy-from | Argument did not exist | file_name |
Outputs
The following outputs were removed in this version
| Remote Access Command Name | Old Command Outputs | Remote Access v2 Command Name | New Command Outputs |
|---|---|---|---|
| ssh | Command outputs were: - command - stdout - stderr - remote machine IP - success status |
ssh | Outputs: - stdout - stderr |
Configuration parameters
hostname— Default Hostname or IP Addresscredentials— Useradditional_password—ciphers— Cipherskey_algorithms— Key Algorithms
Commands (3)
-
copy-fromCopies the given file from the remote machine to Cortex XSOAR.
-
copy-toCopies the given file from Cortex XSOAR to the remote machine.
-
sshRun the specified command on the remote system with SSH.
import tempfile from concurrent.futures import ThreadPoolExecutor, as_completed from io import StringIO import paramiko import urllib3 from CommonServerPython import * # noqa # pylint: disable=unused-wildcard-import from paramiko import AutoAddPolicy, SSHClient, Transport, transport from paramiko.ssh_exception import NoValidConnectionsError from scp import SCPClient, SCPException from CommonServerUserPython import * # noqa # Disable insecure warnings urllib3.disable_warnings() # pylint: disable=no-member DEFAULT_TIMEOUT = 10.0 DEFAULT_PORT = 22 """ HELPER FUNCTIONS """ def perform_copy_command( ssh_client: SSHClient, file_path: str, destination_path: str, copy_to_remote: bool, socket_timeout: float ) -> Union[str, bytes]: """ Function to perform copy to or copy from remote machine. This helper function was separated from command functions mainly for easier mocking in tests. Args: ssh_client (SSHClient): SSH client to perform copy from or to. file_path (str): - Copy to remote machine from Cortex XSOAR - the Cortex XSOAR file path. - Copy to Cortex XSOAR from remote machine - the remote machine file path. destination_path (str): - Copy to remote machine from Cortex XSOAR - the remote machine file path to contain the copied data. - Copy to Cortex XSOAR from remote machine - Temp file name to save the file, before extracting its data. copy_to_remote (bool): Whether a request to copy to remote was made. socket_timeout(float): Socket timeout. Returns: (str): Empty str if command requested was copy to. (str): Str containing the copied file from remote machine data. Raises: (Exception): if unexpected behaviour occurred. """ try: with SCPClient(ssh_client.get_transport(), socket_timeout=socket_timeout) as scp_client: if copy_to_remote: scp_client.put(file_path, destination_path) return "" else: with tempfile.TemporaryDirectory() as temp_dir: scp_client.get(file_path, f"{temp_dir}/{destination_path}") with open(f"{temp_dir}/{destination_path}", "rb") as f: return f.read() except (FileNotFoundError, SCPException) as e: if "No such file or directory" in str(e): raise DemistoException( f"Could not find the given path {file_path} in the local machine.\nPlease verify the path is correct." ) from e if "Not a directory" in str(e): raise DemistoException( f"Given destination path: {destination_path} does not exist in remote machine.\n" "Please verify destination path is valid." ) from e if "No such file or directory" in str(e): raise DemistoException( f"Could not find the given path {file_path} in the remote machine.\nPlease verify the path is correct." ) from e raise e def get_available_ciphers() -> Set[str]: """ Gets a set of the available ciphers supported by server. Returns: (Set[str]): Set of supported ciphers. """ tmp_socket = socket.socket() opts = transport.Transport(tmp_socket).get_security_options() tmp_socket.close() return set(opts.ciphers) def get_available_key_algorithms() -> Set[str]: """ Gets a set of the available key algorithms supported by server. Returns: (Set[str]): Set of supported ciphers. """ tmp_socket = socket.socket() opts = transport.Transport(tmp_socket).get_security_options() tmp_socket.close() return set(opts.kex) def create_paramiko_ssh_client( host_name: str, user_name: str, password: str, ciphers: Set[str], key_algorithms: Set[str], private_key: str = "", port: int = DEFAULT_PORT, ) -> SSHClient: """ Creates the Paramiko SSH client. Args: host_name (str): Hostname of the machine to create the SSH for. user_name (str): User to create the SSH session with the given host. password (str): Password of the given user. ciphers (Set[str]): Set of ciphers to be used, if given. key_algorithms (Set[str]): Set of key algorithms to be used, if given. private_key (str): The SSH certificate (should be PEM file based certificate only). port (int): The port to connect to. Returns: (SSHClient): Paramiko SSH client if connection was successful, exception otherwise. """ if ciphers: # Getting available ciphers from server, in order to print an appropriate error message upon no cipher match. available_ciphers = get_available_ciphers() if not ciphers.intersection(available_ciphers): raise DemistoException( f"Given ciphers are not available in server.\nCiphers available in server are: {available_ciphers}" ) Transport._preferred_ciphers = (*ciphers,) # type: ignore if key_algorithms: available_key_args = get_available_key_algorithms() if not key_algorithms.intersection(available_key_args): raise DemistoException( f"Given key algorithms are not available in server.\n" f"Key algorithms available in server are: {available_key_args}" ) Transport._preferred_kex = (*key_algorithms,) # type: ignore client = SSHClient() client.set_missing_host_key_policy(AutoAddPolicy()) try: rsa_private_key = None if private_key: # authenticating with private key only works for certificates which are based on PEM files. # (RSA private keys) rsa_private_key = paramiko.RSAKey.from_private_key(StringIO(private_key)) # type: ignore # [assignment] client.connect(hostname=host_name, username=user_name, password=password, port=port, pkey=rsa_private_key) except NoValidConnectionsError as e: raise DemistoException(f"Unable to connect to port {port} on {host_name}") from e return client def find_nonexistent_systems(given_systems: List[str], given_hosts: List[str]): investigation = demisto.investigation() if not investigation: return None systems = investigation.get("systems") investigation_id = investigation.get("id") demisto.debug(f"Available systems on investigation {investigation_id} are {systems}.") if not systems: return None systems_names = [system_properties.get("name") for system_properties in systems] not_found_systems = [] for given_system in given_systems: if given_system not in systems_names: not_found_systems.append(given_system) systems_hosts = [system_properties.get("host") for system_properties in systems] not_found_hosts = [] for given_host in given_hosts: if given_host not in systems_hosts: not_found_hosts.append(given_host) if not not_found_hosts and not not_found_systems: return None return ( f'{f"Systems {not_found_systems}" if not_found_systems else ""}' f'{" and " if not_found_systems and not_found_hosts else ""}' f'{f"Hosts {not_found_hosts}" if not_found_hosts else ""} ' f'not found on investigation {investigation_id}. ' f'Available systems by name are {systems_names}, and by host are {systems_hosts}.' ) def create_clients( host_name: str, user: str, password: str, ciphers: Set[str], key_algorithms: Set[str], certificate: str, systems: List[str], hosts: List[str], port: int = DEFAULT_PORT, ) -> List[SSHClient]: clients = [ create_paramiko_ssh_client(system, user, password, ciphers, key_algorithms, certificate, port) for system in systems ] clients.extend( [create_paramiko_ssh_client(host, user, password, ciphers, key_algorithms, certificate, port) for host in hosts] ) if not clients and host_name: client = create_paramiko_ssh_client(host_name, user, password, ciphers, key_algorithms, certificate, port) clients.append(client) return clients def close_clients(clients: List[SSHClient]): for client in clients: client.close() """ COMMAND FUNCTIONS """ def execute_shell_command(ssh_client: SSHClient, args: Dict[str, Any]) -> CommandResults: """ Executes shell command. Args: ssh_client (SSHClient): SSH client to perform the command with. args (Dict[str, Any]): Cortex XSOAR arguments. Returns: (CommandResults). """ command: str = args.get("cmd", "") timeout: Optional[int] = arg_to_number(args.get("timeout")) # exec_command returns a tuple of stdin, stdout, stderr. No need to parse stdin because it does not contain data. _, stdout, std_err = ssh_client.exec_command(command, timeout=timeout) stdout_str: str = stdout.read().decode() std_error_str: str = std_err.read().decode() if stdout_str or std_error_str: outputs: Optional[List[Dict]] = [ {"output": stdout_str, "error": std_error_str, "command": command, "success": not std_error_str} ] readable_output = tableToMarkdown(f"Command {command} Outputs", outputs, removeNull=True) else: outputs = None readable_output = f"### Command {command} was executed successfully without any outputs." return CommandResults(outputs_prefix="RemoteAccess.Command", outputs=outputs, readable_output=readable_output) def copy_to_command(ssh_client: SSHClient, args: Dict[str, Any]) -> CommandResults: """ Executes a safe copy from Cortex XSOAR to remote machine. Args: ssh_client (SSHClient): SSH client to perform the command with. args (Dict[str, Any]): Cortex XSOAR arguments. Returns: (CommandResults). """ dest_dir_arg = args.get("dest-dir", "") destination_path_arg = args.get("destination_path", "") if dest_dir_arg and destination_path_arg: raise DemistoException('Please provide at most one of "dest-dir" argument or "destination_path", not both.') # Support `entry` argument to maintain BC: entry: str = args.get("entry", "") entry_id: str = args.get("entry_id", entry) if not entry_id: raise DemistoException('No entry ID path given. Please provide one of the "entry_id" (recommended) or "entry" inputs.') if timeout := args.get("timeout"): timeout = float(timeout) else: timeout = DEFAULT_TIMEOUT file_path_data = demisto.getFilePath(entry_id) if not (file_path := file_path_data.get("path", "")): raise DemistoException("Could not find given entry ID path. Please assure given entry ID is correct.") file_name = file_path_data.get("name", "") if dest_dir_arg: destination_path = os.path.join(dest_dir_arg, file_name) destination_dir = dest_dir_arg elif destination_path_arg: destination_path = destination_path_arg destination_dir = os.path.split(destination_path)[0] else: destination_path = file_name destination_dir = "" # Create all folders to destination_path in the remote machine if destination_dir: try: execute_shell_command(ssh_client, args={"cmd": f"mkdir -p {destination_dir}"}) except Exception as e: # ignore the error of creating the dir, as sometime is already exist and the error are due to permission # otherwise the next operation will fail. demisto.debug(f"Ignoring the error: {e!s}, occurred when run the command: mkdir -p {destination_dir}") perform_copy_command(ssh_client, file_path, destination_path, copy_to_remote=True, socket_timeout=timeout) return CommandResults(readable_output=f"### The file corresponding to entry ID: {entry_id} was copied to remote host.") def copy_from_command(ssh_client: SSHClient, args: Dict[str, Any]) -> Dict: """ Executes a safe copy from remote machine to Cortex XSOAR machine. Args: ssh_client (SSHClient): SSH client to perform the command with. args (Dict[str, Any]): Cortex XSOAR arguments. Returns: (Dict): FileResult data. """ if timeout := args.get("timeout"): timeout = float(timeout) else: timeout = DEFAULT_TIMEOUT # Support `file` argument to maintain BC: file: str = args.get("file", "") file_path: str = args.get("file_path", file) file_name: str = args.get("file_name", os.path.basename(file_path)) remote_file_data = perform_copy_command(ssh_client, file_path, file_name, copy_to_remote=False, socket_timeout=timeout) return fileResult(file_name, remote_file_data) """ MAIN FUNCTION """ def main() -> None: params = demisto.params() args = demisto.args() command = demisto.command() credentials: Dict[str, Any] = params.get("credentials") or {} user: str = credentials.get("identifier", "") password: str = credentials.get("password", "") certificate: str = (credentials.get("credentials") or {}).get("sshkey", "") host_name: str = params.get("hostname", "") ciphers: Set[str] = set(argToList(params.get("ciphers"))) key_algorithms: Set[str] = set(argToList(params.get("key_algorithms"))) demisto.debug(f"Command being called is {demisto.command()}") if ( (password_param := params.get("additional_password", {}).get("password")) and command != "test-module" and password_param != args.get("additional_password") ): raise DemistoException( "Additional password to use the module have been supplied.\n" 'Please supply "additional_password" argument that matches the "Additional Password"' " parameter value." ) systems = argToList(args.get("system")) hosts = argToList(args.get("host")) port = args.get("port") or DEFAULT_PORT clients = [] try: if nonexistent_systems_result := find_nonexistent_systems(systems, hosts): raise DemistoException(nonexistent_systems_result) clients = create_clients(host_name, user, password, ciphers, key_algorithms, certificate, systems, hosts, port) commands = { "ssh": execute_shell_command, "copy-to": copy_to_command, "copy-from": copy_from_command, } if command == "test-module": return_results("ok") elif command in commands: if not clients: raise DemistoException("Command can't be executed because no hostname, system, or host was provided.") results = [] with ThreadPoolExecutor(max_workers=len(clients)) as executor: future_results = [executor.submit(commands[command], ssh_client=client, args=args) for client in clients] for future in as_completed(future_results): results.append(future.result()) return_results(results) else: raise NotImplementedError(f"""Command '{command}' is not implemented.""") # Log exceptions and return errors except Exception as e: return_error(f"Failed to execute {demisto.command()} command.\nError:\n{e!s}") finally: close_clients(clients) """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): main()