Raja's Exocortex

SFTPGo - Self Hosted File Sharing Service

SFTPGo is a multi-protocol SFTP, FTP, WebDAV server that has a web based admin interface for creating users/folders and access control.

Key Points of this Setup

  1. All HTTP requests are redirected to HTTPS.
  2. HTTP admin page and HTTP Client works as required.
  3. sqlite is used as the backend data provider to store configuration metadata.
  4. SFTP is configured on port 2022, eg. sftp://sftp.rsubr.in:2022, this is to avoid conflicting with the docker host's native SSH service.

Limitations

  1. SFTPGo is installed directly on the host via docker. Treafik ingress controller is not supported, this is for a future build #TODO.
  2. This means that SFTPGo should be the only application installed in the docker host as it consumes TCP 80, 443 and 2022 (SFTP) ports.
  3. Environment variables in docker-compose.yaml are also poorly documented and do not work as expected. This setup uses a bind mount to the sftpgo.json config file.
  4. #TODO SFTPGo can create Let's Encrypt SSL certificates, but both HTTP and TLS verifications are not working. External Let's Encrypt SSL certificate creation is required, through the use of certbot.
  5. #TODO SFTPGo supports customizing the logo and site header. This needs to be setup in sftpgo.json config file and is not yet unimplemented.

Files

docker-compose.yaml

# docker-compose file to start up sftpgo.com
# to start the container run:
#    docker-compose up -d
#
# Note: containrrr/watchtower service will auto update this container

version: "2.4"

name: sftpgo.rsubr.in

services:
  sftpgo:
    image: drakkan/sftpgo:v2-alpine-slim
    container_name: sftpgo.rsubr.in
    restart: always

    mem_limit: 512M

    ports:
      - 80:8080
      - 443:9443
      - 2022:2022

    volumes:
      - ./data:/srv/sftpgo/data
      - ./backups:/srv/sftpgo/backups
      - ./conf:/var/lib/sftpgo
      - ./sftpgo.json:/etc/sftpgo/sftpgo.json:ro
      - /etc/localtime:/etc/localtime:ro

    labels:
      - com.centurylinklabs.watchtower.enable=true

sftpgo.json Config File

{
  "common": {
    "idle_timeout": 15,
    "upload_mode": 0,
    "actions": {
      "execute_on": [],
      "execute_sync": [],
      "hook": ""
    },
    "setstat_mode": 0,
    "temp_path": "",
    "proxy_protocol": 0,
    "proxy_allowed": [],
    "startup_hook": "",
    "post_connect_hook": "",
    "post_disconnect_hook": "",
    "data_retention_hook": "",
    "max_total_connections": 0,
    "max_per_host_connections": 20,
    "whitelist_file": "",
    "allow_self_connections": 0,
    "defender": {
      "enabled": false,
      "driver": "memory",
      "ban_time": 30,
      "ban_time_increment": 50,
      "threshold": 15,
      "score_invalid": 2,
      "score_valid": 1,
      "score_limit_exceeded": 3,
      "score_no_auth": 2,
      "observation_time": 30,
      "entries_soft_limit": 100,
      "entries_hard_limit": 150,
      "safelist_file": "",
      "blocklist_file": "",
      "safelist": [],
      "blocklist": []
    },
    "rate_limiters": [
      {
        "average": 0,
        "period": 1000,
        "burst": 1,
        "type": 2,
        "protocols": [
          "SSH",
          "FTP",
          "DAV",
          "HTTP"
        ],
        "allow_list": [],
        "generate_defender_events": false,
        "entries_soft_limit": 100,
        "entries_hard_limit": 150
      }
    ]
  },
  "acme": {
    "domains": ["sftp.rsubr.in"],
    "email": "rajasuperman@gmail.com",
    "key_type": "4096",
    "certs_path": "certs",
    "ca_endpoint": "https://acme-v02.api.letsencrypt.org/directory",
    "renew_days": 30,
    "http01_challenge": {
      "port": 80,
      "proxy_header": "",
      "webroot": ""
    },
    "tls_alpn01_challenge": {
      "port": 0
    }
  },
  "sftpd": {
    "bindings": [
      {
        "port": 2022,
        "address": "",
        "apply_proxy_config": true
      }
    ],
    "max_auth_tries": 0,
    "banner": "",
    "host_keys": [],
    "host_certificates": [],
    "host_key_algorithms": [],
    "moduli": [],
    "kex_algorithms": [],
    "ciphers": [],
    "macs": [],
    "trusted_user_ca_keys": [],
    "revoked_user_certs_file": "",
    "login_banner_file": "",
    "enabled_ssh_commands": [
      "md5sum",
      "sha1sum",
      "sha256sum",
      "cd",
      "pwd",
      "scp"
    ],
    "keyboard_interactive_authentication": false,
    "keyboard_interactive_auth_hook": "",
    "password_authentication": true,
    "folder_prefix": ""
  },
  "ftpd": {
    "bindings": [
      {
        "port": 0,
        "address": "",
        "apply_proxy_config": true,
        "tls_mode": 0,
        "certificate_file": "",
        "certificate_key_file": "",
        "min_tls_version": 12,
        "force_passive_ip": "",
        "passive_ip_overrides": [],
        "client_auth_type": 0,
        "tls_cipher_suites": [],
        "passive_connections_security": 0,
        "active_connections_security": 0,
        "debug": false
      }
    ],
    "banner": "",
    "banner_file": "",
    "active_transfers_port_non_20": true,
    "passive_port_range": {
      "start": 50000,
      "end": 50100
    },
    "disable_active_mode": false,
    "enable_site": false,
    "hash_support": 0,
    "combine_support": 0,
    "certificate_file": "",
    "certificate_key_file": "",
    "ca_certificates": [],
    "ca_revocation_lists": []
  },
  "webdavd": {
    "bindings": [
      {
        "port": 0,
        "address": "",
        "enable_https": false,
        "certificate_file": "",
        "certificate_key_file": "",
        "min_tls_version": 12,
        "client_auth_type": 0,
        "tls_cipher_suites": [],
        "prefix": "",
        "proxy_allowed": [],
        "client_ip_proxy_header": "",
        "client_ip_header_depth": 0,
        "disable_www_auth_header": false
      }
    ],
    "certificate_file": "",
    "certificate_key_file": "",
    "ca_certificates": [],
    "ca_revocation_lists": [],
    "cors": {
      "enabled": false,
      "allowed_origins": [],
      "allowed_methods": [],
      "allowed_headers": [],
      "exposed_headers": [],
      "allow_credentials": false,
      "max_age": 0,
      "options_passthrough": false,
      "options_success_status": 0,
      "allow_private_network": false
    },
    "cache": {
      "users": {
        "expiration_time": 0,
        "max_size": 50
      },
      "mime_types": {
        "enabled": true,
        "max_size": 1000
      }
    }
  },
  "data_provider": {
    "driver": "sqlite",
    "name": "sftpgo.db",
    "host": "",
    "port": 0,
    "username": "",
    "password": "",
    "sslmode": 0,
    "disable_sni": false,
    "target_session_attrs": "",
    "root_cert": "",
    "client_cert": "",
    "client_key": "",
    "connection_string": "",
    "sql_tables_prefix": "",
    "track_quota": 2,
    "delayed_quota_update": 0,
    "pool_size": 0,
    "users_base_dir": "/srv/sftpgo/data",
    "actions": {
      "execute_on": [],
      "execute_for": [],
      "hook": ""
    },
    "external_auth_hook": "",
    "external_auth_scope": 0,
    "pre_login_hook": "",
    "post_login_hook": "",
    "post_login_scope": 0,
    "check_password_hook": "",
    "check_password_scope": 0,
    "password_hashing": {
      "bcrypt_options": {
        "cost": 10
      },
      "argon2_options": {
        "memory": 65536,
        "iterations": 1,
        "parallelism": 2
      },
      "algo": "bcrypt"
    },
    "password_validation": {
      "admins": {
        "min_entropy": 0
      },
      "users": {
        "min_entropy": 0
      }
    },
    "password_caching": true,
    "update_mode": 0,
    "create_default_admin": false,
    "naming_rules": 1,
    "is_shared": 0,
    "node": {
      "host": "",
      "port": 0,
      "proto": "http"
    },
    "backups_path": "/srv/sftpgo/backups"
  },
  "httpd": {
    "bindings": [
      {
        "port": 8080,
        "address": "",
        "enable_web_admin": true,
        "enable_web_client": true,
        "enable_rest_api": true,
        "enabled_login_methods": 0,
        "enable_https": false,
        "certificate_file": "",
        "certificate_key_file": "",
        "min_tls_version": 12,
        "client_auth_type": 0,
        "tls_cipher_suites": [],
        "proxy_allowed": [],
        "client_ip_proxy_header": "",
        "client_ip_header_depth": 0,
        "hide_login_url": 0,
        "render_openapi": true,
        "web_client_integrations": [],
        "oidc": {
          "client_id": "",
          "client_secret": "",
          "config_url": "",
          "redirect_base_url": "",
          "scopes": [
            "openid",
            "profile",
            "email"
          ],
          "username_field": "",
          "role_field": "",
          "implicit_roles": false,
          "custom_fields": [],
          "insecure_skip_signature_check": false,
          "debug": false
        },
        "security": {
          "enabled": true,
          "allowed_hosts": [],
          "allowed_hosts_are_regex": false,
          "hosts_proxy_headers": [],
          "https_redirect": true,
          "https_host": "",
          "https_proxy_headers": [],
          "sts_seconds": 0,
          "sts_include_subdomains": false,
          "sts_preload": false,
          "content_type_nosniff": false,
          "content_security_policy": "",
          "permissions_policy": "",
          "cross_origin_opener_policy": "",
          "expect_ct_header": ""
        },
        "branding": {
          "web_admin": {
            "name": "",
            "short_name": "",
            "favicon_path": "",
            "logo_path": "",
            "login_image_path": "",
            "disclaimer_name": "",
            "disclaimer_path": "",
            "default_css": "",
            "extra_css": []
          },
          "web_client": {
            "name": "",
            "short_name": "",
            "favicon_path": "",
            "logo_path": "",
            "login_image_path": "",
            "disclaimer_name": "",
            "disclaimer_path": "",
            "default_css": "",
            "extra_css": []
          }
        }
      },
      {
        "port": 9443,
        "address": "",
        "enable_web_admin": true,
        "enable_web_client": true,
        "enable_rest_api": true,
        "enabled_login_methods": 0,
        "enable_https": true,
        "certificate_file": "/var/lib/sftpgo/certs/sftp.rsubr.in.crt",
        "certificate_key_file": "/var/lib/sftpgo/certs/sftp.rsubr.in.key",
        "min_tls_version": 12,
        "client_auth_type": 0,
        "tls_cipher_suites": [],
        "proxy_allowed": [],
        "client_ip_proxy_header": "",
        "client_ip_header_depth": 0,
        "hide_login_url": 0,
        "render_openapi": true,
        "web_client_integrations": [],
        "oidc": {
          "client_id": "",
          "client_secret": "",
          "config_url": "",
          "redirect_base_url": "",
          "scopes": [
            "openid",
            "profile",
            "email"
          ],
          "username_field": "",
          "role_field": "",
          "implicit_roles": false,
          "custom_fields": [],
          "insecure_skip_signature_check": false,
          "debug": false
        },
        "security": {
          "enabled": false,
          "allowed_hosts": [],
          "allowed_hosts_are_regex": false,
          "hosts_proxy_headers": [],
          "https_redirect": false,
          "https_host": "",
          "https_proxy_headers": [],
          "sts_seconds": 0,
          "sts_include_subdomains": false,
          "sts_preload": false,
          "content_type_nosniff": false,
          "content_security_policy": "",
          "permissions_policy": "",
          "cross_origin_opener_policy": "",
          "expect_ct_header": ""
        },
        "branding": {
          "web_admin": {
            "name": "",
            "short_name": "",
            "favicon_path": "",
            "logo_path": "",
            "login_image_path": "",
            "disclaimer_name": "",
            "disclaimer_path": "",
            "default_css": "",
            "extra_css": []
          },
          "web_client": {
            "name": "",
            "short_name": "",
            "favicon_path": "",
            "logo_path": "",
            "login_image_path": "",
            "disclaimer_name": "",
            "disclaimer_path": "",
            "default_css": "",
            "extra_css": []
          }
        }
      }
    ],
    "templates_path": "templates",
    "static_files_path": "static",
    "openapi_path": "openapi",
    "web_root": "",
    "certificate_file": "",
    "certificate_key_file": "",
    "ca_certificates": [],
    "ca_revocation_lists": [],
    "signing_passphrase": "",
    "token_validation": 0,
    "max_upload_file_size": 1048576000,
    "cors": {
      "enabled": false,
      "allowed_origins": [],
      "allowed_methods": [],
      "allowed_headers": [],
      "exposed_headers": [],
      "allow_credentials": false,
      "max_age": 0,
      "options_passthrough": false,
      "options_success_status": 0,
      "allow_private_network": false
    },
    "setup": {
      "installation_code": "",
      "installation_code_hint": "Installation code"
    },
    "hide_support_link": false
  },
  "telemetry": {
    "bind_port": 0,
    "bind_address": "127.0.0.1",
    "enable_profiler": false,
    "auth_user_file": "",
    "certificate_file": "",
    "certificate_key_file": "",
    "min_tls_version": 12,
    "tls_cipher_suites": []
  },
  "http": {
    "timeout": 20,
    "retry_wait_min": 2,
    "retry_wait_max": 30,
    "retry_max": 3,
    "ca_certificates": [],
    "certificates": [],
    "skip_tls_verify": false,
    "headers": []
  },
  "command": {
    "timeout": 30,
    "env": [],
    "commands": []
  },
  "kms": {
    "secrets": {
      "url": "",
      "master_key": "",
      "master_key_path": ""
    }
  },
  "mfa": {
    "totp": [
      {
        "name": "Default",
        "issuer": "SFTPGo",
        "algo": "sha1"
      }
    ]
  },
  "smtp": {
    "host": "",
    "port": 25,
    "from": "",
    "user": "",
    "password": "",
    "auth_type": 0,
    "encryption": 0,
    "domain": "",
    "templates_path": "templates"
  },
  "plugins": []
}

