Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enabled symbolic link following in mounts #495

Merged
merged 1 commit into from
Dec 29, 2021

Conversation

jwehrlich
Copy link

This update will allow for symbolic links to be followed in mounts in sshfs.

@jwehrlich
Copy link
Author

jwehrlich commented Dec 26, 2021

Resolves #498

jandubois
jandubois previously approved these changes Dec 26, 2021
Copy link
Member

@jandubois jandubois left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, this is great! Now I can remove all my extra mounts for symlink targets from override.yaml again. 😄

Leaving the merging to @AkihiroSuda in case there are any security concerns, but I can't think of any myself.

@AkihiroSuda
Copy link
Member

Leaving the merging to @AkihiroSuda in case there are any security concerns, but I can't think of any myself.

Can we have a test to ensure that the symlink is resolved inside the guest VFS, not in the host VFS?

lima/hack/test-example.sh

Lines 149 to 163 in 03b4ac1

if [[ -n ${CHECKS["mount-home"]} ]]; then
hometmp="$HOME/lima-test-tmp"
INFO "Testing home access (\"$hometmp\")"
rm -rf "$hometmp"
mkdir -p "$hometmp"
defer "rm -rf \"$hometmp\""
echo "random-content-${RANDOM}" >"$hometmp/random"
expected="$(cat "$hometmp/random")"
got="$(limactl shell "$NAME" cat "$hometmp/random")"
INFO "$hometmp/random: expected=${expected}, got=${got}"
if [ "$got" != "$expected" ]; then
ERROR "Home directory is not shared?"
exit 1
fi
fi

@AkihiroSuda AkihiroSuda linked an issue Dec 27, 2021 that may be closed by this pull request
@jandubois
Copy link
Member

jandubois commented Dec 27, 2021

Can we have a test to ensure that the symlink is resolved inside the guest VFS, not in the host VFS?

I guess we can have a test, but it really would be testing sshfs functionality, and not lima.

But I just realized another potential issue: Since this option "hides" symlinks from the guest, it means certain commands (e.g ls, find, tar) will now recurse into parts of the filesystem they would previously exclude:

$ ls -lR loop
total 0
-rw-r--r--  1 jan  staff  0 27 Dec 01:29 file
lrwxr-xr-x  1 jan  staff  1 27 Dec 01:30 loop -> .

$ lima ls -lR loop
loop:
total 4
-rw-r--r-- 1 jan dialout   0 Dec 27 09:29 file
drwxr-xr-x 1 jan dialout 128 Dec 27 09:30 loop

loop/loop:
total 4
-rw-r--r-- 1 jan dialout   0 Dec 27 09:29 file
drwxr-xr-x 1 jan dialout 128 Dec 27 09:30 loop

[...]

loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop:
ls: cannot access 'loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop': Operation not permitted
total 0
-rw-r--r-- 1 jan dialout 0 Dec 27 09:29 file
?????????? ? ?   ?       ?            ? loop

$ find loop -name file
loop/file

$ lima find loop -name file
loop/file
loop/loop/file
loop/loop/loop/file
loop/loop/loop/loop/file
loop/loop/loop/loop/loop/file
loop/loop/loop/loop/loop/loop/file
[...]
find: ‘loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop/loop’: Operation not permitted

I still think this is fine, but maybe needs to be documented or configurable.

@AkihiroSuda
Copy link
Member

I still think this is fine, but maybe needs to be documented or configurable.

Doesn't seem fine to me.
Probably we should have a new YAML field like .[]mounts.sshfs.followSymlinks.

Copy link
Member

@AkihiroSuda AkihiroSuda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, but #495 (comment)

@jandubois
Copy link
Member

Probably we should have a new YAML field like .[]mounts.sshfs.followSymlinks.

Do we really need the extra sshfs level in there? I would make it another property at the same level as writable.

@AkihiroSuda
Copy link
Member

