Commit 345b0539 authored by segfault's avatar segfault

Fix SFTP configuration for use with containers

parent 8bf501e1
......@@ -2,8 +2,7 @@ import sh
from pathlib import Path
import random
import string
import logging
import time
from logging import getLogger
from onionkit import _
from onionkit.service import OnionService
......@@ -13,26 +12,47 @@ from onionkit.options.persistence import Persistence
from onionkit.options.autostart import Autostart
from onionkit.options.allow_localhost import AllowLocalhost
from onionkit.options.allow_lan import AllowLAN
from onionkit.data_file import DataFile
from onionkit.data_file import DataFile, IniLikeConfigFile
from onionkit.mountpoint import MountPoint
logger = getLogger(__name__)
KEY_NAME = "ssh_host_ed25519_key"
class DataDir(MountPoint):
source = Path("data")
target = Path("/var/lib/sftp")
is_dir = True
data_dir = DataDir()
class SSHDConfigFile(DataFile):
source = Path(data_dir.source, "sshd_config")
config_file = SSHDConfigFile()
class SystemdUnit(IniLikeConfigFile, MountPoint):
source = Path("onionkit-sftp.service")
target = Path("/etc/systemd/system/onionkit-sftp.service")
mode = 0o644
default_content = '''[Unit]
Description=OpenBSD Secure Shell server for SFTP in onionkit auditd.service
ExecStart=/usr/sbin/sshd -D -f /var/lib/sftp/sshd_config
ExecReload=/bin/kill -HUP $MAINPID
systemd_unit_file = SystemdUnit()
class ServerPassword(OnionServiceOption):
# We don't use a super long password here, because the user has to type it. This should still
......@@ -55,7 +75,7 @@ class ServerPassword(OnionServiceOption):
def apply(self):
sh.chpasswd(sh.echo("%s:%s" % (self.service.user, self.Value)))
self.service.container.execute_command("echo {}:{} | chpasswd".format(self.service.user, self.Value))
def on_value_changed(self):
......@@ -87,7 +107,8 @@ class SFTPServer(OnionService):
data_files = [
user = "sftp"
......@@ -96,8 +117,6 @@ class SFTPServer(OnionService):
self.chroot_dir = Path(, "chroot")
self.files_dir = Path(self.chroot_dir, "files")
self.secret_key_file = Path(data_dir.source, "ssh_host_ed25519_key")
self.public_key_file = self.secret_key_file.with_suffix(".pub")
config_file.default_content = '''# onionkit sshd for SFTP configuration
......@@ -118,13 +137,12 @@ PermitRootLogin no
Subsystem sftp internal-sftp
ForceCommand internal-sftp
ChrootDirectory {}
'''.format(self.user, self.secret_key_file, self.port, self.chroot_dir)
'''.format(self.user, Path(, KEY_NAME), self.port, self.chroot_dir)
def public_key(self) -> str:
with open(self.public_key_file) as f:
s =
return " ".join(s.split()[:-1]).strip()
public_key_file = Path(data_dir.source, KEY_NAME).with_suffix(".pub")
return public_key_file.read_text().strip()
def ConnectionInfo(self) -> str:
......@@ -133,50 +151,32 @@ ChrootDirectory {}
s = self.connection_info_header
s += _("Application: %s\n") % self.ClientApplicationForDisplay
s += _("Address: %s:%s\n") % (self.Address, self.virtual_port)
s += _("Address: sftp://%[email protected]%s:%s\n") % (self.user, self.Address, self.virtual_port)
s += _("Password: %s\n") % self.options_dict["ServerPassword"].Value
s += _("SSH Public Key: %s") % self.public_key
return s
def configure(self):
# Create user
sh.adduser("--system", "--group", "--home",, "--no-create-home", "--shell",
"/bin/false", "--disabled-login", self.user)
self.container.execute_command("adduser --system --group --home {home} --no-create-home --shell /bin/false "
"--disabled-login {user}".format(, user=self.user))
# Create chroot directory
logging.debug("Creating directory %s", self.chroot_dir)
sh.install("-o", "root", "-g", self.user, "-m", "750", "-d", self.chroot_dir)
logger.debug("Creating directory %s", self.chroot_dir)
self.container.execute_command("install -o root -g {} -m 750 -d {}".format(self.user, self.chroot_dir))
# Create files directory
logging.debug("Creating directory %s", self.files_dir)
sh.install("-o", self.user, "-g", "nogroup", "-m", "700", "-d", self.files_dir)
logger.debug("Creating directory %s", self.files_dir)
self.container.execute_command("install -o {} -g nogroup -m 700 -d {}".format(self.user, self.files_dir))
# Generate new host key
logging.debug("Generating new host key %s", self.secret_key_file)
if self.secret_key_file.exists():
raise FileExistsError("Secret key file %s already exists. Aborting" % self.secret_key_file)
sh.ssh_keygen(sh.echo("n"), "-N", "", "-t", "ed25519", "-f", str(self.secret_key_file))
# Generate new secret key
secret_key_file = Path(data_dir.source, KEY_NAME)
logger.debug("Generating new host key %s", secret_key_file)
if secret_key_file.exists():
raise FileExistsError("Secret key file %s already exists. Aborting" % secret_key_file)
sh.ssh_keygen(sh.echo("n"), "-N", "", "-t", "ed25519", "-C", "", "-f", str(secret_key_file))
def Uninstall(self):
def delete_user(user):
except sh.ErrorReturnCode_6: # User does not exist
logging.error("Could not delete user %r", user, exc_info=True)
except sh.ErrorReturnCode_8: # User is used by a process
logging.warning("User %r is still used by processes. "
"Killing all processes running as %r.", user, user)
sh.killall("-v", "-u", user)
sh.killall("-9", "-v", "-u", user, _ok_code=[0, 1])
service_class = SFTPServer
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment