Alisson Machado
04 June 2016

Python e SSH

Uma tarefa muito comum dos administradores de sistemas linux é executar o mesmo comando em vários servidores distintos, isso com o objetivo de aplicar um patch de segurança,


instalar um novo pacote, efetuar alguma configuração, até mesmo padronizar configurações. Para isso utilizamos ferramentas como:


  • Puppet
  • Ansible
  • Chef
  • Fabric


Essas são as mais conhecidas.


Mas é possível também fazer essas configurações através do Python, existe um módulo chamado paramiko que foi criado justamente para fazer conexões via SSH, então nesse post vou mostrar a vocês como se usa esse módulo.


O primeiro passo é instalar o paramiko

pip install paramiko


Agora segue o script completo:

#!/usr/bin/python

from paramiko import SSHClient
import paramiko

class SSH:
    def __init__(self):
        self.ssh = SSHClient()
        self.ssh.load_system_host_keys()
        self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        self.ssh.connect(hostname='127.0.0.1',username='root',password='SENHA_DE_ROOT')

    def exec_cmd(self,cmd):
        stdin,stdout,stderr = self.ssh.exec_command(cmd)
        if stderr.channel.recv_exit_status() != 0:
            print stderr.read()
        else:
            print stdout.read()

if __name__ == '__main__':
    ssh = SSH()
    ssh.exec_cmd("apt-get update")


Agora vamos entender o que faz esse script.


A primeira coisa a se entender é que foi criada uma classe chamada SSH, assim é possível facilitar algumas coisas, pois no método construtor, definido por def __init__(self): tem uma sequencia de instruções para efetuar a conexão com um determinador servidor.

        self.ssh = SSHClient()


Essa linha faz a instância da classe SSHClient que foi importada do módulo paramiko logo no topo do script.

from paramiko import SSHClient
import paramiko


Na sequencia do construtor, foi definida a instrução:

        self.ssh.load_system_host_keys()


Essa instrução define que o paramiko vai ler todas as chaves cadastrados no arquivo ~/.ssh/known_hosts, assim evitamos ter que ficar dando um yes ou no na hora de conectar em um servidor via ssh.

        self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())


Nessa linha é definido o que fazer quando a chave de um servidor não é encontrada no ~/.ssh/known_hosts, então foi definido o método paramiko.AutoAddPolicy(), assim quando um servidor for acessado pela primeira vez o paramiko automáticamente vai aceitar a chave desse servidor e cadastrar no arquivo known_hosts.

        self.ssh.connect(hostname='127.0.0.1',username='root',password='SENHA_DE_ROOT')


Nessa linha definimos o servidor em que vamos conectar, o usuário e a senha, caso o seu acesso seja feito através de chaves, é possível omitir o username e o password, deixando somente a chave hostname com o IP do servidor que você quer conectar. Por que todas essas instruções foram colocadas no método construtor?


Dessa maneira assim que for instanciado um objeto dessa classe SSH a conexão já é feita automaticamente, então o método exec_cmd pode executar os comandos diretamente, sem a necessidade de ficar efetuando a conexão antes de executar qualquer comando.


Agora vamos analisar o método exec_cmd. O método exec_cmd tem apenas um parâmetro obrigatório, que é a variável cmd, ela deve receber uma string com o comando que deve ser executado via ssh no servidor.

        stdin,stdout,stderr = self.ssh.exec_command(cmd)


A linha acima usa o próprio atributo ssh da classe SSH, que contém uma instância do SSHClient do paramiko que já está conectada ao servidor, tudo isso foi realizado no construtor dessa classe, essa instância possui um método chamado exec_command que recebe uma string como parâmetro, que será o comando executado no servidor.


Quando o comando é executado esse método retorna uma tupla com 3 valores.

- Standard Input (Entrada padrão, normalmente uma entrada do teclado)

- Standard Output ( Saída padrão, o que aparece na tela )

- Standard Error ( Saída de Error, mensagem de erro mostrada na tela )


Todos foram abreviados como stdin,sdout,sdterr.

        if stderr.channel.recv_exit_status() != 0:
            print stderr.read()
        else:
            print stdout.read()


Depois de recebidos os valores retornados pelo exec_command, precisamos saber se o comando deu erro ou não, para isso foi feito esse if. Na instrução stderr.channel.recv_exit_status(), é verificado se o valor retornado da saída de erro é diferente de 0, se esse valor for diferente de zero significa que um erro aconteceu.


Por exemplo:

- erro 127 ( Command not found )

- erro 1 ( Erro ao executar o comando, pode ser um parâmetro invalido por exemplo)


Esses são os erros mais comuns.


Então se for retornado um erro a condição entra no primeiro bloco de instruções fazendo um print do erro do comando, caso contrário a saída padrão do comando será retornada na tela.

if __name__ == '__main__':
    ssh = SSH()
    ssh.exec_cmd("apt-get update")


Essas ultimas instruções são para testar o nosso script, a linha if __name__ == '__main__': diz que o bloco abaixo só será executado se o script for executado via linha de comando, caso você faça um import desse arquivo essas instruções não serão executadas.


Na sequência é instanciada a nossa classe SSH dentro da variável ssh que acaba se tornando um objeto, nesse momento é feita a conexão com o servidor, uma vez que tudo foi definido no construtor da classe, então logo abaixo em ssh.exec_cmd("apt-get update") é enviado o comando apt-get update para atualizar a base de dados do apt-get no servidor que quisermos, o script irá demorar um pouco e se o comando for executado com sucesso na sua tela irá aparecer o resultado do comando executado.