Let's Encrypt Validation Errors

root@moocher:/srv/sftp.rsubr.in# docker exec -ti sftpgo.rsubr.in sh

/var/lib/sftpgo # sftpgo acme  run
2023-04-08T13:14:23.815 INF configured domains: [sftp.rsubr.in]
2023-04-08T13:14:23.815 DBG loading private key from file "certs/acme-v02.api.letsencrypt.org/rajasuperman@gmail.com.key", stat error: <nil>
2023-04-08T13:14:24.638 DBG configuring HTTP-01 challenge, port 80
2023-04-08T13:14:24.638 INF requesting certificates for domains [sftp.rsubr.in]
2023-04-08T13:14:24.638 INF [INFO] [sftp.rsubr.in] acme: Obtaining bundled SAN certificate
2023-04-08T13:14:25.590 INF [INFO] [sftp.rsubr.in] AuthURL: https://acme-v02.api.letsencrypt.org/acme/authz-v3/217673093437
2023-04-08T13:14:25.590 INF [INFO] [sftp.rsubr.in] acme: Could not find solver for: tls-alpn-01
2023-04-08T13:14:25.590 INF [INFO] [sftp.rsubr.in] acme: use http-01 solver
2023-04-08T13:14:25.590 INF [INFO] [sftp.rsubr.in] acme: Trying to solve HTTP-01
2023-04-08T13:14:32.331 INF [INFO] Deactivating auth: https://acme-v02.api.letsencrypt.org/acme/authz-v3/217673093437
2023-04-08T13:14:32.631 ERR unable to obtain certificates for domains [sftp.rsubr.in]: error: one or more domains had a problem:
[sftp.rsubr.in] acme: error: 403 :: urn:ietf:params:acme:error:unauthorized :: 141.148.211.51: Invalid response from https://sftp.rsubr.in/.well-known/acme-challenge/-K72P_3E3DVWmAP3s7UNVMhMHk5ZScP-ea4cqpdN324: 404

2023-04-08T13:14:32.631 ERR Cannot get certificates: unable to obtain certificates: error: one or more domains had a problem:
[sftp.rsubr.in] acme: error: 403 :: urn:ietf:params:acme:error:unauthorized :: 141.148.211.51: Invalid response from https://sftp.rsubr.in/.well-known/acme-challenge/-K72P_3E3DVWmAP3s7UNVMhMHk5ZScP-ea4cqpdN324: 404