Probably we should have a new YAML field like .[]mounts.sshfs.followSymlinks.

Do we really need the extra sshfs level in there? I would make it another property at the same level as writable.

SSHFS will be abolished as soon as the 9p patch gets merged.
https://lists.gnu.org/archive/html/qemu-devel/2021-11/msg04325.html

So I'd prefer to have the followSymlinks property under sshfs.
Not sure how it will look like with 9p.

@jandubois
Copy link
Member

Not sure how it will look like with 9p.

Well, either we can support it with 9p, or if we can't, we "undocument" the field and throw an error during validation if it is still set to true.

I somewhat dislike leaking implementation details to the names of user settings. Furthermore I dislike adding additional struct levels with just a single field in them. But if you feel strongly about this, then that's ok too...

@jwehrlich
Copy link
Author

@jandubois / @AkihiroSuda: I've made the changes to allow for this to be configurable via a new SSHFS->follow_symlinks parameter in the mount section of the yaml file. If you would like an automated test for this, let me know which test(s) files you think would be most applicable to update.

For example:
https://github.com/lima-vm/lima/pull/495/files#diff-184f50817a75384709c275a226e0bd6f24e229edc9a82644a75e11637453f196

@jwehrlich
Copy link
Author

jwehrlich commented Dec 27, 2021

Can we have a test to ensure that the symlink is resolved inside the guest VFS, not in the host VFS?

@AkihiroSuda, perhaps I've mistaken the comment. But follow_symlinks will resolve symlinks on the host's file system. This will allow the guest OS to be able to traverse these symlinked directories. That said, from my understanding. User permissions on the host side will still be honored. Which is exactly what I think we want...

Reference:
https://manpages.debian.org/experimental/sshfs/sshfs.1.en.html#o~9
• https://support.nesi.org.nz/hc/en-gb/articles/360000621135-Can-I-use-SSHFS-to-mount-the-cluster-filesystem-on-my-local-machine-

pkg/limayaml/limayaml.go Outdated Show resolved Hide resolved
pkg/limayaml/limayaml.go Outdated Show resolved Hide resolved
@@ -42,8 +42,15 @@ mounts:
# CAUTION: `writable` SHOULD be false for the home directory.
# Setting `writable` to true is possible, but untested and dangerous.
writable: false
# 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 in the symlnked folder. This defaults to false if not supplied.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a note that explains that followSymlinks does not map the symlink end-points into the guest, but makes them look like regular files/directories. With the option enabled, there are no symlinks in the mounted tree, and symlinks in the tree cannot be created from the guest.

@jandubois
Copy link
Member

It turns out that with followSymlinks enabled, you cannot create symlinks from inside the guest:

jan@lima-default:/Users/jan$ ln -s .lima lima
ln: failed to create symbolic link 'lima': Input/output error

This is a strong argument against making this enabled by default, and makes me question if this change is desirable after all. It is only helpful for symlinks that point outside the mapped tree, so maybe adding the symlink targets explicitly to the mounts is a better option (which was one of the reasons I wanted the default.yaml and override.yaml mechanism, to have those mounted in all configs).

@jwehrlichskytap
Copy link

jwehrlichskytap commented Dec 27, 2021

This is a strong argument against making this enabled by default, and makes me question if this change is desirable after all.

I can make that change to the default.yaml to ensure that in the examples it's default off. That said, I think this feature is very useful. At my company, we have people wanting files to be specified from any number of locations. To automate this, we have a specific source folder that they can just add a symbolic link so that nothing needs to be edited with lima. This also meeds nothing to restart or to reload, it just works.

TLDR;
We really want to be able to specify a symlink that is not a child of the mounted directory. This is exactly the use case for the flag

@jwehrlichskytap
Copy link

@jandubois, regarding creating symbolic links in the guest. One can actually do this, but I think it would probably make more sense to actually do this on the host side of things as it would be more sticky. that said, I did this with the default config with no changes.

