sftpfs

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 12, 2025 License: MIT Imports: 12 Imported by: 0

README

SftpFs - SFTP FileSystem

Go Reference Go Report Card CI License

The sftpfs package provides bidirectional SFTP support for the absfs ecosystem:

  • Client Mode: Access remote SFTP servers as an absfs.FileSystem
  • Server Mode: Serve any absfs.FileSystem over SFTP protocol

Features

  • Bidirectional: Both client and server implementations
  • Secure file operations: All operations encrypted over SSH
  • Multiple authentication methods: Password and SSH key authentication
  • Standard interface: Client implements absfs.Filer for seamless integration
  • Full file operations: Read, write, seek, truncate, and more
  • Directory operations: Create, remove, and list directories
  • Server mode: Expose any absfs filesystem via SFTP

Install

go get github.com/absfs/sftpfs

Client Usage

Password Authentication
package main

import (
    "log"
    "os"

    "github.com/absfs/sftpfs"
)

func main() {
    // Connect using password
    fs, err := sftpfs.Dial("example.com:22", "username", "password")
    if err != nil {
        log.Fatal(err)
    }
    defer fs.Close()

    // Use like any other filesystem
    f, _ := fs.OpenFile("/remote/path/file.txt", os.O_RDONLY, 0)
    defer f.Close()

    // Read, write, etc.
}
Key-Based Authentication
package main

import (
    "log"
    "os"

    "github.com/absfs/sftpfs"
)

func main() {
    // Read private key
    key, err := os.ReadFile("/home/user/.ssh/id_rsa")
    if err != nil {
        log.Fatal(err)
    }

    // Connect using SSH key
    fs, err := sftpfs.DialWithKey("example.com:22", "username", key)
    if err != nil {
        log.Fatal(err)
    }
    defer fs.Close()

    // Use filesystem operations
    fs.Mkdir("/remote/newdir", 0755)
}
Advanced Configuration
package main

import (
    "log"
    "time"

    "github.com/absfs/sftpfs"
)

func main() {
    config := &sftpfs.Config{
        Host:     "example.com:22",
        User:     "username",
        Password: "password",
        Timeout:  60 * time.Second,
    }

    fs, err := sftpfs.New(config)
    if err != nil {
        log.Fatal(err)
    }
    defer fs.Close()

    // Use filesystem
}

Server Usage

The server mode allows you to expose any absfs.FileSystem over SFTP protocol. This is useful for creating custom file servers, testing, or bridging different storage backends.

Basic Server
package main

import (
    "crypto/rand"
    "crypto/rsa"
    "log"
    "net"

    "github.com/absfs/memfs"
    "github.com/absfs/sftpfs"
    "golang.org/x/crypto/ssh"
)

func main() {
    // Create a filesystem to serve (could be any absfs.FileSystem)
    fs, err := memfs.NewFS()
    if err != nil {
        log.Fatal(err)
    }

    // Generate a host key (in production, load from file)
    privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
    if err != nil {
        log.Fatal(err)
    }
    signer, err := ssh.NewSignerFromKey(privateKey)
    if err != nil {
        log.Fatal(err)
    }

    // Create SFTP server with password authentication
    server := sftpfs.NewServer(fs, &sftpfs.ServerConfig{
        HostKeys: []ssh.Signer{signer},
        PasswordCallback: sftpfs.SimplePasswordAuth("admin", "secret"),
    })

    // Listen and serve
    listener, err := net.Listen("tcp", ":2222")
    if err != nil {
        log.Fatal(err)
    }
    log.Println("SFTP server listening on :2222")
    log.Fatal(server.Serve(listener))
}
Multi-User Authentication
users := map[string]string{
    "alice": "password1",
    "bob":   "password2",
    "carol": "password3",
}

server := sftpfs.NewServer(fs, &sftpfs.ServerConfig{
    HostKeys:         []ssh.Signer{signer},
    PasswordCallback: sftpfs.MultiUserPasswordAuth(users),
})
Public Key Authentication
server := sftpfs.NewServer(fs, &sftpfs.ServerConfig{
    HostKeys: []ssh.Signer{hostKey},
    PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
        // Verify the public key against your authorized keys
        authorizedKey, _, _, _, err := ssh.ParseAuthorizedKey(authorizedKeysData)
        if err != nil {
            return nil, err
        }

        if ssh.KeysEqual(key, authorizedKey) {
            return nil, nil // Authentication successful
        }
        return nil, fmt.Errorf("unknown public key")
    },
})
Serving Different Filesystems
// Serve local files
osfs, _ := osfs.NewFS()
server := sftpfs.NewServer(osfs, config)

// Serve in-memory files
memfs, _ := memfs.NewFS()
server := sftpfs.NewServer(memfs, config)

// Serve composed filesystems
union := unionfs.New(baseFS, overlayFS)
server := sftpfs.NewServer(union, config)

Testing

Unit Tests

Unit tests use mock interfaces and do not require an SFTP server:

go test -v ./...
Integration Tests

Integration tests require a running SFTP server. The project includes Docker Compose configuration for easy setup:

# Start the SFTP server
docker-compose up -d

# Run integration tests
go test -v -tags=integration ./...

# Stop the server
docker-compose down

The Docker setup uses atmoz/sftp with the following credentials:

  • Host: localhost:2222
  • Username: testuser
  • Password: testpass
Benchmarks

Run benchmarks to measure performance:

go test -bench=. -benchmem ./...
Coverage

Check test coverage:

go test -cover ./...

Security Note

The current implementation uses ssh.InsecureIgnoreHostKey() which skips host key verification. For production use, you should implement proper host key verification to prevent man-in-the-middle attacks.

Example of implementing host key verification:

// For production, implement proper host key callback
hostKeyCallback, err := knownhosts.New("/home/user/.ssh/known_hosts")
if err != nil {
    log.Fatal(err)
}
// Use hostKeyCallback in your SSH configuration

API Reference

Client Types and Methods
FileSystem Methods
Method Description
New(config *Config) Create a new SFTP filesystem with configuration
Dial(host, user, password string) Quick connect with password auth
DialWithKey(host, user string, privateKey []byte) Quick connect with key auth
Close() Close the SFTP connection
OpenFile(name string, flag int, perm os.FileMode) Open or create a file
Mkdir(name string, perm os.FileMode) Create a directory
Remove(name string) Remove a file or empty directory
Rename(oldpath, newpath string) Rename a file
Stat(name string) Get file information
Chmod(name string, mode os.FileMode) Change file mode
Chtimes(name string, atime, mtime time.Time) Change file times
Chown(name string, uid, gid int) Change file ownership
File Methods
Method Description
Name() Return the file name
Read(b []byte) Read bytes from file
ReadAt(b []byte, off int64) Read at specific offset
Write(b []byte) Write bytes to file
WriteAt(b []byte, off int64) Write at specific offset
WriteString(s string) Write string to file
Seek(offset int64, whence int) Seek within file
Close() Close the file
Stat() Get file information
Sync() Sync file (no-op for SFTP)
Truncate(size int64) Truncate file to size
Readdir(n int) Read directory entries
Readdirnames(n int) Read directory entry names
Server Types and Methods
Server
Method Description
NewServer(fs absfs.FileSystem, config *ServerConfig) Create a new SFTP server
Serve(listener net.Listener) Accept connections and serve SFTP
ServeConn(conn net.Conn) Handle a single connection
SSHConfig() Get the underlying SSH server config
ServerConfig
Field Type Description
HostKeys []ssh.Signer SSH host keys (at least one required)
PasswordCallback func(ssh.ConnMetadata, []byte) (*ssh.Permissions, error) Password authentication handler
PublicKeyCallback func(ssh.ConnMetadata, ssh.PublicKey) (*ssh.Permissions, error) Public key authentication handler
NoClientAuth bool Allow connections without authentication (testing only)
MaxAuthTries int Maximum authentication attempts (default: 6)
ServerVersion string SSH server version string
Helper Functions
Function Description
SimplePasswordAuth(user, pass string) Create single-user password callback
MultiUserPasswordAuth(users map[string]string) Create multi-user password callback
NewServerHandler(fs absfs.FileSystem) Create low-level SFTP handlers

absfs

Check out the absfs repo for more information about the abstract filesystem interface and features like filesystem composition.

LICENSE

This project is governed by the MIT License. See LICENSE

Documentation

Overview

Package sftpfs implements an absfs.Filer for SFTP (SSH File Transfer Protocol). It provides secure file operations over SSH using the github.com/pkg/sftp library.

Index

Constants

This section is empty.

Variables

View Source
var ErrAuthFailed = &AuthError{msg: "authentication failed"}

ErrAuthFailed is returned when authentication fails.

View Source
var ErrNotDir = os.ErrInvalid

ErrNotDir is returned when a path is expected to be a directory but is not.

Functions

func MultiUserPasswordAuth

func MultiUserPasswordAuth(users map[string]string) func(ssh.ConnMetadata, []byte) (*ssh.Permissions, error)

