Thursday, March 28, 2019

Zato Blog: SSH commands as API microservices

This is a quick guide on how to turn SSH commands into a REST API service. The use-case may be remote administration of devices or equipment that does not offer a REST interface or making sure that access to SSH commands is restricted to selected external REST-based API clients only.

Python

The first thing needed is code of the service that will connect to SSH servers. Below is a service doing just that - it receives name of the command to execute and host to run in on, translating stdout and stderr of SSH commands into response documents which Zato in turn serializes to JSON.

It uses the sh library for SSH connections which means that each new one is started in a subprocess.

# -*- coding: utf-8 -*-

from __future__ import absolute_import, division, print_function, unicode_literals

# stdlib
from traceback import format_exc

# sh
from sh import ssh

# Zato
from zato.server.service import Service

class SSHInvoker(Service):
    """ Accepts an SSH command to run on a remote host and returns its output to caller.
    """
    class SimpleIO:
        input_required = 'host', 'command'
        output_required = 'is_ok', 'cid'
        output_optional = 'stdout', 'stderr'
        skip_empty_keys = True
        response_elem = None

    def handle(self):

        # Local aliases
        host = self.request.input.host
        command = self.request.input.command

        # Correlation ID is always to be returned
        self.response.payload.cid = self.cid

        try:
            # Run the command and collect output
            output = ssh(host, command)

            # Assign both stdout and stderr to response
            self.response.payload.stdout = output.stdout
            self.response.payload.stderr = output.stderr

        except Exception:
            # Catch any exception and log it
            self.logger.warn('Exception caught (%s), e:`%s', self.cid, format_exc())
            self.response.payload.is_ok = False

        else:
            # Everything went fine
            self.response.payload.is_ok = True

Web-admin

In web-admin, let's go ahead and create an HTTP Basic Auth definition that a remote API client will authenticate against:

Now, the SSH service can be mounted on a newly created REST channel - note the security definition used and that data format is set to JSON.

Usage

At this point, everything is ready to use. For testing purposes, let's invoke the service from command line:

$ curl "api:8MbrHs2GHHQjw@localhost:11223/api/ssh" -d \
    '{"host":"localhost", "command":"uptime"}'
{
    "is_ok": true,
    "cid": "27406f29c66c2ab6296bc0c0",
    "stdout": " 09:45:42 up 37 min,  1 user,  load average: 0.14, 0.27, 0.18\n"}
$

This completes it - the service is deployed and made accessible via a REST channel that can be invoked using JSON. Any command can be sent to any host and, assuming that SSH commands can be executed at all, their output will be returned to API callers in JSON responses.



from Planet Python
via read more

No comments:

Post a Comment

TestDriven.io: Working with Static and Media Files in Django

This article looks at how to work with static and media files in a Django project, locally and in production. from Planet Python via read...