Guest VM - Readonly: Rightfully fails because mounted as read-only by default

jwehrlich@lima-default:/Users/jwehrlich$ ln -s /Users/jwehrlich/.lima/ my-lima
ln: failed to create symbolic link 'my-lima': Read-only file system

Guest VM - Readonly: Creating a symlink outside a mounted location works

jwehrlich@lima-default:/Users/jwehrlich$ cd /Users/
jwehrlich@lima-default:/Users/jwehrlich$ sudo ln -s /Users/jwehrlich/.lima/ my-lima
jwehrlich@lima-default:/Users$ ls
jwehrlich  my-lima
jwehrlich@lima-default:/Users$ ls my-lima
_config  archlinux  default

Gest VM - Writable: Creating symlink works since mount is read writable

jwehrlich@lima-default:/Users/jwehrlich$ ln -s /Users/jwehrlich/.lima /Users/jwehrlich/my-lima
jwehrlich@lima-default:/Users/jwehrlich$ ls my-lima
_config  archlinux  default

Host - With the execution of the last "Guest VM" will show that the symbolic link exists on the host machine

~/src/lima$ ls /Users/jwehrlich/my-lima
_config   archlinux default   flexd

@jandubois
Copy link
Member

jandubois commented Dec 27, 2021

Gest VM - Writable: Creating symlink works since mount is read writable

jwehrlich@lima-default:/Users/jwehrlich$ ln -s /Users/jwehrlich/.lima /Users/jwehrlich/my-lima

Interesting, it does actually work. I wonder why I get an error though:

$ lima
jan@lima-default:/Users/jan$ ln -s .lima lima; echo $?
ln: failed to create symbolic link 'lima/.lima': Operation not permitted
1
jan@lima-default:/Users/jan$ ls -ld lima
drwx------ 1 jan dialout 288 Dec 27 22:15 lima
jan@lima-default:/Users/jan$ rm lima
rm: cannot remove 'lima': Is a directory

And of course, even though you can create the symlink, you cannot delete it, because inside the guest it is a directory, not a symlink.

This kind of violates the lima premise that the environment on the guest and the host behave as similar as possible (so it looks like you run nerdctl on the host, when it really runs inside the guest).

I'm not opposed to keeping the feature, I'm just saying it should not be the default in the builtin template anymore. And the caveats need to be clearly documented.

Also remember that @AkihiroSuda said that this feature may not be supportable when we switch from sshfs to 9p. Maybe using 9p will be an option instead of a replacement, but it is too early to tell.

@@ -45,6 +45,8 @@ mounts:
# 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 in the symlnked folder. This defaults to false if not supplied.
# As a result, symlinked folders on the Host system will look and feel like
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is both files and directories, not just directories. Please also mention that symlinks to directories cannot be deleted inside the guest.

@jandubois
Copy link
Member

Please squash commits, to eliminate the refactoring from the history!

