Automatische Datentransfers per SFTP mit Passwort und via Proxy

Aus UUGRN
Version vom 2. August 2016, 17:03 Uhr von Rabe (Diskussion | Beiträge) (Komentierte Gesamtlösung sftp://username@ftp.example.com/)

Dieser Artikel beschreibt einen Spezialfall aus dem Bereich OpenSSH, bei dem mehrere erschwerende Bedingungen zusammenkommen:

  • Es sollen automatisiert Dateien per SFTP ausgetauscht werden
  • Zugriffe sollen gescriptet werden (Batch-Mode, Shell-Scripte), also nicht-interaktiv
  • Zugriff auf den Server ausschließlich mit SFTP möglich, keine normale SSH-Shell, kein scp, kein rsync+ssh oder ähnliches.
  • Zugriff auf das Zielsystem nur per HTTP-Proxy möglich
  • Zugriff auf den Account nur mit Benutzername und Passwort möglich, keine Public-Key Authentifikation möglich

Allgemein

Für jede der Bedinungen gibt es einzelne Lösungen, die im Zusammenspiel allerdings trickreich sein können.

nur SFTP, automatisiert

Für viele Anwendungsfälle aus dem Bereich automatisierter Filetransfer greift man zu Lösungen wie scp oder rsync+ssh oder andere Konstrukte die direkt auf ssh aufbauen. In diesem Fall ist auf dem Zielserver nur sftp möglich.

sftp ist ein sogenannte subsystem aus SSH und nur genau dieses Subsystem ist für uns erreichbar.

This service allows sftp connections only.

Wir müssen also für alle Zugriffe das "sftp"-Tool verwenden, oder zumindest das "sftp"-Subsystem per ssh ansprechen.

Mittels sftp -b kann man einfache vordefinierte Batchscripte ausführen, also eine Abfolge von diesen commands:

sftp> help
Available commands:
bye                                Quit sftp
cd path                            Change remote directory to 'path'
chgrp grp path                     Change group of file 'path' to 'grp'
chmod mode path                    Change permissions of file 'path' to 'mode'
chown own path                     Change owner of file 'path' to 'own'
df [-hi] [path]                    Display statistics for current directory or
                                   filesystem containing 'path'
exit                               Quit sftp
get [-Ppr] remote [local]          Download file
help                               Display this help text
lcd path                           Change local directory to 'path'
lls [ls-options [path]]            Display local directory listing
lmkdir path                        Create local directory
ln [-s] oldpath newpath            Link remote file (-s for symlink)
lpwd                               Print local working directory
ls [-1afhlnrSt] [path]             Display remote directory listing
lumask umask                       Set local umask to 'umask'
mkdir path                         Create remote directory
progress                           Toggle display of progress meter
put [-Ppr] local [remote]          Upload file
pwd                                Display remote working directory
quit                               Quit sftp
rename oldpath newpath             Rename remote file
rm path                            Delete remote file
rmdir path                         Remove remote directory
symlink oldpath newpath            Symlink remote file
version                            Show SFTP version
!command                           Execute 'command' in local shell
!                                  Escape to local shell
?                                  Synonym for help

És gibt wiederum verschiedene Tools, die im Hintergrund sftp sprechen, zum Beispiel lftp mit seiner mächtigen Scriptsprache oder sshfs als FUSE-Treiber für ein auf sftp basiertes Dateisystem, das man in Linux (nicht Gnome, KDE) mounten kann. Diese Möglichkeiten sind hier out-of-scope.

Wichtig zu wissen ist, dass das sftp(1)-Tool die Datei ssh_config(5) (in /etc/ssh/ und ~/.ssh/config) verwendet.


Zielsystem nur per Proxy erreichbar

Szenario
Wir befinden uns im Backend eines DataWarehouse oder im einen Entwicklernetz ohne direkten Zugang zum Internet.
Wir müssen den einen oder anderen Proxy verwenden, wenn wir auf Ziele im Internet zugreifen können möchten.

OpenSSH selbst kennt keine Mechanismen für SOCKS-Proxy oder HTTP(s)-Proxy, bietet allerdings eine Schnittstelle an, wo man eigene Proxy-Clients konfigurieren kann. Diesen Client mit allen erforderlichen Optionen konfiguriert man über ProxyCommand in ssh_config(5) ein.

Beispiele dazu

Gerade in Verbindung mit HTTP-Proxy ist wichtig, dass der HTTP-Proxy den CONNECT-Zugriff auf das Zielsystem erlaubt. Normalerweise ist das für HTTPS nur der Port 443, Details Dazu unter Firewall Piercing#Einen_Webproxy_als_Transportweg_verwenden

HTTP-Proxy mit netcat, in ssh_config
ProxyCommand /bin/nc.openbsd -X CONNECT -x proxy.dmz.lan:3128 %h %p

Alternativ kann man eine Verbindung zur Außenwelt auch andere Konstrukte benutzen zum Beispiel eine SSH-Verbindung zu einem Server in der DMZ (erreichbar von innen und mit Zugriff auf das Internet):

DMZ-Server mit netcat-openbsd, in ssh_config
ProxyCommand /usr/bin/ssh server.dmz.lan /bin/nc.openbsd %h %p

Zugriff nur mit Passwort

Falls man das unbeschreibliche Pech hat auf einen SFTP-Server zugreifen zu müssen, auf den man sich ausschließlich per Username+Passwort einloggen kann, muss man zu externen Hilfsmitteln greifen, eines davon ist sshpass, andere Lösungen bevorzugen expect.

OpenSSH kennt keine Methoden für eine Passwortübergabe, es fragt ausschließlich interaktiv (über ein Terminal/pty). sshpass ruft also ssh oder sftp auf und verwendet dazu ein Pseudo-TTY, damit das jeweilige Binary auch nach dem Passwort fragt. Dieses TTY ist unabhängig vom TTY der Shell, in der wir das ausprobieren.

sshpass wiederum akzeptiert ein Passwort über verschiedene Parameter:

  • als Commandline Argument: -ppassword
  • aus der ersten Zeile einer Datei mit -ffilename
  • aus einem Filedescriptor über -dnumber
  • oder aus einer Umgebungsvariable SSHPASS mittels -e


Gesamtlösung

Die Gesamtlösung ist mehr als der oben vorgestellten Einzelaspekte. Die Lösung berücksichtigt den Aspekt, dass man bei OpenSSH mehrere parallele oder aufeinander folgende SSH-Verbindungen durch eine ControlMaster-Verbindung leiten kann, lediglich der erste Zugriff muss dabei Authentifiziert werden (ControlMaster, ControlPath, ControlPersist). Diese Eigenschaft machen wir uns hier zunutze.

ssh_config

Zunächst eine vollständige ~/.ssh/config mit Zeilennummern:

01      Host ftp.example.com
02              Hostname ftp.example.com
03              Port 22
04              Protocol 2
05              StrictHostKeyChecking no
06              ServerAliveCountMax 30
07              ServerAliveInterval 10
08              ProxyCommand /usr/bin/nohup /bin/nc.openbsd -X CONNECT -x proxy.dmz.lan:3128 %h %p
09              ControlMaster auto
10              ControlPath /run/shm/ssh_master_%r@%h:%p.sock
11              ControlPersist yes
12              KbdInteractiveAuthentication no
13              PasswordAuthentication yes
14              PubkeyAuthentication no
15              PreferredAuthentications password
16              RequestTTY no
ProxyCommand (Zeile 8)
  • ESSENZIELL wichtig: Das nohup im ProxyCommand verhindert, dass sshpass den netcat-Prozess (nc.openbsd) beendet (SIGHUP), das scheint mir einer der Bugs von sshpass zu sein.
  • /bin/nc.openbsd -X CONNECT -x proxy.dmz.lan:3128 %h %p verwendet die CONNECT-Methode des HTTP-Proxy-Servers proxy.dmz.lan auf Port 3128, um die TCP-Verbindung zum Zielserver (%h:%p ftp.example.com:22) herzustellen. Anstelle eines HTTP-Proxys könnte auch ein SOCKS-Proxy verwendet werden (-X 5), anstelle von /bin/nc.openbsd kann auch ein SSH-Aufruf stehen, der dann netcat als remote command ausführt
  • Man kann den Proxy-Zugriff testen, indem man das ProxyCommand einzeln aufruft:
/bin/nc.openbsd -X CONNECT -x proxy.dmz.lan:3128 ftp.example.com 22
SSH-2.0-OpenSSH_6.0p1 Debian-4+deb7u2
^C
Timeout vermeiden (Zeilen 6+7)
Aufgrund von mit ServerAliveInterval und ServerAliveCountMax werden regelmäßig (zB alle 10sec) Dummy-Pakete durch die SSH-Verbindung geschickt. Das soll verhindern, dass der Proxy-Server oder ein NAT-Router die Verbindung aufgrund von Inaktivität vergisst oder aktiv beendet..
ControlMaster (Zeilen 9-11)
Die ControlMaster Optionen erzeugen einen UnixDomain-Socket zB /run/shm/ssh_master_username@ftp.example.com:22.sock. Alle SSH-Zugriffe in der Form username@ftp.example.com:22 werden (sofern vorhanden) durch diesen Socket geleitet, welcher durch die initiale SSH-Verbindung gehalten wird.
Authentifikationsmethode gezielt wählen(Zeilen 12-15)
Mit KbdInteractiveAuthentication, PasswordAuthentication, PubkeyAuthentication und PreferredAuthentications wird die passwortbasierte Authentifikation erzwungen, andere Möglichkeiten (zB Public-Keys) werden hier unterbunden und können somit auch bei vorhadenen Keys nicht zu Fehlversuchen führen.
RequestTTY (Zeile 16)
Mit RequestTTY no sagen wir dem initialen SSH-Aufruf, dass er obwohl er in einem TTY-Kontext (durch sshpass) aufgerufen wird selbst kein TTY auf dem Server anfordern soll. Es wird für sftp nicht benötigt und könnte nur zu Fehlern führen.

Verbindungsaufbau

Die initiale SSH-Verbidung (sftp) wird authentifiziert und tut sonst nichts weiter als die dann offene Verbindung aufrecht zu erhalten.

export SSHPASS=geheimespasswort 
/usr/bin/sshpass -e ssh username@ftp.example.com -s sftp -f -N

sshpass soll das Passwort aus dem Prozess-Environment beziehen. Es wird ssh statt sftp ausgeführt, jedoch mit speziellen Parametern: durch "-s sftp" sprechen wir gezielt das "sftp"-Subsystem an, mit "-f -N" wird der Client in den Hintergerund geforkt (-f) und führt sonst nichts weiter aus (-N).

sshpass führt ssh aus und das wiederum dann

/usr/bin/nohup /bin/nc.openbsd -X CONNECT -x proxy.dmz.lan:3128 %h %p

.

Alle weiteren Zugriffe auf username@ftp.example.com verwenden dann die bestehende Verbindung, zB
sftp username@ftp.example.com  < <(echo dir)
oder auch als sshfs-Mount (verwendet sftp)
sshfs username@ftp.example.com ./ftp/
ls -la ./ftp/