SFTP (Protocolo de transferencia segura de archivos) es un protocolo de transferencia de archivos que aprovecha un conjunto de utilidades que brindan acceso seguro a una computadora remota para brindar comunicaciones seguras. Se basa en SSH.
Contenido relacionado
- Cómo trabajar con el cliente SFTP en Linux:10 comandos sftp
- Cómo configurar un servidor SFTP en el servidor Debian 11
- Descargar archivos desde el servidor SFTP utilizando un script de python
- Enumere, cargue y descargue archivos desde un servidor SFTP usando golang
- Cómo configurar un servidor SFTP en OpenSUSE Leap 15.3 Server
- Cómo instalar y configurar el servidor sftp en Ubuntu 20.04
- Cómo configurar un servidor SFTP en CentOS 8/RHEL 8 Server
Requisitos
Para seguir:
- Asegúrese de tener golang instalado localmente.
- Asegúrese de tener acceso a un servidor SFTP:nombre de usuario y contraseña
- Asegúrese de estar familiarizado con el terminal
Tabla de contenido
- Creación de la estructura de directorios e inicialización del módulo golang
- Creación del script:Importaciones
- Creando el script:Función para listar archivos
- Creación del script:Función para descargar archivos
- Creación del script:código completo
- Construyendo y probando el código
1. Creando la estructura de directorios e inicializando el módulo golang
Necesitamos un directorio que tenga nuestro contenido. Créalo con este comando:
mkdir gosftp
Cambie al directorio e inicialice un módulo golang:
➜ cd gosftp
➜ go mod init gosftp
go: creating new go.mod: module gosftp
go: to add module requirements and sums:
go mod tidy
Esto creará un archivo go.mod
con este contenido:
module gosftp
go 1.17
2. Creando el script:Importaciones
No creemos el guión. Crea un archivo llamado main.go
y agregue estas importaciones:
package main
import (
"bufio"
"fmt"
"io"
"log"
"net"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"github.com/pkg/sftp"
)
2. Creando el script:Conexión al servidor
Ahora que tenemos las importaciones, usemos este código para inicializar la conexión al servidor sftp:
// Create a url
rawurl := fmt.Sprintf("sftp://%v:%[email protected]%v", sftpUser, sftpPass, sftpHost)
// Parse the URL
parsedUrl, err := url.Parse(rawurl)
if err != nil {
log.Fatalf("Failed to parse SFTP To Go URL: %s", err)
}
// Get user name and pass
user := parsedUrl.User.Username()
pass, _ := parsedUrl.User.Password()
// Parse Host and Port
host := parsedUrl.Host
// Get hostkey
hostKey := getHostKey(host)
log.Printf("Connecting to %s ...\n", host)
var auths []ssh.AuthMethod
// Try to use $SSH_AUTH_SOCK which contains the path of the unix file socket that the sshd agent uses
// for communication with other processes.
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
}
// Use password authentication if provided
if pass != "" {
auths = append(auths, ssh.Password(pass))
}
// Initialize client configuration
config := ssh.ClientConfig{
User: user,
Auth: auths,
// Auth: []ssh.AuthMethod{
// ssh.KeyboardInteractive(SshInteractive),
// },
// Uncomment to ignore host key check
// HostKeyCallback: ssh.InsecureIgnoreHostKey(),
HostKeyCallback: ssh.FixedHostKey(hostKey),
// HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
// return nil
// },
Timeout: 30 * time.Second,
}
addr := fmt.Sprintf("%s:%s", host, sftpPort)
// Connect to server
conn, err := ssh.Dial("tcp", addr, &config)
if err != nil {
log.Fatalf("Failed to connec to host [%s]: %v", addr, err)
}
defer conn.Close()
// Create new SFTP client
sc, err := sftp.NewClient(conn)
if err != nil {
log.Fatalf("Unable to start SFTP subsystem: %v", err)
}
defer sc.Close()
3. Creando el script:Función para listar archivos
Ahora vamos a crear una función para listar archivos. Estamos usando la conexión al servidor sftp para leer el contenido del directorio remoto y luego agregarlo a una lista de estructuras para regresar.
func listFiles(sc sftp.Client, remoteDir string) (theFiles []remoteFiles, err error) {
files, err := sc.ReadDir(remoteDir)
if err != nil {
return theFiles, fmt.Errorf("Unable to list remote dir: %v", err)
}
for _, f := range files {
var name, modTime, size string
name = f.Name()
modTime = f.ModTime().Format("2006-01-02 15:04:05")
size = fmt.Sprintf("%12d", f.Size())
if f.IsDir() {
name = name + "/"
modTime = ""
size = "PRE"
}
theFiles = append(theFiles, remoteFiles{
Name: name,
Size: size,
ModTime: modTime,
})
}
return theFiles, nil
}
4. Creando el script:Función para subir archivos
Vamos a crear una función para subir archivos al servidor sftp. Usaremos la conexión para abrir un archivo, luego crearemos directorios remotos recursivamente y luego copiaremos los datos del archivo local
// Upload file to sftp server
func uploadFile(sc sftp.Client, localFile, remoteFile string) (err error) {
log.Printf("Uploading [%s] to [%s] ...", localFile, remoteFile)
srcFile, err := os.Open(localFile)
if err != nil {
return fmt.Errorf("Unable to open local file: %v", err)
}
defer srcFile.Close()
// Make remote directories recursion
parent := filepath.Dir(remoteFile)
path := string(filepath.Separator)
dirs := strings.Split(parent, path)
for _, dir := range dirs {
path = filepath.Join(path, dir)
sc.Mkdir(path)
}
// Note: SFTP Go doesn't support O_RDWR mode
dstFile, err := sc.OpenFile(remoteFile, (os.O_WRONLY | os.O_CREATE | os.O_TRUNC))
if err != nil {
return fmt.Errorf("Unable to open remote file: %v", err)
}
defer dstFile.Close()
bytes, err := io.Copy(dstFile, srcFile)
if err != nil {
return fmt.Errorf("Unable to upload local file: %v", err)
}
log.Printf("%d bytes copied", bytes)
return nil
}
5. Creando el script:Función para descargar archivos
Esta función descarga un archivo del servidor remoto dada la ruta remota. En esta función, estamos creando un archivo en el directorio tmp y luego copiando los datos de la ruta del archivo remoto.
// Download file from sftp server
func downloadFile(sc sftp.Client, remoteFile, localFile string) (err error) {
localPath := "/tmp/" + localFile
log.Printf("Downloading [%s] to [%s] ...", remoteFile, localFile)
// Note: SFTP To Go doesn't support O_RDWR mode
srcFile, err := sc.OpenFile(remoteFile, (os.O_RDONLY))
if err != nil {
return fmt.Errorf("unable to open remote file: %v", err)
}
defer srcFile.Close()
dstFile, err := os.Create(localPath)
if err != nil {
return fmt.Errorf("unable to open local file: %v", err)
}
defer dstFile.Close()
bytes, err := io.Copy(dstFile, srcFile)
if err != nil {
return fmt.Errorf("unable to download remote file: %v", err)
}
log.Printf("%d bytes copied to %v", bytes, localPath)
return nil
}
5. Creando el script:Código completo
Este es el código completo del script para realizar operaciones con SFTP usando Golang:
package main
import (
"bufio"
"fmt"
"io"
"log"
"net"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"github.com/pkg/sftp"
)
const (
sftpUser = "citizix"
sftpPass = "Str0ngP4ss"
sftpHost = "10.2.11.10"
sftpPort = "22"
)
func main() {
// Create a url
rawurl := fmt.Sprintf("sftp://%v:%[email protected]%v", sftpUser, sftpPass, sftpHost)
// Parse the URL
parsedUrl, err := url.Parse(rawurl)
if err != nil {
log.Fatalf("Failed to parse SFTP To Go URL: %s", err)
}
// Get user name and pass
user := parsedUrl.User.Username()
pass, _ := parsedUrl.User.Password()
// Parse Host and Port
host := parsedUrl.Host
// Get hostkey
hostKey := getHostKey(host)
log.Printf("Connecting to %s ...\n", host)
var auths []ssh.AuthMethod
// Try to use $SSH_AUTH_SOCK which contains the path of the unix file socket that the sshd agent uses
// for communication with other processes.
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
}
// Use password authentication if provided
if pass != "" {
auths = append(auths, ssh.Password(pass))
}
// Initialize client configuration
config := ssh.ClientConfig{
User: user,
Auth: auths,
// Auth: []ssh.AuthMethod{
// ssh.KeyboardInteractive(SshInteractive),
// },
// Uncomment to ignore host key check
// HostKeyCallback: ssh.InsecureIgnoreHostKey(),
HostKeyCallback: ssh.FixedHostKey(hostKey),
// HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
// return nil
// },
Timeout: 30 * time.Second,
}
addr := fmt.Sprintf("%s:%s", host, sftpPort)
// Connect to server
conn, err := ssh.Dial("tcp", addr, &config)
if err != nil {
log.Fatalf("Failed to connec to host [%s]: %v", addr, err)
}
defer conn.Close()
// Create new SFTP client
sc, err := sftp.NewClient(conn)
if err != nil {
log.Fatalf("Unable to start SFTP subsystem: %v", err)
}
defer sc.Close()
// List files in the root directory .
theFiles, err := listFiles(*sc, ".")
if err != nil {
log.Fatalf("failed to list files in .: %v", err)
}
log.Printf("Found Files in . Files")
// Output each file name and size in bytes
log.Printf("%19s %12s %s", "MOD TIME", "SIZE", "NAME")
for _, theFile := range theFiles {
log.Printf("%19s %12s %s", theFile.ModTime, theFile.Size, theFile.Name)
}
// Upload local file
err = uploadFile(*sc, "/Users/etowett/Desktop/data.csv", "./citizix/data.csv")
if err != nil {
log.Fatalf("could not upload file: %v", err)
}
// Download remote file to local file.
err = downloadFile(*sc, "citizix/data.csv", "data.csv")
if err != nil {
log.Fatalf("Could not download file data.csv; %v", err)
}
return
}
func SshInteractive(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
// Hack, check https://stackoverflow.com/questions/47102080/ssh-in-go-unable-to-authenticate-attempted-methods-none-no-supported-method
answers = make([]string, len(questions))
// The second parameter is unused
for n, _ := range questions {
answers[n] = sftpPass
}
return answers, nil
}
type remoteFiles struct {
Name string
Size string
ModTime string
}
func listFiles(sc sftp.Client, remoteDir string) (theFiles []remoteFiles, err error) {
files, err := sc.ReadDir(remoteDir)
if err != nil {
return theFiles, fmt.Errorf("Unable to list remote dir: %v", err)
}
for _, f := range files {
var name, modTime, size string
name = f.Name()
modTime = f.ModTime().Format("2006-01-02 15:04:05")
size = fmt.Sprintf("%12d", f.Size())
if f.IsDir() {
name = name + "/"
modTime = ""
size = "PRE"
}
theFiles = append(theFiles, remoteFiles{
Name: name,
Size: size,
ModTime: modTime,
})
}
return theFiles, nil
}
// Upload file to sftp server
func uploadFile(sc sftp.Client, localFile, remoteFile string) (err error) {
log.Printf("Uploading [%s] to [%s] ...", localFile, remoteFile)
srcFile, err := os.Open(localFile)
if err != nil {
return fmt.Errorf("Unable to open local file: %v", err)
}
defer srcFile.Close()
// Make remote directories recursion
parent := filepath.Dir(remoteFile)
path := string(filepath.Separator)
dirs := strings.Split(parent, path)
for _, dir := range dirs {
path = filepath.Join(path, dir)
sc.Mkdir(path)
}
// Note: SFTP Go doesn't support O_RDWR mode
dstFile, err := sc.OpenFile(remoteFile, (os.O_WRONLY | os.O_CREATE | os.O_TRUNC))
if err != nil {
return fmt.Errorf("Unable to open remote file: %v", err)
}
defer dstFile.Close()
bytes, err := io.Copy(dstFile, srcFile)
if err != nil {
return fmt.Errorf("Unable to upload local file: %v", err)
}
log.Printf("%d bytes copied", bytes)
return nil
}
// Download file from sftp server
func downloadFile(sc sftp.Client, remoteFile, localFile string) (err error) {
log.Printf("Downloading [%s] to [%s] ...\n", remoteFile, localFile)
// Note: SFTP To Go doesn't support O_RDWR mode
srcFile, err := sc.OpenFile(remoteFile, (os.O_RDONLY))
if err != nil {
return fmt.Errorf("unable to open remote file: %v", err)
}
defer srcFile.Close()
dstFile, err := os.Create(localFile)
if err != nil {
return fmt.Errorf("unable to open local file: %v", err)
}
defer dstFile.Close()
bytes, err := io.Copy(dstFile, srcFile)
if err != nil {
return fmt.Errorf("unable to download remote file: %v", err)
}
log.Printf("%d bytes copied to %v", bytes, dstFile)
return nil
}
// Get host key from local known hosts
func getHostKey(host string) ssh.PublicKey {
// parse OpenSSH known_hosts file
// ssh or use ssh-keyscan to get initial key
file, err := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"))
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to read known_hosts file: %v\n", err)
os.Exit(1)
}
defer file.Close()
scanner := bufio.NewScanner(file)
var hostKey ssh.PublicKey
for scanner.Scan() {
fields := strings.Split(scanner.Text(), " ")
if len(fields) != 3 {
continue
}
if strings.Contains(fields[0], host) {
var err error
hostKey, _, _, _, err = ssh.ParseAuthorizedKey(scanner.Bytes())
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing %q: %v\n", fields[2], err)
os.Exit(1)
}
break
}
}
if hostKey == nil {
fmt.Fprintf(os.Stderr, "No hostkey found for %s", host)
os.Exit(1)
}
return hostKey
}
6. Construyendo y probando el código
Ahora que tenemos nuestro código, vamos a compilarlo y probarlo.
Primero, asegúrese de que todas las dependencias se descarguen usando go mod tidy
comando.
Esta es mi salida:
❯ go mod tidy
go: finding module for package golang.org/x/crypto/ssh
go: finding module for package github.com/pkg/sftp
go: found github.com/pkg/sftp in github.com/pkg/sftp v1.13.4
go: found golang.org/x/crypto/ssh in golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
A continuación, construyamos nuestro código en un gosftp
binario en el directorio actual:
➜ go build -o gosftp
Ahora ejecute el script. Esta es mi salida:
➜ ./gosftp
2021/10/08 13:10:36 Connecting to 10.2.11.10 ...
2021/10/08 13:10:43 Found Files in . Files
2021/10/08 13:10:43 MOD TIME SIZE NAME
2021/10/08 13:10:43 PRE etowett/
2021/10/08 13:10:43 PRE citizix/
2021/10/08 13:10:43 PRE PAYMENTDATA/
2021/10/08 13:10:43 Uploading [/Users/etowett/Desktop/data.csv] to [./citizix/data.csv] ...
2021/10/08 13:10:44 24 bytes copied
2021/10/08 13:10:45 Downloading [citizix/data.csv] to [data.csv] ...
2021/10/08 13:10:46 24 bytes copied to &{0xc000090a20}
Conclusión
En este artículo, logramos crear un script para listar archivos en un servidor sftp remoto, Subir archivos y descargar archivos.