MultiUserPasswordAuth returns a PasswordCallback that validates against a user/password map.

func NewServerHandler

func NewServerHandler(fs absfs.FileSystem) sftp.Handlers

NewServerHandler creates SFTP handlers that serve the given absfs.FileSystem.

func SimplePasswordAuth

func SimplePasswordAuth(username, password string) func(ssh.ConnMetadata, []byte) (*ssh.Permissions, error)

SimplePasswordAuth returns a PasswordCallback that validates a single user/password. This is a convenience function for simple authentication scenarios.

Types

type AuthError

type AuthError struct {
	// contains filtered or unexported fields
}

AuthError represents an authentication failure.

func (*AuthError) Error

func (e *AuthError) Error() string

type Config

type Config struct {
	Host     string        // Host address (e.g., "example.com:22")
	User     string        // Username for authentication
	Password string        // Password for authentication (if using password auth)
	Key      []byte        // Private key for authentication (if using key auth)
	Timeout  time.Duration // Connection timeout
}

Config contains the configuration for connecting to an SFTP server.

type File

type File struct {
	// contains filtered or unexported fields
}

File wraps an sftp.File to implement absfs.File interface.

func (*File) Close

func (f *File) Close() error

Close closes the SFTP file.

func (*File) Name

func (f *File) Name() string

Name returns the name of the file.

func (*File) Read

func (f *File) Read(b []byte) (int, error)

Read reads from the SFTP file.

func (*File) ReadAt

func (f *File) ReadAt(b []byte, off int64) (int, error)

ReadAt reads from the SFTP file at a specific offset.

func (*File) ReadDir

func (f *File) ReadDir(n int) ([]iofs.DirEntry, error)

ReadDir reads the directory and returns fs.DirEntry values.

func (*File) Readdir

func (f *File) Readdir(n int) ([]os.FileInfo, error)

Readdir reads directory entries.

func (*File) Readdirnames

func (f *File) Readdirnames(n int) ([]string, error)

Readdirnames reads directory entry names.

func (*File) Seek

func (f *File) Seek(offset int64, whence int) (int64, error)

Seek seeks within the SFTP file.

func (*File) Stat

func (f *File) Stat() (os.FileInfo, error)

Stat returns file info for the SFTP file.

func (*File) Sync

func (f *File) Sync() error

Sync commits the current contents of the file to stable storage.

func (*File) Truncate

func (f *File) Truncate(size int64) error

Truncate changes the size of the file.

func (*File) Write

func (f *File) Write(b []byte) (int, error)

Write writes to the SFTP file.

func (*File) WriteAt

func (f *File) WriteAt(b []byte, off int64) (int, error)

WriteAt writes to the SFTP file at a specific offset.

func (*File) WriteString

func (f *File) WriteString(s string) (int, error)

WriteString writes a string to the SFTP file.

type FileSystem

type FileSystem struct {
	// contains filtered or unexported fields
}

FileSystem implements absfs.Filer for SFTP protocol.

func Dial

func Dial(host, user, password string) (*FileSystem, error)

Dial creates a new SFTP filesystem by dialing the specified host. This is a convenience function for simple password-based authentication.

func DialWithKey

func DialWithKey(host, user string, privateKey []byte) (*FileSystem, error)

DialWithKey creates a new SFTP filesystem using SSH key authentication.

func New

func New(config *Config) (*FileSystem, error)

New creates a new SFTP filesystem with the given configuration.

func (*FileSystem) Chmod

func (fs *FileSystem) Chmod(name string, mode os.FileMode) error

Chmod changes the mode of a file on the SFTP server.

func (*FileSystem) Chown

func (fs *FileSystem) Chown(name string, uid, gid int) error

Chown changes the owner and group of a file on the SFTP server.

func (*FileSystem) Chtimes

func (fs *FileSystem) Chtimes(name string, atime time.Time, mtime time.Time) error

Chtimes changes the access and modification times of a file on the SFTP server.

func (*FileSystem) Close

func (fs *FileSystem) Close() error

Close closes the SFTP connection.

func (*FileSystem) Mkdir

func (fs *FileSystem) Mkdir(name string, perm os.FileMode) error

Mkdir creates a directory on the SFTP server.

func (*FileSystem) OpenFile

func (fs *FileSystem) OpenFile(name string, flag int, perm os.FileMode) (absfs.File, error)

OpenFile opens a file on the SFTP server.

func (*FileSystem) ReadDir

func (fs *FileSystem) ReadDir(name string) (entries []iofs.DirEntry, err error)

