Request for daemon + browser-support for krellian-kiosk

nope, i don’t, but i know it is supposed to be possible to drop privileges to it … i guess you need to do this inside the application initialization or right before you start it since, as you noticed, XWayland needs to start as root first … but once you have a DISPLAY set you should be able to run as user

I think that’s what I started with but snapcraft kept telling me the $SNAP was redundant. Anyway I’ve added it back and will test.

I’ve rolled a patch for that and generated a new build with both fixes but I’m struggling to test it because I keep getting weird errors and timeouts on snap install/snap remove. Even at one point got “snap: command not found”!

All I can think is that microSD card in my Pi is wearing out, so I’ll try again next week with a fresh card + install.

@benfrancis what I think you should try first is to ignore the bin/drop-snap-daemon.sh bit for now and just work on getting listen-streams working to inherit the systemd file descriptors for your desired port. After you have confirmed that is working, then resume trying to get setpriv to work.

Could anyone point towards documentation for the “sockets” section of the snapcraft.yaml section @ijohnson proposed and exactly what it does?

I’ve been trying to get the systemd hand-off of the socket working as suggested, looking at the systemd configurations that snap is auto-generating and reading some background into this feature. The snapcraft.yaml reference mentions a “socket” (as opposed to “sockets”) feature, but doesn’t go into much detail as to how it should be used.

@ijohnson I note that the example project you linked to in your original email and the now un-maintained “systemd” npm library it uses are intended to provide “socket activation” for services, whereby services can be started on demand by systemd and shut down when not in use.

It occurs to me that whilst this might make sense for a simple Node.js server, it’s not really desirable for my Electron-based digital signage application which acts as both a web client (used to render web pages to the screen) and web server (used for remote control).

Whilst I haven’t yet been able to get the socket handoff working, I have noticed that the above configuration results in a snap which is no longer started on system boot, but is only started when a TCP connection is opened on port 80 (e.g. via an HTTP request). This isn’t going to work in my case, because the UI needs to be started on boot, and not terminated when the server hasn’t received any requests for a while.

I explained at the start of this thread why the (relatively simple) kiosk application I’m trying to package as a snap acts as both a web client and web server. That seemingly requires two things which ordinarily require root access:

  1. Starting a web server on port 80
  2. Starting a graphical interface using XWayland

In order to meet current snap store policies it seems my application can not be allowed to run as root, but so far the proposed solutions for making the server component run as a non-root user do not appear to work for the graphical client component of the application which uses XWayland and needs to run continuously from boot.

Have I understood this correctly, and can you offer any further advice on how I can modify my application so that it can be allowed into the snap store? I’m starting to wonder whether Ubuntu Core is a good fit for my use case after all :frowning:

Thanks

Sorry the documentation has a typo, this should be sockets. I’ll make sure this gets fixed and work on creating better examples in the docs…

That is part of the intended feature set, but that’s not why it was recommended for your project, for your project what is desirable about this is that your application doesn’t need to open port 80 by itself, and instead systemd opens that port and provides it via a file descriptor to your service when your service starts. Indeed, many projects (including some parts of snapd itself) rely on this in order to shut themselves down and start back up when a request is issued, but that is not strictly necessary. You can still have your service startup on boot and always be running when you use socket activation. Apologies if this was unclear. However read on…

Yes, it appears that we have stumbled onto a bug here, wherein there is not currently a way to actually start a daemon that is using socket activation except through actually socket activating it. I’m fairly certain this was unintentional and that this is a bug, but I guess what I expected to have worked is to have an install hook like this:

#!/bin/sh
snapctl start --enable "$SNAP_NAME.daemon"

which would manual start the daemon, however it appears looking at the code in snapd, in the case of there being a socket for the service “daemon”, we just plain skip over actually starting the daemon which is unintentional to say the least, and instead just make sure the socket is ready. I will file a bug to this effect and try to get that fixed for you. In the meantime, you could instead have something (which is hacky indeed) like this in your install hook that actually just manually socket activates the application:

