Dateien per Node.js und ssh2-sftp-client zu SFTP exportieren
Laden Sie eine lokale Datei mit put() hoch, mit
get() herunter und vergleichen Sie die Bytes, bevor Sie den Erfolg melden.
Diese Anleitung nutzt ssh2-sftp-client mit SSH-Schlüsselauthentifizierung und einem
temporären lokalen OpenSSH-Server. So können Sie die gesamte Übertragung ohne bestehendes
SFTP-Konto ausprobieren.
Sowohl den Server als auch den Benutzer authentifizieren
SFTP überträgt Dateien über SSH. Ihr Clientschlüssel identifiziert Sie gegenüber dem Server;
der Hostschlüssel des Servers identifiziert diesen gegenüber Ihnen. Der zugrunde liegende
Client ssh2
akzeptiert Hostschlüssel automatisch, sofern Sie keinen hostVerifier angeben.
Deshalb prüft das Beispiel den Hostschlüssel explizit.
Beziehen Sie bei einem bestehenden Server dessen Hostschlüssel-Fingerabdruck über einen
vertrauenswürdigen Kanal von seinem Administrator. Mit hostHash: 'sha256' übergibt
ssh2 der Prüffunktion einen 64-stelligen Hex-Digest in Kleinbuchstaben
aus den rohen Bytes des öffentlichen SSH-Schlüssels. Diese Darstellung unterscheidet sich vom
base64-Fingerabdruck SHA256:, den OpenSSH-Werkzeuge ausgeben. Übernehmen Sie
den erwarteten Wert nicht aus einer ungeprüften ersten Verbindung.
Das Clientprojekt erstellen
Die folgenden Befehle verwenden Bash, Node.js 26.8.1, Yarn 4.12.0,
ssh-keygen und Docker Engine 28 oder neuer unter Linux. Der Client ist auf
ssh2-sftp-client 12.1.1 festgelegt.
Node führt die TypeScript-Datei direkt aus; ein Build-Schritt ist
nicht nötig. Das Beispiel hält beide Kopien der Datei im Arbeitsspeicher. Verwenden Sie daher
eine kleine Datei, die problemlos in den RAM passt.
Führen Sie diese Befehle in einem Verzeichnis aus, in dem node-sftp-demo noch
nicht existiert. Die Verkettung mit && stoppt die Einrichtung, wenn
das Erstellen des Verzeichnisses oder der Wechsel dorthin fehlschlägt. Die leere Lockdatei hält
dieses Yarn-Projekt eigenständig, auch wenn das übergeordnete Verzeichnis ein anderes Projekt ist.
mkdir node-sftp-demo &&
cd node-sftp-demo &&
printf '{"private":true,"type":"module"}\n' > package.json &&
touch yarn.lock &&
yarn add --exact ssh2-sftp-client@12.1.1 &&
ssh-keygen -q -t ed25519 -N '' -f client_key &&
printf 'Hello over SFTP.\n' > example.txt
Bleiben Sie für die weiteren Befehle in diesem Verzeichnis. client_key ist
ein unverschlüsselter privater Schlüssel, der nur für diese lokale Übung bestimmt ist. Nur sein
öffentlicher Teil gelangt in den Container. Halten Sie echte private Schlüssel aus der
Versionsverwaltung heraus und nutzen Sie das für Ihren Server freigegebene
Authentifizierungsverfahren.
Einen lokalen SFTP-Server starten
Führen Sie Folgendes im ersten Terminal aus. Es installiert OpenSSH in einem Ubuntu-24.04-Container,
legt den Benutzer demo an und gibt ihm ein privates, beschreibbares
Verzeichnis /home/demo/incoming.
ForceCommand internal-sftp beschränkt Sitzungen auf SFTP;
Passwortanmeldung und Weiterleitung sind deaktiviert.
docker run --rm --name node-sftp-demo \
--publish 127.0.0.1::22 \
--mount "type=bind,src=$PWD/client_key.pub,dst=/client_key.pub,readonly" \
ubuntu:24.04 bash -euc '
if ! command -v sshd >/dev/null; then
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends openssh-server
fi
useradd -m -s /bin/sh demo
passwd -d demo
install -d -m 700 -o demo -g demo /home/demo/.ssh /home/demo/incoming
install -m 600 -o demo -g demo /client_key.pub /home/demo/.ssh/authorized_keys
mkdir -p /run/sshd
ssh-keygen -q -t ed25519 -N "" -f /etc/ssh/demo_host_key
exec /usr/sbin/sshd -D -e -f /dev/null \
-o HostKey=/etc/ssh/demo_host_key \
-o PasswordAuthentication=no \
-o KbdInteractiveAuthentication=no \
-o PermitRootLogin=no \
-o AllowUsers=demo \
-o DisableForwarding=yes \
-o "Subsystem=sftp internal-sftp" \
-o ForceCommand=internal-sftp
'
Lassen Sie den Prozess weiterlaufen, nachdem er eine Zeile mit dem Anfang
Server listening on ausgegeben hat. Falls er vorher endet, beheben Sie den ausgegebenen
Fehler, bevor Sie fortfahren. Docker wählt einen verfügbaren Hostport und
bindet ihn an die Loopback-Schnittstelle. Die Dateien und der
Hostschlüssel des Servers existieren nur in diesem Container und verschwinden, wenn er entfernt wird.
Öffnen Sie ein zweites Terminal in node-sftp-demo. Lesen Sie den zugewiesenen Port
aus und kopieren Sie den öffentlichen Hostschlüssel über Ihre lokale Docker-Verbindung.
Sie ist in diesem Beispiel der vertrauenswürdige Administrationskanal:
docker port node-sftp-demo 22/tcp &&
docker cp node-sftp-demo:/etc/ssh/demo_host_key.pub server_host_key.pub
Der erste Befehl gibt eine Adresse wie 127.0.0.1:32768 aus. Verwenden Sie unten
den tatsächlich ausgegebenen Port. Das Kopieren des öffentlichen Schlüssels ersetzt eine bereits
vorhandene Datei server_host_key.pub in diesem Demoverzeichnis. Jeder neue Container
hat einen neuen Hostschlüssel. Wiederholen Sie diesen Schritt daher, wenn Sie ihn neu erstellen.
Eine Datei hochladen und prüfen
Speichern Sie dies als transfer.ts. Der lokale Pfad und der vollständige
Dateipfad auf dem Server werden als Befehlszeilenargumente übergeben. Das übergeordnete Verzeichnis
auf dem Server muss bereits existieren und Ihrem Konto das Schreiben und Lesen von Dateien erlauben.
import { readFile } from 'node:fs/promises'
import Client from 'ssh2-sftp-client'
let stage = 'configuration'
async function main(): Promise<void> {
const [localPath, remotePath] = process.argv.slice(2)
const { SFTP_HOST, SFTP_PORT, SFTP_USERNAME, SFTP_KEY_FILE, SFTP_HOST_SHA256 } = process.env
const port = Number(SFTP_PORT ?? '22')
if (
!localPath || !remotePath || !SFTP_HOST || !SFTP_USERNAME || !SFTP_KEY_FILE ||
!/^[a-f0-9]{64}$/.test(SFTP_HOST_SHA256 ?? '') ||
!Number.isInteger(port) || port < 1 || port > 65535
) {
throw new Error('Provide two paths, SFTP settings, and a verified SHA-256 hex fingerprint')
}
stage = 'reading local files'
const original = await readFile(localPath)
const privateKey = await readFile(SFTP_KEY_FILE)
const sftp = new Client()
try {
stage = 'connect'
await sftp.connect({
host: SFTP_HOST,
port,
username: SFTP_USERNAME,
privateKey,
hostHash: 'sha256',
hostVerifier: (fingerprint: string) => fingerprint === SFTP_HOST_SHA256,
readyTimeout: 10000,
})
stage = 'upload'
await sftp.put(original, remotePath)
stage = 'download verification'
// Version 12.1.1 can return an empty array for a zero-byte download.
const downloaded = Buffer.from(await sftp.get(remotePath))
if (!original.equals(downloaded)) {
throw new Error('Downloaded bytes differ from the uploaded bytes')
}
} finally {
await sftp.end()
}
console.log(`Verified ${original.length} bytes at ${remotePath}`)
}
main().catch(() => {
console.error(`SFTP transfer failed during ${stage}; check the settings and server logs`)
process.exitCode = 1
})
put() ersetzt eine vorhandene Datei auf dem Server am gewählten Pfad.
Verwenden Sie ein Ziel, das gefahrlos überschrieben werden kann. Die Datei wird direkt
beschrieben: Eine unterbrochene Übertragung kann eine abgeschnittene oder unvollständige Datei
hinterlassen. Eine fehlgeschlagene Prüfung macht den Schreibvorgang nicht rückgängig. Die
heruntergeladene Kopie bleibt im Arbeitsspeicher; das Skript erstellt oder überschreibt keine
lokale Downloaddatei. Buffer.from() normalisiert sowohl das Ergebnis eines leeren
Downloads als auch gewöhnliche Binärpuffer, sodass eine gültige Null-Byte-Datei die Prüfung besteht.
Das Paket dokumentiert
put() und get().
Hier werden sie nacheinander über dieselbe Verbindung ausgeführt. Der Block
finally ruft nach Erfolg oder Fehlschlag end() auf.
Bei Fehlern erhält der Prozess einen Exitstatus ungleich null. Der Verbindungsparameter
readyTimeout begrenzt den SSH-Handshake, nicht die gesamte Übertragung.
Verwenden Sie bei unbeaufsichtigter Ausführung eine Zeitgrenze für den gesamten Job.
Legen Sie die Verbindungsdaten im zweiten Terminal fest. Ersetzen Sie
32768 durch den von Docker ausgegebenen Port. Der Befehl decodiert das
base64-Feld des vertrauenswürdigen öffentlichen Schlüssels und bildet den Hash dieser Bytes,
nicht den Hash des Textes in der Datei .pub:
export SFTP_HOST=127.0.0.1 SFTP_PORT=32768 SFTP_USERNAME=demo SFTP_KEY_FILE=client_key
SFTP_HOST_SHA256=$(node --input-type=module -e '
import { createHash } from "node:crypto"
import { readFileSync } from "node:fs"
const [, key] = readFileSync("server_host_key.pub", "utf8").trim().split(/\s+/)
console.log(createHash("sha256").update(Buffer.from(key, "base64")).digest("hex"))
') &&
export SFTP_HOST_SHA256 &&
yarn node transfer.ts example.txt /home/demo/incoming/example.txt
Für die mitgelieferte Datei wird bei Erfolg Folgendes ausgegeben:
Verified 17 bytes at /home/demo/incoming/example.txt
Um Ihre eigene kleine Datei zu übertragen, ersetzen Sie die beiden Pfadargumente. Setzen Sie Pfade mit Leerzeichen in Anführungszeichen. Ein erfolgreicher Vergleich belegt, dass der Server in diesem Moment die von Ihnen hochgeladenen Bytes zurückgegeben hat. Er belegt weder die dauerhafte Sicherung der Datei noch, dass ein anderer Prozess sie verarbeitet hat.
Eine fehlgeschlagene Übertragung diagnostizieren
| Fehlerphase | Was zu prüfen ist |
|---|---|
configuration | Geben Sie beide Pfade, einen ganzzahligen Port von 1 bis 65535 und den Fingerabdruck in Hex-Form an. |
reading local files | Prüfen Sie, ob Quelldatei und privater Schlüssel existieren und lesbar sind. |
connect | Prüfen Sie Port, vertrauenswürdigen Hostschlüssel, Benutzernamen und autorisierten Clientschlüssel. Eine Änderung des Hostschlüssels muss mit dem Administrator überprüft werden. |
upload | Prüfen Sie das übergeordnete Verzeichnis auf dem Server und die Schreibrechte. Das Skript erstellt keine Verzeichnisse. |
download verification | Prüfen Sie die Leserechte und ob ein anderer Prozess die Datei auf dem Server verschoben oder geändert hat. |
Ein Verbindungsabbruch durch den Server kann beide Übertragungsschritte fehlschlagen lassen.
Prüfen Sie vor einem erneuten Versuch das Serverterminal und ob eine unvollständige Zieldatei
vorhanden ist. Entfernen Sie die Hostschlüsselprüfung nicht, um einen Verbindungsfehler zu umgehen.
Verwenden Sie bei einem bestehenden Server die Pfade aus Sicht des jeweiligen SFTP-Kontos.
Ein durch chroot eingeschränktes Konto sieht möglicherweise /incoming/example.txt,
obwohl der Administrator einen längeren Dateisystempfad sieht.
Den lokalen Server stoppen
Führen Sie zum Abschluss Folgendes im zweiten Terminal aus:
docker stop node-sftp-demo
Da der Server mit --rm gestartet wurde, werden beim Stoppen der Container
und die darin hochgeladenen Dateien gelöscht. Ihr lokales Projekt, die Beispieldatei und der nur
für diese Übung bestimmte Clientschlüssel bleiben in node-sftp-demo erhalten.