ReadDir reads the directory named by name and returns a list of directory entries.

func (*FileSystem) ReadFile

func (fs *FileSystem) ReadFile(name string) ([]byte, error)

ReadFile reads the file named by name and returns the contents.

func (*FileSystem) Remove

func (fs *FileSystem) Remove(name string) error

Remove removes a file or empty directory from the SFTP server.

func (*FileSystem) Rename

func (fs *FileSystem) Rename(oldpath, newpath string) error

Rename renames a file on the SFTP server.

func (*FileSystem) Stat

func (fs *FileSystem) Stat(name string) (os.FileInfo, error)

Stat returns file info for a file on the SFTP server.

func (*FileSystem) Sub

func (fs *FileSystem) Sub(dir string) (iofs.FS, error)

Sub returns an fs.FS corresponding to the subtree rooted at dir.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server provides an SFTP server backed by any absfs.FileSystem. It handles SSH connections and SFTP protocol negotiation.

func NewServer

func NewServer(fs absfs.FileSystem, config *ServerConfig) *Server

NewServer creates a new SFTP server for the given filesystem.

Example usage:

fs, _ := memfs.NewFS()

// Load or generate host key
privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
signer, _ := ssh.NewSignerFromKey(privateKey)

server := sftpfs.NewServer(fs, &sftpfs.ServerConfig{
    HostKeys: []ssh.Signer{signer},
    PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
        if c.User() == "admin" && string(pass) == "secret" {
            return nil, nil
        }
        return nil, fmt.Errorf("invalid credentials")
    },
})

listener, _ := net.Listen("tcp", ":2222")
server.Serve(listener)

func (*Server) SSHConfig

func (s *Server) SSHConfig() *ssh.ServerConfig

SSHConfig returns the underlying SSH server configuration. This can be used to add additional configuration options.

func (*Server) Serve

func (s *Server) Serve(listener net.Listener) error

Serve accepts incoming connections on the listener and serves SFTP. This function blocks until the listener is closed.

func (*Server) ServeConn

func (s *Server) ServeConn(conn net.Conn) error

ServeConn handles a single incoming connection. This is useful for custom connection handling or testing.

type ServerConfig

type ServerConfig struct {
	// HostKeys are the private keys for the SSH server.
	// At least one host key is required.
	HostKeys []ssh.Signer

	// PasswordCallback validates password authentication.
	// If nil, password authentication is disabled.
	PasswordCallback func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error)

	// PublicKeyCallback validates public key authentication.
	// If nil, public key authentication is disabled.
	PublicKeyCallback func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error)

	// NoClientAuth allows any client to connect without authentication.
	// WARNING: Only use this for testing or trusted networks.
	NoClientAuth bool

	// MaxAuthTries specifies the maximum number of authentication attempts.
	// If 0, defaults to 6.
	MaxAuthTries int

	// ServerVersion is the SSH server version string.
	// If empty, defaults to "SSH-2.0-sftpfs".
	ServerVersion string
}

ServerConfig holds configuration for the SFTP server.

type ServerHandler

type ServerHandler struct {
	// contains filtered or unexported fields
}

ServerHandler implements all four sftp.Handlers interfaces: FileReader, FileWriter, FileCmder, and FileLister. It adapts an absfs.FileSystem to serve files via SFTP protocol.

func (*ServerHandler) Filecmd

func (h *ServerHandler) Filecmd(r *sftp.Request) error

Filecmd implements sftp.FileCmder. Handles file commands like mkdir, remove, rename, etc. Called for SFTP Methods: Setstat, Rename, Rmdir, Mkdir, Link, Symlink, Remove

func (*ServerHandler) Filelist

func (h *ServerHandler) Filelist(r *sftp.Request) (sftp.ListerAt, error)

Filelist implements sftp.FileLister. Returns a ListerAt for directory listings and file stat operations. Called for SFTP Methods: List, Stat, Readlink

func (*ServerHandler) Fileread

func (h *ServerHandler) Fileread(r *sftp.Request) (io.ReaderAt, error)

Fileread implements sftp.FileReader. Returns an io.ReaderAt for the requested file path. Called for SFTP Method: Get

func (*ServerHandler) Filewrite

func (h *ServerHandler) Filewrite(r *sftp.Request) (io.WriterAt, error)

Filewrite implements sftp.FileWriter. Returns an io.WriterAt for the requested file path. Called for SFTP Methods: Put, Open

Directories

Path Synopsis
internal
mocks
Package mocks provides test doubles for SFTP testing.
Package mocks provides test doubles for SFTP testing.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL