Request for daemon + browser-support for webthings-controller

WebThings Controller is a kiosk-style web app runtime for smart displays, which by its nature both needs to be a daemon (since it runs continuously from boot and restarts if it crashes), and have browser-support (since it requires embedding and controlling a webview).

Screenshot of the initial prototype with WebThings Gateway as an example web app:

Please note that although designed for a slightly different use case, this is very similar to the krellian-kiosk snap which I created a few years ago. It took nine months of working together to finally get that snap approved, but I’m hoping this time around will be a lot simpler since things seem to have moved along a lot.

Notably this time I am using Ubuntu Frame with Electron/Chromium’s Wayland support via the Ozone platform (as per the Mir server Electron example), rather than xwayland-kiosk-launch as was previously recommended. This snap also currently doesn’t have the complication of running a web server which needs binding to port 80.

Is there a current best practice for running Electron as a non-root user (i.e. as a snap_daemon user), as per the Snap Store requirements for approving daemon+browser-support?

This is what we had to do last time, but I’m hoping it might be simpler this time around since it required a lot of hacks.

Please note that for legal purposes WebThings is a trading name of Krellian Ltd. and the “krellian” user account is already a vetted publisher on the Snap Store, but this webthings-controller snap needs to be published from the separate “webthingsio” user account.

Thanks in advance for your help.

Hi @benfrancis,

Thanks for the post and thanks for including all the relevant history. I will take a good read of the (long) previous post before weighing in on this one, but just wanted to post now to say that has begun!

1 Like

That was indeed quite a saga with krellian-kiosk!

To answer your questions directly, I do not know the best practice for dropping privileges with Electron apps, so I’ll tag in @Igor who might know a better starting point, and maybe @alan_g might have some suggestions/advice.

If it does not prove possible to achieve relatively easily, given the effort you have gone to previously and the lessons learned from that, plus the understanding of the nature of these kiosk apps, I think it would be fair to approve this request regardless… but we should try first!

As it happens, I’ve recently been working on working out a way to run Frame as a user, not as a system service.

It boils down to having Frame SystemD-launched as a user session, and then the client application launched within.

I’ll be documenting this within a couple weeks, but the whole thing can be seen here:

Just replace iot-example-graphical-snap with your own snap and things should work as usual!

Let me know if you get it going and I’ll follow up when I’ve more to share.

1 Like

Hi @Saviq, thank you for sharing this.

I’m not sure exactly what I’m looking at, is this a gadget snap which starts a particular snap package in a different way as a non-root user? Presumably that won’t help with store approval, since the configuration is external to the snap.

If it starts as a user session, will the application still automatically restart itself if it crashes?

I may have to wait for your documentation to properly understand what you’ve done, but in the meantime I’m still interested in a way to drop privileges on my Electron app so it can be approved for the snap store and run as a normal system daemon.

Correct, an image built using this gadget snap (see docs) starts a user session with Frame as the compositor, and then the applications configured as user services.

It will, because it’s browser-support together with daemon which is a problem, as the former opens quite a set of privileges and an exploit in the browser could then get you (confined, but still) root privileges.

Yes it will!

[Service]
Restart=always

Ultimately you’re in control (see systemd.service).

The problem with “dropping privileges” is that you have to trust the snap that it will do so. So the snap confinement story gets poked here, too.

Documentation pending!

@Saviq That is interesting, thank you.

Assuming I don’t need to do anything else as root during startup in the future (e.g. binding to port 80 like Krellian Kiosk originally did), the approach you describe sounds like a good option.

I might also still need some help getting Electron to run as a non-root user on Ubuntu Core though. Currently if I remove the daemon configuration from the webthings-controller snapcraft.yaml and start it manually on Ubuntu Core as the default ubuntu user, it fails to start without sudo. Is that expected? The only output on the console is:

Setting up watches.
Watches established.

Would modifications also be needed to the example snapcraft.yaml from the Electron-quick-start branch of the iot-example-graphical-snap repo to get it to run in this way?

Thanks

Yeah it’s waiting for a wayland compositor (e.g. Frame) running for the user, which there is none - that’s what the documentation will cover. As a very quick hack you could do (from ssh):

systemd-run --uid=$UID -p TTYPath=tty8 -p PAMName=login -- tail -f /dev/null
# Ctrl+Alt+F8
systemd-run --user ubuntu-frame
systemd-run --user <your-app>

This starts the user session on tty8, activates it (loginctl activate doesn’t always work, depending on setup) and starts Frame inside the user session, and the app. Ultimately this flow, encoded in SystemD .service files, is what is needed.

No, as long as there is a plain app in there (no daemon:), that can be wrapped in a user service. We’ll just set daemon: false in the gadget configuration so it doesn’t waste resources.

@Saviq Thanks for this.

If I’ve understood correctly then I guess the downside of this approach is that if a user wants to install my snap (alongside the ubuntu-frame snap) on a vanilla Ubuntu Core installation, it will fail to start out of the box. The snap will only work within a special image generated from my own custom gadget snap, or on a vanilla image with the additional steps you describe.

That’s not necessarily a problem for my particular use case, but it’s arguably not great for the Snap Store if it results in a bunch of snaps that fail to start out of the box.

I do see that this is a better approach from a security point of view though, for snaps which don’t actually require root access.

Looking forward to your documentation so I can try to put this into practice.

There is work ongoing to allow user services, so this may get simpler in the future. Whether autostarting should be the default behaviour in a user session is always going to be a debate :slight_smile:

Any case, image-based deployments are preferred so you keep as little state on the device as possible (and can easily scale / redeploy). But yeah, my documentation will go through both cases.

1 Like

I have banished the daemon for now, with a view to trying the approach proposed by @Saviq.

This has unblocked a 0.0.1 edge release from being published to the Snap Store.

I wouldn’t say this issue has quite been solved yet, but we certainly have a path towards a potential solution using a gadget snap to create a custom image where the snap is started as a user service rather than a system service.

Thank you for the help so far.

3 Likes

@benfrancis that’s great.

I am then removing this request from our review queue. Still, if you have any further questions or need further accesses to properly make webthings-controller work, please write here again and we will be happy to assist.

Thanks!

@Saviq Hi, I’m following up to find out the latest status of the user services approach you described here, and whether it is documented anywhere yet?

A key question I have is whether using this approach will require me to publish my own custom gadget snap, since as I understand it those can not be hosted in the public Snap Store and I would therefore have to pay for a dedicated app store (currently prohibitively expensive for our open source project).

I would ideally like to be able to generate a custom OS image which bundles an Electron-based snap and starts it as a user service on boot. I have successfully been able to create and sign a model assertion and use it to generate a custom OS image which bundles the snap (along with ubuntu-frame, ubuntu-frame-osk and an existing gadget snap), but it currently requires a user to manually associate the device with an Ubuntu SSO account, log in on the command line and manually start the snap as root (which then won’t automatically restart if it crashes).

This could potentially be turned into an Ubuntu Appliance if there was interest in that.

FYI, the webthings-controller snap has now been renamed/moved to webthings-shell, but the requirements are the same.

Thanks

Ben

Hi @benfrancis, here’s this approach documented:

https://mir-server.io/docs/run-ubuntu-frame-unprivileged

It’s not the ultimate solution, we need to teach SnapD to do this on Core images rather than relying on cloud-init, which will go away from Core at some point.