Repository: lima-vm/lima
Pull request #804 (2022-04-18 07:18:20)

sshfs: support setting `sftpDriver: "openssh-sftp-server"`
See `docs/mount.md`



Patch:
diff --git a/README.md b/README.md
index d2f4fa9e..e0b91064 100644
--- a/README.md
+++ b/README.md
@@ -250,7 +250,7 @@ The current default spec:
 ## How it works
 
 - Hypervisor: QEMU with HVF accelerator
-- Filesystem sharing: [reverse sshfs](https://github.com/lima-vm/sshocker/blob/v0.2.0/pkg/reversesshfs/reversesshfs.go) (likely to be replaced with 9p or Samba in future)
+- Filesystem sharing: [Reverse SSHFS (default),  or virtio-9p-pci aka virtfs](./docs/mount.md)
 - Port forwarding: `ssh -L`, automated by watching `/proc/net/tcp` and `iptables` events in the guest
 
 ## Developer guide
@@ -266,6 +266,7 @@ The current default spec:
 - Performance optimization
 - More guest distros
 - Windows hosts
+- virtio-fs to replace virtio-9p-pci aka virtfs (work has to be done on QEMU repo)
 - [vsock](https://github.com/apple/darwin-xnu/blob/xnu-7195.81.3/bsd/man/man4/vsock.4) to replace SSH (work has to be done on QEMU repo)
 
 ## FAQs & Troubleshooting
diff --git a/docs/mount.md b/docs/mount.md
new file mode 100644
index 00000000..228a339b
--- /dev/null
+++ b/docs/mount.md
@@ -0,0 +1,77 @@
+# Filesystem mounts
+
+Lima supports several methods for mounting the host filesystem into the guest.
+
+The default mount type is shown in the following table:
+
+| Lima Version     | Default                             |
+| ---------------- | ----------------------------------- |
+| < 0.10           | reverse-sshfs + Builtin SFTP server |
+| >= 0.10          | reverse-sshfs + OpenSSH SFTP server |
+| >= 1.0 (Planned) | 9p                                  |
+
+## Mount types
+
+### reverse-sshfs
+The "reverse-sshfs" mount type exposes the host filesystem by running an SFTP server on the host.
+While the host works as an SFTP server, the host does not open any TCP port,
+as the host initiates an SSH connection into the guest and let the guest connect to the SFTP server via the stdin.
+
+An example configuration:
+```yaml
+mountType: "reverse-sshfs"
+mounts:
+- location: "~"
+  sshfs:
+    # Enabling the SSHFS cache will increase performance of the mounted filesystem, at
+    # the cost of potentially not reflecting changes made on the host in a timely manner.
+    # Warning: It looks like PHP filesystem access does not work correctly when
+    # the cache is disabled.
+    # 🟢 Builtin default: true
+    cache: null
+    # SSHFS has an optional flag called 'follow_symlinks'. This allows mounts
+    # to be properly resolved in the guest os and allow for access to the
+    # contents of the symlink. As a result, symlinked files & folders on the Host
+    # system will look and feel like regular files directories in the Guest OS.
+    # 🟢 Builtin default: false
+    followSymlinks: null
+    # SFTP driver, "builtin" or "openssh-sftp-server". "openssh-sftp-server" is recommended.
+    # 🟢 Builtin default: "openssh-sftp-server" if OpenSSH SFTP Server binary is found, otherwise "builtin"
+    sftpDriver: null
+```
+
+The default value of `sftpDriver` has been set to "openssh-sftp-server" since Lima v0.10, when an OpenSSH SFTP Server binary
+such as `/usr/libexec/sftp-server` is detected on the host.
+Lima prior to v0.10 had used "builtin" as the SFTP driver.
+
+#### Caveats
+- A mount is disabled when the SSH connection was shut down.
+- A compromised `sshfs` process in the guest may have an access to unexposed host directories.
+
+### 9p
+The "9p" mount type is implemented by using QEMU's virtio-9p-pci devices.
+virtio-9p-pci is also known as "virtfs", but note that this is unrelated to [virtio-fs](https://virtio-fs.gitlab.io/).
+
+An example configuration:
+```yaml
+mountType: "9p"
+mounts:
+- location: "~"
+  9p:
+    # Supported security models are "passthrough", "mapped-xattr", "mapped-file" and "none".
+    # 🟢 Builtin default: "mapped-xattr"
+    securityModel: null
+    # Select 9P protocol version. Valid options are: "9p2000" (legacy), "9p2000.u", "9p2000.L".
+    # 🟢 Builtin default: "9p2000.L"
+    protocolVersion: null
+    # The number of bytes to use for 9p packet payload, where 4KiB is the absolute minimum.
+    # 🟢 Builtin default: "128KiB"
+    msize: null
+    # Specifies a caching policy. Valid options are: "none", "loose", "fscache" and "mmap".
+    # Try choosing "mmap" or "none" if you see a stability issue with the default "fscache".
+    # See https://www.kernel.org/doc/Documentation/filesystems/9p.txt
+    # 🟢 Builtin default: "fscache" for non-writable mounts, "mmap" for writable mounts
+    cache: null
+```
+#### Caveats
+- The "9p" mount type is known to be incompatible with CentOS, Rocky Linux, and AlmaLinux as their kernel do not support `CONFIG_NET_9P_VIRTIO`.
diff --git a/examples/default.yaml b/examples/default.yaml
index 86695c77..3028a9e4 100644
--- a/examples/default.yaml
+++ b/examples/default.yaml
@@ -61,6 +61,9 @@ mounts:
     # system will look and feel like regular files directories in the Guest OS.
     # 🟢 Builtin default: false
     followSymlinks: null
+    # SFTP driver, "builtin" or "openssh-sftp-server". "openssh-sftp-server" is recommended.
+    # 🟢 Builtin default: "openssh-sftp-server" if OpenSSH SFTP Server binary is found, otherwise "builtin"
+    sftpDriver: null
   9p:
     # Supported security models are "passthrough", "mapped-xattr", "mapped-file" and "none".
     # 🟢 Builtin default: "mapped-xattr"
diff --git a/go.mod b/go.mod
index 1f92fdf3..a6c0808b 100644
--- a/go.mod
+++ b/go.mod
@@ -17,7 +17,7 @@ require (
 	github.com/google/go-cmp v0.5.7
 	github.com/gorilla/mux v1.8.0
 	github.com/hashicorp/go-multierror v1.1.1
-	github.com/lima-vm/sshocker v0.2.3
+	github.com/lima-vm/sshocker v0.3.0
 	github.com/mattn/go-isatty v0.0.14
 	github.com/mattn/go-shellwords v1.0.12
 	github.com/miekg/dns v1.1.48
diff --git a/go.sum b/go.sum
index 4252ba51..6033ffca 100644
--- a/go.sum
+++ b/go.sum
@@ -118,8 +118,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/lima-vm/sshocker v0.2.3 h1:z4xTGS51Bo03adLHcetl3WjSK4zbJeFe6GT0ChiZI7I=
-github.com/lima-vm/sshocker v0.2.3/go.mod h1:tdwxS0o2d2vNfe2CN+QN9J8XF3VPX4y9IUu7fpy2ibc=
+github.com/lima-vm/sshocker v0.3.0 h1:4W7AFfwqkhPwBIOtwfnxxggdS3/bs6JkTYT1RfrXwfQ=
+github.com/lima-vm/sshocker v0.3.0/go.mod h1:LtQ68MCRh2MPgAczFNyElrOObNR1lsb31YCeFGgeLyc=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
diff --git a/pkg/hostagent/mount.go b/pkg/hostagent/mount.go
index 6621313c..ee42fe7a 100644
--- a/pkg/hostagent/mount.go
+++ b/pkg/hostagent/mount.go
@@ -51,6 +51,7 @@ func (a *HostAgent) setupMount(ctx context.Context, m limayaml.Mount) (*mount, e
 	}
 	logrus.Infof("Mounting %q", expanded)
 	rsf := &reversesshfs.ReverseSSHFS{
+		Driver:              *m.SSHFS.SFTPDriver,
 		SSHConfig:           a.sshConfig,
 		LocalPath:           expanded,
 		Host:                "127.0.0.1",
diff --git a/pkg/limayaml/defaults.go b/pkg/limayaml/defaults.go
index 0e4867dd..a73fb2c8 100644
--- a/pkg/limayaml/defaults.go
+++ b/pkg/limayaml/defaults.go
@@ -376,6 +376,9 @@ func FillDefault(y, d, o *LimaYAML, filePath string) {
 			if mount.SSHFS.FollowSymlinks != nil {
 				mounts[i].SSHFS.FollowSymlinks = mount.SSHFS.FollowSymlinks
 			}
+			if mount.SSHFS.SFTPDriver != nil {
+				mounts[i].SSHFS.SFTPDriver = mount.SSHFS.SFTPDriver
+			}
 			if mount.NineP.SecurityModel != nil {
 				mounts[i].NineP.SecurityModel = mount.NineP.SecurityModel
 			}
@@ -406,6 +409,9 @@ func FillDefault(y, d, o *LimaYAML, filePath string) {
 		if mount.SSHFS.FollowSymlinks == nil {
 			mount.SSHFS.FollowSymlinks = pointer.Bool(false)
 		}
+		if mount.SSHFS.SFTPDriver == nil {
+			mount.SSHFS.SFTPDriver = pointer.String("")
+		}
 		if mount.NineP.SecurityModel == nil {
 			mounts[i].NineP.SecurityModel = pointer.String(Default9pSecurityModel)
 		}
diff --git a/pkg/limayaml/limayaml.go b/pkg/limayaml/limayaml.go
index 013000d7..c7ba62f0 100644
--- a/pkg/limayaml/limayaml.go
+++ b/pkg/limayaml/limayaml.go
@@ -57,9 +57,17 @@ type Mount struct {
 	NineP    NineP  `yaml:"9p,omitempty" json:"9p,omitempty"`
 }
 
+type SFTPDriver = string
+
+const (
+	SFTPDriverBuiltin           = "builtin"
+	SFTPDriverOpenSSHSFTPServer = "openssh-sftp-server"
+)
+
 type SSHFS struct {
-	Cache          *bool `yaml:"cache,omitempty" json:"cache,omitempty"`
-	FollowSymlinks *bool `yaml:"followSymlinks,omitempty" json:"followSymlinks,omitempty"`
+	Cache          *bool       `yaml:"cache,omitempty" json:"cache,omitempty"`
+	FollowSymlinks *bool       `yaml:"followSymlinks,omitempty" json:"followSymlinks,omitempty"`
+	SFTPDriver     *SFTPDriver `yaml:"sftpDriver,omitempty" json:"sftpDriver,omitempty"`
 }
 
 type NineP struct {


Test patch:
diff --git a/pkg/limayaml/defaults_test.go b/pkg/limayaml/defaults_test.go
index 8d6e0563..a7327a4c 100644
--- a/pkg/limayaml/defaults_test.go
+++ b/pkg/limayaml/defaults_test.go
@@ -146,6 +146,7 @@ func TestFillDefault(t *testing.T) {
 	expect.Mounts[0].Writable = pointer.Bool(false)
 	expect.Mounts[0].SSHFS.Cache = pointer.Bool(true)
 	expect.Mounts[0].SSHFS.FollowSymlinks = pointer.Bool(false)
+	expect.Mounts[0].SSHFS.SFTPDriver = pointer.String("")
 	expect.Mounts[0].NineP.SecurityModel = pointer.String(Default9pSecurityModel)
 	expect.Mounts[0].NineP.ProtocolVersion = pointer.String(Default9pProtocolVersion)
 	expect.Mounts[0].NineP.Msize = pointer.String(Default9pMsize)
@@ -297,6 +298,7 @@ func TestFillDefault(t *testing.T) {
 	expect.Containerd.Archives[0].Arch = *d.Arch
 	expect.Mounts[0].SSHFS.Cache = pointer.Bool(true)
 	expect.Mounts[0].SSHFS.FollowSymlinks = pointer.Bool(false)
+	expect.Mounts[0].SSHFS.SFTPDriver = pointer.String("")
 	expect.Mounts[0].NineP.SecurityModel = pointer.String(Default9pSecurityModel)
 	expect.Mounts[0].NineP.ProtocolVersion = pointer.String(Default9pProtocolVersion)
 	expect.Mounts[0].NineP.Msize = pointer.String(Default9pMsize)