#!/bin/bash -ex
snapctl start --enable "$SNAP_DAEMON.daemon"

attempts=0
until curl http://localhost:80/some-non-existent-route; do
    sleep 1
    attempts=$(( attempts + 1 ))
    if [ "$attempts" = 10 ]; then
        exit 1
    fi
done

I say a non-existent route since you should at least then just trigger a 404 and not accidentally do something else that is unintended, and for the purposes of this hook you don’t even really need to make a complete HTTP request, you just need to try and write some data to that port so that systemd then activates the service. The loop is necessary since there is a race enabling the socket unit and trying to actually trigger the socket activation.

I made a full example of this here: https://github.com/anonymouse64/test-go-socket-activation-snap (but in Go not NodeJS since I don’t have much NodeJS experience). While doing so, I noticed that there is a slight error in the provided script to drop permissions, it should be calling exec on the setpriv command, since AFAICT only the PID that systemd launches is allowed to use the FD that systemd provides. So changing your drop-snap-daemon.sh script to this should get you further:

#!/bin/sh -e
exec "$SNAP/usr/bin/setpriv" --clear-groups --reuid snap_daemon --regid snap_daemon -- "$@"

If you wouldn’t mind giving that a try and seeing if you can make it further, I’m still hopeful we can make it work for your application, but do note that as mentioned originally, we still could allow your snap to use browser-sandbox in a daemon application with appropriate vetting if this still proves intractable.

To be clear, is this all done by the xwayland-kiosk-launch component of your script or does your NodeJS application need to do part of this as well? I am not familiar much with XWayland kiosks or electron for that matter…

Also, from above, you will need to ensure that all scripts / intermediary wrappers for your app end with an exec ... to ensure the same PID is used.

It is true, we’re lacking the means for expressing whether one wants to act on the socket, timer, or a corresponding service. There was some previous discussion in: Command line interface to manipulate services but the problem was not fully addressed then.

OTOH, if a service is socket activated, I expect it to be running only once it has been activated. While here we’re trying to use the socket activation as means of obtaining file descriptors bound to specific access-limited ports to avoid running as root.

1 Like

Might it make sense to extend the install-mode feature added by the following PR to cover this case?

While the current behaviour is probably the most common, there are certainly cases where you want socket activation and to be started on boot. For example, if you’ve got a set of co-dependent services then socket activation can be used as an alternative to before/after to order their start up.

1 Like

Yes, I think @jamesh is right in that we should extend install-mode to cover additional use cases, I guess my expectation is that socket activated services by default do not start up on their own and must be socket activated, but the bug I was trying to point out was that we have no way to actually start a socket activated service except for activating it which is awkward as can be seen in my examples above.

Thank you for the ongoing help. The fact that we’re uncovering bugs in the platform at least reassures me that I’m not just struggling with something obvious, but that we’re actually in slightly uncharted territory.

This is all handled by the xwayland-kiosk-launch command, which comes from the xwayland-kiosk-helper, as described in the Electron-based kiosk snap tutorial. I don’t fully understand all the ins and outs of how it works, but it launches “X11 applications on a Wayland kiosk, using Xwayland and a basic window manager to enforce fullscreen kiosk behaviour”.

OK, good news, I now have this working here https://github.com/krellian/kiosk/pull/75

OK, I made this change and I’m getting the same permissions error as before:

Feb 01 18:57:23 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[1917]: /snap/krellian-kiosk/x3/bin/xwayland-kiosk-launch: line 151: /snap/krellian-kiosk/x3/bin/drop-snap-daemon.sh: Permission denied

(Full output here.)

The output of sudo journalctl --no-pager | grep DENIED includes the following errors:

Feb 01 19:01:33 Unknown-b8-27-eb-f2-7b-0b-2 audit[6469]: AVC apparmor="DENIED" operation="open" profile="snap.krellian-kiosk.krellian-kiosk" name="/var/cache/fontconfig/" pid=6469 comm="ls" requested_mask="r" denied_mask="r" fsuid=0 ouid=0
Feb 01 19:01:33 Unknown-b8-27-eb-f2-7b-0b-2 kernel: audit: type=1400 audit(1612206093.283:501): apparmor="DENIED" operation="open" profile="snap.krellian-kiosk.krellian-kiosk" name="/var/cache/fontconfig/" pid=6469 comm="ls" requested_mask="r" denied_mask="r" fsuid=0 ouid=0
Feb 01 19:01:33 Unknown-b8-27-eb-f2-7b-0b-2 audit[6471]: AVC apparmor="DENIED" operation="bind" profile="snap.krellian-kiosk.krellian-kiosk" pid=6471 comm="Xwayland" family="unix" sock_type="stream" protocol=0 requested_mask="bind" denied_mask="bind" addr="@/tmp/.X11-unix/X24"
Feb 01 19:01:33 Unknown-b8-27-eb-f2-7b-0b-2 kernel: audit: type=1400 audit(1612206093.339:502): apparmor="DENIED" operation="bind" profile="snap.krellian-kiosk.krellian-kiosk" pid=6471 comm="Xwayland" family="unix" sock_type="stream" protocol=0 requested_mask="bind" denied_mask="bind" addr="@/tmp/.X11-unix/X24"

The output of ls /snap/krellian-kiosk/current/bin -al | grep drop-snap-daemon.sh is:

-rw-r--r-- 1 root root 105 Feb 1 17:44 drop-snap-daemon.sh

Suggesting the shell script is not in fact executable. How do I fix that? Is it sufficient to add the executable flag to the file in my local source tree and check it into git?

Thank you. Do you have a bug number I can follow so that I know when I can remove the hack?

I’ve not used hooks before. Can you explain why this should be in the install hook? My understanding from the documentation is that the install hook is only executed on first install, whereas presumably this socket activation needs to happen every time the application is started.

Can you explain what you mean by this? What edits do I need to make where exactly? The only shell script I’ve added myself is drop-snap-daemon.sh

Thanks

Given this, I think that your application probably doesn’t need to setup any kind of XWayland things and thus doesn’t need root for any of that. In any case we should find out as we get closer to the solution I think…

:tada: so that means that your listen-streams are working and your application is reading the file descriptors from systemd properly, and we just need to sort out the permission dropping bits.

this still looks suspicious to me, but is not related to dropping privileges AFAICT, so let’s ignore it for now…

Yes, you can run chmod +x drop-snap-daemon.sh and then commit the script to git and rebuild the snap. Let’s tackle this part next and see if this is sufficient to get a bit further.

Sure, here is the bug: Bug #1914121 “no way to actually start a socket activated servic...” : Bugs : snapd

Hmm, that’s a fair point I hadn’t thought about on reboots, I think to handle that case then unfortunately you would want to have the shell script in the install hook instead be a one-shot daemon that is also in your snap, so move the install hook script to somewhere in $SNAP/bin/activate-svc.sh or something (and make sure it is executable :wink:), then add something like this to your snapcraft.yaml in the apps section:

apps:
  activator:
    command: bin/activate-svc.sh
    plugs:
      - network
      - network-bind # may not be necessary but just for good luck

but let’s not try this until we can get the current version you have with the hook working to make sure we can get the user permission dropping bits to work :smiley:

Then you are fine, the other scripts will already have this, and we just needed to add this to the drop-snap-daemon.sh script

@ijohnson Quick update:

I made drop-snap-daemon.sh executable (added to the PR), and I now get past the previous error to a new one:

Feb 02 18:11:15 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[2595]: /snap/krellian-kiosk/x5/bin/drop-snap-daemon.sh: 2: exec: /snap/krellian-kiosk/x5/usr/bin/setpriv: not found

What am I missing? Why is setpriv not included inside the snap? I added util-linux to stage-packages in the snapcraft.yaml as suggested (see here).

Ah, so apparently on 20.04 (i.e. base: core20) the right package is util-linux, but on Ubuntu 18.04, setpriv comes from the verbatim setpriv package, so just change util-linux to setpriv in your stage-packages and then that should get you further

1 Like

OK, that got me further. I think setpriv is now working.

Electron now segfaults on startup with the following trace:

Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]: A JavaScript error occurred in the main process
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]: Uncaught Exception:
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]: Error: Failed to get 'appData' path
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]:     at App.l._setDefaultAppPaths (electron/js2c/browser_init.js:5:1420)
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]:     at Object.<anonymous> (electron/js2c/browser_init.js:205:2351)
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]:     at Object../lib/browser/init.ts (electron/js2c/browser_init.js:205:3580)
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]:     at __webpack_require__ (electron/js2c/browser_init.js:1:128)
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]:     at electron/js2c/browser_init.js:1:1200
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]:     at electron/js2c/browser_init.js:1:1267
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]:     at NativeModule.compile (internal/bootstrap/loaders.js:287:5)
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]:     at NativeModule.compileForPublicLoader (internal/bootstrap/loaders.js:222:8)
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]:     at loadNativeModule (internal/modules/cjs/helpers.js:23:9)
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]:     at Module._load (internal/modules/cjs/loader.js:698:15)
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]: No protocol specified
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]: /snap/krellian-kiosk/x6/bin/xwayland-kiosk-launch: line 151:  8535 Segmentation fault      "$@"

It looks like it’s crashing while trying to execute app.getPath('appData'), which according to the Electron documentation tries to access $XDG_CONFIG_HOME or ~/.config on Linux.

Given this works when starting as root, my best guess is that this is due to differences in the environment variables between the root and snap_daemon users.

Could the solution be as simple as setting an environment variable in my snapcraft.yaml, or is this due to another constraint of snap confinement? If the latter, is there a recommended way to work around this in snaps?

I would assume this will be a problem for anyone trying to run an Electron-based application as the snap_daemon user.

Thanks

That does indeed look like an issue with incorrect permissions, though I admit I’m a bit disappointed that electron just segfaults when presented with something like a permission denied error :-/

Anyways, yes one way to fix this is to set XDG_CONFIG_HOME to somewhere that snap_daemon will be able to own. I would recommend setting this to $SNAP_DATA/snap-daemon-home/.config, and add something like this to the drop-snap-daemon.sh script:

#!/bin/bash -ex
# setup a $SNAP_USER_DATA under $SNAP_DATA for snap-daemon user
mkdir -p $SNAP_DATA/snap-daemon-home/.config
chown -R snap_daemon:snap_daemon $SNAP_DATA/snap-daemon-home/

# set XDG_CONFIG_HOME to use our $SNAP_USER_DATA 
XDG_CONFIG_HOME=$SNAP_DATA/snap-daemon-home/.config 
export XDG_CONFIG_HOME

# maybe this is unnecessary, but just to be safe if electron needs $HOME for anything else
HOME=$SNAP_DATA/snap-daemon-home
export HOME

exec $SNAP/... # the existing command you have
1 Like

Thanks for the quick reply!

Ah, I may have unfairly blamed Electron there, it may be xwayland-kiosk-launch which is segfaulting.

Unfortunately it still seems to segfault after adding those lines to drop-snap-daemon.sh to set the environment variable and permissions, but I can’t tell why. Full output here.

I think the shell script worked the first time around, but generates errors on subsequent executions. Here is what it ends up looking like:

$ ls -al /var/snap/krellian-kiosk/x7/snap-daemon-home/.config/
total 12
drwxr-xr-x 3 snap_daemon snap_daemon 4096 Feb  3 18:55 .
drwxr-xr-x 3 snap_daemon snap_daemon 4096 Feb  3 18:55 ..
drwx------ 3 snap_daemon snap_daemon 4096 Feb  3 18:55 krellian-kiosk

In contrast with the socket activation code, would it make more sense for these first lines of shell script to go in an install hook instead, since it should only need doing once?

Anyway, so far I haven’t been able to figure out why xwayland-kiosk-launch is still crashing when run under the snap_daemon user, but there was a suggestion earlier that XWayland has to run as root, so maybe that’s the problem? :frowning:

@ogra Would you mind expanding a little on how you think this could work? What exactly are you suggesting I could do “inside the application initialization” and what do you mean by that? You can see my ongoing attempt to run my Electron kiosk application in the PR here, which uses setpriv to drop down from root in a shell script called as part of the startup command.

Is it possible the above segfault in xwayland-kiosk-launch is because I’m dropping down from root too early?

Thanks

@benfrancis to be clear, you are dropping user privileges after XWayland is launched, your script that calls setpriv with your electron app is executed after the xwayland-kiosk-launch script runs.

Yes that should be fine, but you would also want this code in a post-refresh hook in order to ensure that users who have your snap currently installed are refreshed properly and continue to work (not sure if you have any such users right now). If you have no such existing userbase to support, then the install hook is perfectly fine.

Your logs don’t seem to imply that xwayland-kiosk-launch is to fault here, in fact I see that on the every execution the drop-snap-daemon.sh script starts running which would only be possible if the xwayland-kiosk-launch script finished successfully

Also reading through the logs on why the drop-snap-daemon.sh script failed in subsequent operations, perhaps you just need to change the script to something like

#!/bin/bash -ex
# setup a $SNAP_USER_DATA under $SNAP_DATA for snap-daemon user
mkdir -p $SNAP_DATA/snap-daemon-home/.config
chown  snap_daemon:snap_daemon $SNAP_DATA/snap-daemon-home
chown  snap_daemon:snap_daemon $SNAP_DATA/snap-daemon-home/.config

# set XDG_CONFIG_HOME to use our $SNAP_USER_DATA 
XDG_CONFIG_HOME=$SNAP_DATA/snap-daemon-home/.config 
export XDG_CONFIG_HOME

# maybe this is unnecessary, but just to be safe if electron needs $HOME for anything else
HOME=$SNAP_DATA/snap-daemon-home
export HOME

exec $SNAP/... # the existing command you have

this just removes the -R, so it doesn’t try to change permissions for subdirectories below there if they are differently privileged.

Quick update:

The script no longer produces errors, thanks.

I managed to get past the “No protocol specified” error and segfault by giving the snap_daemon user access to the X server using xhost, as discussed in IRC.

I now have a new error from Electron:

Feb 04 20:54:27 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk[27595]: Cannot open pixbuf loader module file '/root/snap/krellian-kiosk/common/.cache/gdk-pixbuf-loaders.cache': Permission denied
                                                                   
                                                                   This likely means that your installation is broken.
                                                                   Try running the command
                                                                     gdk-pixbuf-query-loaders > /root/snap/krellian-kiosk/common/.cache/gdk-pixbuf-loaders.cache
                                                                   to make things work again for the time being.

I’m not sure why Electron is trying to access files in the /root directory…

# ls -al /root/snap/krellian-kiosk/common/.cache/
total 24
drwx------ 5 root root 4096 Feb  4 20:57 .
drwxr-xr-x 3 root root 4096 Jan 22 20:23 ..
drwxr-xr-x 2 root root 4096 Feb  4 18:58 fontconfig
-rw-r--r-- 1 root root 3721 Feb  4 20:57 gdk-pixbuf-loaders.cache
drwxr-xr-x 2 root root 4096 Feb  4 20:57 gio-modules
drwxr-xr-x 2 root root 4096 Feb  4 20:57 immodules

This directory is $SNAP_USER_COMMON. I assume that one of the various wrapper scripts, possibly the xwayland-kiosk-launch one, is setting some environment variable to be $SNAP_USER_COMMON/.cache and you will want to change that to something like $SNAP_DATA/snap-daemon-home/.cache which will be owned/readable by the snap-daemon user.