@@ -299,6 +299,7 @@ func FillDefault(y, d, o *LimaYAML, filePath string) {
for _, mount := range append(append(d.Mounts, y.Mounts...), o.Mounts...) {
if i, ok := location[mount.Location]; ok {
mounts[i].Writable = mount.Writable
mounts[i].SSHFS = mount.SSHFS
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unfortunately not enough. Both mount.Writable and mount.SSHFS.ForwardSymlinks need to become pointers to booleans, so they can be independently defaulted/overridden. This should also be added to the defaults_test.go to make sure it works.

Not making mount.Writable a pointer to start with was a mistake already, but it becomes more obvious now that we have multiple properties on a mount.

@jwehrlich
Copy link
Author

jwehrlich commented Dec 28, 2021

Please squash commits, to eliminate the refactoring from the history!

Out of curiosity, why don't you just let Github handle this for you? Instead of Merge pull request you can click on the dropdown on the right and select Squash and merge

@@ -40,16 +40,21 @@ func (a *HostAgent) setupMount(ctx context.Context, m limayaml.Mount) (*mount, e
if err := os.MkdirAll(expanded, 0755); err != nil {
return nil, err
}
// NOTE: allow_other requires "user_allow_other" in /etc/fuse.conf
ssh_options := "allow_other"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • s/ssh/sshfs/
  • We use camelCase, not snake_case

@jandubois
Copy link
Member

Out of curiosity, why don't you just let Github handle this for you? Instead of Merge pull request you can click on the dropdown on the right and select Squash and merge

Because it feels kind of wrong to attach a DCO sign-off to a merge-commit that has not been created directly by the author. Maybe it is fine; idk what kind of policy the big foundations (Linux Foundations, CNCF) have. Letting the author squash is keeping it on the safe side.

The other reason though is that not all commits should be squashed for every PR. We want to squash false starts and refactoring, but not commits that have been intentionally created to address different aspects of the PR.

Copy link
Member

@jandubois jandubois left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The merging of followSymlinks does not work correctly (and has no tests).

I can make the updates for Mount#Writable, but I figured that probably warrants its own PR.

Yes, logically it should be a separate PR, but since it is closely related to how followSymlinks is handled, I wouldn't object to including it here. Your choice!

if i, ok := location[mount.Location]; ok {
mounts[i].SSHFS.FollowSymlinks = mount.SSHFS.FollowSymlinks
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You only overwrite if the pointer is not nil:

Suggested change
mounts[i].SSHFS.FollowSymlinks = mount.SSHFS.FollowSymlinks
if mount.SSHFS.FollowSymlinks != nil {
mounts[i].SSHFS.FollowSymlinks = mount.SSHFS.FollowSymlinks
}

@@ -297,7 +297,11 @@ func FillDefault(y, d, o *LimaYAML, filePath string) {
mounts := make([]Mount, 0, len(d.Mounts)+len(y.Mounts)+len(o.Mounts))
location := make(map[string]int)
for _, mount := range append(append(d.Mounts, y.Mounts...), o.Mounts...) {
if mount.SSHFS.FollowSymlinks == nil {
mount.SSHFS.FollowSymlinks = pointer.Bool(false)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting the default cannot happen here; you need to merge from all the sources first, and then have another loop over the final result, filling in the missing values with their defaults. Look at how the networks are being merged, and copy that mechanism.

Adding test cases for this to defaults_test.go would help to show that the current logic does not work as expected.

@jwehrlich jwehrlich force-pushed the link-following branch 2 times, most recently from bb9bcde to b663c48 Compare December 28, 2021 17:51
@jwehrlich
Copy link
Author

@jandubois / @AkihiroSuda: I think all your concerns should now be addressed in the latest iteration.

@jwehrlich
Copy link
Author

@jandubois, I've got the refactored Writable ready to be turned into a PR as soon as this one goes out. :)

pkg/hostagent/mount.go Outdated Show resolved Hide resolved
@jwehrlich jwehrlich force-pushed the link-following branch 2 times, most recently from 0dac92a to b709cb9 Compare December 29, 2021 00:15
Signed-off-by: Jason W. Ehrlich <jwehrlich@outlook.com>
Copy link
Author

@jwehrlich jwehrlich left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed sshOptions to sshfsOptions

pkg/hostagent/mount.go Outdated Show resolved Hide resolved
Copy link
Member

@jandubois jandubois left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, LGTM now. Waiting for @AkihiroSuda review before merging.

Copy link
Member

@AkihiroSuda AkihiroSuda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@AkihiroSuda AkihiroSuda merged commit 595ed4f into lima-vm:master Dec 29, 2021
@AkihiroSuda AkihiroSuda added this to the v0.8.1 milestone Dec 29, 2021
@AkihiroSuda AkihiroSuda added the enhancement New feature or request label Dec 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component/sshfs enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Symbolic links do not resolve in mounts
4 participants