Skip to content

feat: SFTP driver (v1.6.0) #184

Description

@lewta

Summary

Add type: sftp driver for load testing SFTP file transfer infrastructure. Supports upload/download/list operations, multiple user targets, configurable payload sizes, SSH handshake metadata output, algorithm policy enforcement, and optional EICAR upload for malware scanner testing.

Design

Full spec in ROADMAP.md — v1.6.0.

Config

target_defaults:
  sftp:
    port: 22
    operation: upload        # upload | download | list
    timeout_s: 30
    insecure: false
    allowed_ciphers: []
    allowed_kex: []
    allowed_host_key_types: []
    allowed_macs: []

targets:
  - url: sftp://sftp.example.com/uploads/test.bin
    type: sftp
    weight: 10
    sftp:
      username: testuser
      password: secret          # mutually exclusive with private_key
      private_key: /path/to/key # file path OR inline PEM string
      file_size_min_bytes: 1024
      file_size_max_bytes: 10485760

  - url: sftp://sftp.example.com/uploads/eicar.txt
    type: sftp
    weight: 1
    sftp:
      username: testuser
      password: secret
      eicar: true               # upload EICAR test string instead of random bytes

SSH handshake metadata in JSONL output

{
  "ts": "2026-04-01T12:00:00Z",
  "url": "sftp://sftp.example.com/uploads/test.bin",
  "type": "sftp",
  "status": 200,
  "duration_ms": 312,
  "bytes": 1048576,
  "sftp_server_version": "SSH-2.0-OpenSSH_8.9p1 Ubuntu-3",
  "sftp_host_key_type": "ssh-ed25519",
  "sftp_host_key_fp": "SHA256:abc123...",
  "sftp_auth_methods": "publickey,password"
}

sftp_entry_count also included for list operations.

Metadata flows via a new Meta map[string]string field on task.Result, merged inline into JSONL records. CSV output unaffected.

Algorithm policy enforcement

Restrict accepted SSH algorithms — handshake fails (→ 502) if server can't satisfy. Enables policy probes: e.g., alert if host key rotates from Ed25519 to RSA.

Status code mapping

Condition Code
Success 200
Auth failure 401
Permission denied 403
File not found (download) 404
Host key rejected / policy mismatch 502
SFTP protocol error 502
Connection timeout 504

Implementation checklist

Cross-cutting

  • Add Meta map[string]string to task.Result
  • Update JSONL writer to merge Meta keys inline
  • Update record struct in internal/output/writer.go

Config (internal/config/)

  • Add SFTPConfig struct to schema.go
  • Add SFTP SFTPConfig to TargetConfig and TargetDefaultsConfig
  • Add "sftp" to validTypes in both loadTargetsFile and validate
  • Add SFTP-specific validation (operation values, auth fields, size constraints)

Driver (internal/driver/sftp.go)

  • SFTPDriver with ssh.Client cache (sync.Mutex, keyed by host:port+username)
  • upload — generate random bytes or EICAR; measure bytes transferred
  • download — fetch file; set BytesRead to actual file size
  • listReadDir; set sftp_entry_count in Meta
  • private_key — accept file path or inline PEM string
  • Algorithm policy via ssh.Config (Ciphers, KeyExchanges, HostKeyAlgorithms, MACs)
  • Handshake metadata capture (server version, host key type/fingerprint, auth methods)
  • SFTP error → HTTP-like status code mapping
  • Register in internal/engine/engine.go drivers map

Docs (Definition of Done)

  • CHANGELOG.md[Unreleased] entry
  • docs/content/docs/drivers.md — SFTP section
  • docs/content/docs/configuration.mdsftp block in target_defaults table
  • docs/content/docs/dependencies.md — new deps, count, licences
  • docs/content/docs/_index.md — SFTP in Sections table
  • README.md — protocol list, target_defaults block/table, targets example, architecture table, verification table
  • config/example.yaml — SFTP block in target_defaults, commented target example
  • ROADMAP.md — already updated ✓

Dependencies

  • github.com/pkg/sftp — pure Go, no CGO
  • golang.org/x/crypto/ssh — verify if already transitive before adding explicitly

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions