Seite 1 von 1

SSH Verbindungen aufbauen, Befehle ausführen und protokollieren

Verfasst: Sonntag 8. März 2020, 11:21
von lithium213
Hallo zusammen,

ich bin zwar seit über 15 Jahren Informatiker, aber ein totaler Programmier-Neuling und möchte Folgendes erreichen:

Ich möchte ein Programm schreiben, welches sich auf verschiedenen Rechnern über SSH anmeldet, dort ein paar Befehle ausführt und das Ganze protokolliert.
Für einen Rechner (mit dem ganzen Code in einer Python Datei) funktioniert das schon recht gut, allerdings möchte ich die Zielrechner und die auszuführenden Befehle aus einer separaten Datei auslesen und habe aktuell mühe, die Befehle so einzulesen, dass diese korrekt ausgeführt werden.

Ich habe dazu folgende Dateien erstellt:

Hosts.txt (IP, Port)

Code: Alles auswählen

192.168.1.200,22
192.168.1.201,21021
192.168.2.10,21000
Commands.txt (Remote Befehle)

Code: Alles auswählen

hostname
cat /etc/hosts
ifconfig
connect.py (Die Funktion um via SSH zu verbinden, die Befehle abzusetzen und pro Verbindung eine Protokolldatei zu erstellen)

Code: Alles auswählen

import paramiko
from datetime import datetime

now = datetime.now()  # current date and time
timestamp = now.strftime("%m/%d/%Y, %H:%M:%S ")


def run_ssh(hostname, port, username, password, remote_commands):
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(hostname=hostname, port=port, username=username, password=password)

    for command in remote_commands:
        stdin, stdout, stderr = ssh.exec_command(command)
        with open(hostname + ".log", "a") as logfile:
            logfile.write(timestamp + command + "\n")
        for line in stdout.readlines():
            with open(hostname + ".log", "a") as logfile:
                logfile.write(timestamp + line)
    print("Closing SSH Session")
    ssh.close()
run.py (das eigentliche Programm, welches die Zielrechner und die Befehle einlesen, und die Funktion "run_ssh" aus der Datei connect.py ausführen soll)

Code: Alles auswählen

import connect
import csv

with open("hosts.txt") as file:
    reader = csv.reader(file)
    targets = list(reader)

username = "user"
password = "password"
remote_commands = [
    "hostname",
    "cat /etc/hosts",
    "ifconfig",
]

for address in targets:
    hostname = (address[0])
    port = (address[1])
    print("Connecting to " + hostname + " on Port: " + port)
    connect.run_ssh(hostname, port, username, password, remote_commands)


So funktioniert es mit den Befehlen. Die Frage ist nun, wie ich die Variable remote_commands aus der Datei commands.txt einlesen muss, damit das sich gleich verhält, wie mit

Code: Alles auswählen

remote_commands = [
    "hostname",
    "cat /etc/hosts",
    "ifconfig",
]

Wenn ich das so versuche

Code: Alles auswählen

remote_commands=open("commands.txt","r")

print(remote_commands)
erhalte ich folgende Ausgabe:

Code: Alles auswählen

<_io.TextIOWrapper name='commands.txt' mode='r' encoding='cp1252'>
Auch wenn ihr andere Anregungen habt, bin ich froh um euren Input!
Danke und schöne Grüsse aus Zürich

Re: SSH Verbindungen aufbauen, Befehle ausführen und protokollieren

Verfasst: Sonntag 8. März 2020, 11:26
von __deets__
Du musst nach dem öffnen ja auch noch lesen, damit du an den Inhalt kommst.

Und auf einer allgemeineren Ebene die Frage: solche Tools gibt es doch schon. Zb fabric, auch in Python geschrieben. Ansible, puppet, Chef etc. Wäre es nicht besser, die zu nutzen?

Re: SSH Verbindungen aufbauen, Befehle ausführen und protokollieren

Verfasst: Sonntag 8. März 2020, 11:47
von lithium213
__deets__ hat geschrieben: Sonntag 8. März 2020, 11:26 Du musst nach dem öffnen ja auch noch lesen, damit du an den Inhalt kommst.

Und auf einer allgemeineren Ebene die Frage: solche Tools gibt es doch schon. Zb fabric, auch in Python geschrieben. Ansible, puppet, Chef etc. Wäre es nicht besser, die zu nutzen?
Danke für die rasche Antwort! Die Tools schau ich mir gleich mal an, ich muss ja nicht das Rad neu erfinden. Trotzdem möchte ich mir Python aneignen.

Ich habe gerade festgestellt, dass mit

Code: Alles auswählen

commands_file = open("commands.txt")
remote_commands = commands_file.read()

print(remote_commands)
in der SSH Sitzung jeder Buchstabe einzeln ausgeführt wird... das sieht im Log dann so aus:

Code: Alles auswählen

03/08/2020, 11:42:17 h
03/08/2020, 11:42:17 o
03/08/2020, 11:42:17 s
03/08/2020, 11:42:17 t
03/08/2020, 11:42:17 n
03/08/2020, 11:42:17 a
03/08/2020, 11:42:17 m
03/08/2020, 11:42:17 e
03/08/2020, 11:42:17 
ich müsste die Befehle also noch in Anführungszeichen kriegen... weisst du da weiter?

Re: SSH Verbindungen aufbauen, Befehle ausführen und protokollieren

Verfasst: Sonntag 8. März 2020, 12:09
von nezzcarth
Ein Vorteil von Python ist der interaktive Interpreter. Wenn du dein Beispiel dort ausprobierst und dir den Inhalt von "remote_commands" ansiehst, siehst du sofort das Problem: Es ist ein einziger langer String.

Du solltest die Datei so einlesen:

Code: Alles auswählen

with open('commands.txt', 'r') as file: 
    remote_commands = [command.rstrip() for command in file] 
Für Quoting, etc.. kannst du dir mal das shlex Modul ansehen (https://docs.python.org/3/library/shlex.html).

Re: SSH Verbindungen aufbauen, Befehle ausführen und protokollieren

Verfasst: Sonntag 8. März 2020, 13:17
von Sirius3
Eine Datei run.py und connect.py sind zu viel. Das passt alles in eine Datei, die vielleicht auch noch einen sprechenden Namen bekommen könnte. Das was in run.py steht, sollte auch noch in Funktionen wandern. Strings setzt man nicht mit + zusammen, sondern benutzt Stringformatierung.

Code: Alles auswählen

for hostname, port in targets:
    print(f"Connecting to {hostname} on Port: {port}")
    connect.run_ssh(hostname, port, username, password, remote_commands)
now wird nur einmal beim Start ermittelt. Gerade bei vielen Hosts können schon einige Minuten vergehen, so dass die Zeit bis ein Command ausgeführt wird von der log-Zeit deutlich abweicht. Das Dateiformat sollte %Y-%m-%d sein, sonst kann man leicht Monat und Tag verwechseln.

Re: SSH Verbindungen aufbauen, Befehle ausführen und protokollieren

Verfasst: Sonntag 8. März 2020, 14:24
von lithium213
Danke für eure Inputs! Ich habe erst vor zwei Tagen mithilfe eines Youtube Tutorials mit Python angefangen, und bin sehr dankbar um euer Feedback!

Das "f" hier ist für die Stringformatierung notwendig, richtig?

Code: Alles auswählen

 print(f"Connecting to {hostname} on Port: {port}")
Ich habe nun alles in eine Datei gepackt. Die sieht jetzt so aus und funktioniert einwandfrei.
Gibt es noch anderes zu optimieren?

Code: Alles auswählen

import paramiko
import csv
from datetime import datetime


now = datetime.now()  # current date and time
timestamp = now.strftime("%Y-%m-%d, %H:%M:%S ")
username = "user"
password = "password"

with open("hosts.txt") as file:
    reader = csv.reader(file)
    targets = list(reader)

with open('commands.txt', 'r') as file:
    remote_commands = [command.rstrip() for command in file]

def run_ssh(hostname, port, username, password, remote_commands):
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(hostname=hostname, port=port, username=username, password=password)

    for command in remote_commands:
        stdin, stdout, stderr = ssh.exec_command(command)
        with open(hostname + ".log", "a") as logfile:
            logfile.write(timestamp + command + "\n")
        for line in stdout.readlines():
            with open(hostname + ".log", "a") as logfile:
                logfile.write(timestamp + line)
    print("Closing SSH Session")
    ssh.close()


for hostname, port in targets:
    print(f"Connecting to {hostname} on Port: {port}")
    run_ssh(hostname, port, username, password, remote_commands)

Re: SSH Verbindungen aufbauen, Befehle ausführen und protokollieren

Verfasst: Sonntag 8. März 2020, 14:43
von __deets__
Du solltest auf Modul-Ebene keinen ausgefuehrten Code stehen haben. Sondern das alles in Funktionen stecken, und die mit dem ueblichen

Code: Alles auswählen

if __name__ == '__main__':
     main() # die Funktion muss man natuerlich definieren
guard ganz am Ende schuetzen. Somit verhinderst du ungewolltes ausfuehren beim importieren und musst weniger Sorgfalt walten lassen bei Abhaengigkeiten - wuerde der Code im letzten Teil nach oben gestellt, waere run_ssh noch nicht definiert. Durch packen in Funktionen verhindert man sowas, behaelt mehr Uebersicht, etc.

Re: SSH Verbindungen aufbauen, Befehle ausführen und protokollieren

Verfasst: Sonntag 8. März 2020, 16:06
von lithium213
Also wenn ich das richtig verstanden habe, wäre es so korrekt.
Jetzt würde beim Import gar nichts passieren, auch das Einlesen der beiden Dateien gehört eigentlich in die "execute" Funktion... ?

Gruss

Code: Alles auswählen

import paramiko
import csv
from datetime import datetime


now = datetime.now()  # current date and time
timestamp = now.strftime("%Y-%m-%d, %H:%M:%S ")
username = "admin"
password = "password"



def run_ssh(hostname, port, username, password, remote_commands):
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(hostname=hostname, port=port, username=username, password=password)

    for command in remote_commands:
        stdin, stdout, stderr = ssh.exec_command(command)
        with open(hostname + ".log", "a") as logfile:
            logfile.write(timestamp + command + "\n")
        for line in stdout.readlines():
            with open(hostname + ".log", "a") as logfile:
                logfile.write(timestamp + line)
    print("Closing SSH Session")
    ssh.close()

def execute():
    with open("hosts.txt") as file:
        reader = csv.reader(file)
        targets = list(reader)

    with open('commands.txt', 'r') as file:
        remote_commands = [command.rstrip() for command in file]

    for hostname, port in targets:
        print(f"Connecting to {hostname} on Port: {port}")
        run_ssh(hostname, port, username, password, remote_commands)

if __name__ == '__main__':
    execute()

Re: SSH Verbindungen aufbauen, Befehle ausführen und protokollieren

Verfasst: Sonntag 8. März 2020, 16:10
von __deets__
Das ist definitiv schon besser. Wobei ich username und password GROSS schreiben wuerde, weil es Konstanten sind. Und now und timestamp erstens in eine Zeile stecken, und zweitens nur an der Stelle wo sie auch gebraucht werden einmal berechnen wuerde. Ggf. in execute und dann als Argument uebergeben.

Re: SSH Verbindungen aufbauen, Befehle ausführen und protokollieren

Verfasst: Dienstag 7. April 2020, 12:21
von lithium213
Hallo zusammen,

Ich habe noch eine Frage zu meinem Programm oben. Verbindet das Programm für jeden via SSH auszuführenden Befehl neu, oder werden die Befehle so alle nacheinander und innerhalb einer SSH Sitzung ausgeführt?
Ich habe das Problem, dass ein Gerätetyp nach der Anmeldung noch nicht bereit ist und ich eine Verzögerung einbauen muss.

Vielen Dank und schöne Grüsse

Re: SSH Verbindungen aufbauen, Befehle ausführen und protokollieren

Verfasst: Dienstag 7. April 2020, 12:26
von __deets__
Das ist mE alles in einer Session, darum heisst die ja so - und du machst nur eine auf.

Re: SSH Verbindungen aufbauen, Befehle ausführen und protokollieren

Verfasst: Dienstag 7. April 2020, 12:31
von Jankie
Das hier passiert alles in einer Session:

Code: Alles auswählen

    for command in remote_commands:
        stdin, stdout, stderr = ssh.exec_command(command)
        with open(hostname + ".log", "a") as logfile:
            logfile.write(timestamp + command + "\n")
        for line in stdout.readlines():
            with open(hostname + ".log", "a") as logfile:
                logfile.write(timestamp + line)
    print("Closing SSH Session")

Re: SSH Verbindungen aufbauen, Befehle ausführen und protokollieren

Verfasst: Dienstag 7. April 2020, 12:37
von lithium213
Alles klar,
wo müsste ich dann die Verzögerung (time.sleep(5))einbauen, damit nach der Anmeldung über SSH zuerst eine Pause eingelegt wird, bevor die Befehle ausgeführt werden?

Re: SSH Verbindungen aufbauen, Befehle ausführen und protokollieren

Verfasst: Dienstag 7. April 2020, 12:46
von Sirius3
Nicht für jede Zeile die log-Datei neu öffnen:

Code: Alles auswählen

    with open(hostname + ".log", "a") as logfile:
        for command in remote_commands:
            stdin, stdout, stderr = ssh.exec_command(command)
            logfile.write(timestamp + command + "\n")
            for line in stdout:
                logfile.write(timestamp + line)

Re: SSH Verbindungen aufbauen, Befehle ausführen und protokollieren

Verfasst: Dienstag 7. April 2020, 13:01
von Jankie
Zwischen das connect und der for-Schleife wo die Befehle ausgeführt werden.