Request for daemon + browser-support for krellian-kiosk

krellian-kiosk is a snap which provides a web runtime for digital signage and interactive kiosks and therefore needs to be both a daemon and have browser-support. Krellian Kiosk is also intended to become an Ubuntu Appliance.

Requesting approval for daemon + browser-support for the krellian-kiosk snap.

Rejected manual review here.

Prototype source code here.

Thanks

Thanks for making this forum post - the use of browser-support with daemon grants a lot of privileges to a snap (see the previous discussions for a similar request a few years ago for some background on this Suppress the security-snap-v2_daemon_with_browser-support warning for the snap).

Does krellian-kiosk absolutely require the use of browser-support? I understand the wish for daemon is to have long-lived daemon that is automatically started etc - in that case, perhaps the use of the snap_daemon user via system-usernames could help so that the snap doesn’t have to run as root.

However, even in this case, the daemon will still be started as root and it would have to drop privileges to the snap-daemon user, so this doesn’t entirely alleviate the security concern.

As such, if this browser-support is absolutely required, we would need to perform publisher vetting as though this were a request for classic confinement.

Hi Alex,

Krellian Kiosk acts as both a web client and web server. It is essentially a full screen web browser which can be remotely controlled over the Web of Things. I therefore can’t see any way around it being both a browser and a daemon as that is fundamentally what it’s designed to do.

Running as a non-root user would be great, but the application also needs to configure network settings during first time setup (via network-manager) and bind a web server to ports 80 and 443 which I expect requires root access. (It might be possible to bind to different ports and redirect traffic using iptables, but that probably requires root access to configure post-boot as well).

What does publisher vetting involve exactly?

Thanks

Ben

P.S. Might it make sense to request auto-connection of the network-manager interface at the same time?

Are you saying that the browser itself is binding to port 80/443 and manipulating the firewall? If so, the typical design pattern would be to perform all your root actions early, then permanently drop to an unprivileged user. See System usernames for example code on how to do this in a snap-compliant manner.

You might also consider separating your application into different parts which adds security benefits. I don’t know how feasible that is, but eg:

  • run your webserver as a separate daemon, and have it bind to port 80/443 and permanently drop to snap_daemon
  • run your web browser as a separate daemon and either permanently drop immediately to snap_daemon or start is as snap_daemon
  • use configure hooks/etc to setup your firewall settings or have a separate daemon modify the firewall settings

It sounds like your snap would need to plugs network, network-bind and firewall-control (and perhaps network-control).

For use of ‘browser-support’ with a daemon, your snap should be modified to use system-usernames so that the browser process is not running as root. Once that is done, we’ll ask that you continue to use system-usernames for the browser process and assuming you agree, a member of our advocacy will contact you privately and ask a few questions related to your relationship with the software, device, etc.

I’m saying that the application acts as both a web user agent (browser) and web server and the server part binds to port 80/443. As long as the application can bind to port 80/443 (as it currently does) I don’t think it should need to configure the firewall.

It does need to configure the network (e.g. configure a Wi-Fi hotspot, broadcast a service over mDNS and connect to a Wi-Fi access point during first time setup) which can hopefully all still be done as a non-root user via the network-manager interface? (It may also need to re-configure network settings again in future if the network drops for any reason).

OK, I will try to get my head around that, thank you…

I would be open to that, but I’m not sure how feasible it is or how much it would actually help in practice.

An example feature of the application is that a user can control the kiosk remotely using its web interface to tell it to load a given URL:

68747470733a2f2f6b72656c6c69616e2e636f6d2f70726f64756374732f626f782d6331392f696d616765732f6b72656c6c69616e5f626f785f75692e706e67

The way this is implemented using Electron is that an Express.js web server listens for a POST request (or alternatively a WebSocket message) and then tells the system chrome to load the provided URL in its webview by sending a message to the renderer process using Electron’s built-in IPC mechanism (the web server and web content rendering already run in separate processes).

Turning the web user agent and web server into separate executables inside the snap package would mean adding an additional IPC layer for the server process to communicate with the user agent process in order to control it. An example IPC mechanism might be a WebSocket server, which would just be recreating what already exists and adding an unnecessary extra layer of complexity!

architecture_with_snap_confinement

If I’ve understood correctly then I would rather avoid this extra complexity if possible as it isn’t needed for any other platforms on which the kiosk application might run.

So for now I will try to implement the system usernames approach and let you know how I get on. I am of course happy to answer any questions about my company and product to assist with the approval process.

Thanks

@benfrancis please update us on your progress with system-usernames etc - also I assume you still require both daemon and browser-support for krellian-kiosk - if so, the only option then is to do publisher vetting as for classic confinement requests. If this is the case, let me know and we can get the @advocacy team to start the process.

@benfrancis, could you please update us on your progress?

Hi @alexmurray @msalvatore, thanks for following up. I haven’t made any tangible progress on this issue yet.

I haven’t fully explored the system usernames system yet, but I noticed all the examples were in C so I was looking for advice on how to achieve this for an Electron/Node.js application.

A key issue is that the application needs to bind a web server to port 80/443 which requires root access.

@ijohnson has suggested having the application start up using the snap_daemon user using setpriv, then have systemd bind to port 80 and add listen-streams to snapcraft.yaml which connect up the sockets.

This seems non-trivial to figure out (though similar to the iptables solution I suggested above) so I’ve been focusing on implementing the remaining features for the 0.1 release of the snap (e.g. configuring a Wi-Fi hotspot for first boot) to make sure that binding to port 80/443 is definitely the only feature that needs root, before I do that.

Any more advice you can provide on how to achieve the above with systemd and listen-stream would be helpful.

Thanks

@benfrancis hopefully @ijohnson can provide some more guidance re systemd / listen-stream as I am not familiar with this.

Sorry if my email was unclear, but I think this should be rather straight-forward for your application, wherever you open the port(s), just replace that bit of code with the calls to get a port from systemd. The relevant bit of code from that example is just

var server = http.createServer(function(req, res) {
...
}
server.listen('systemd')

Wherever you normally call server.listen(80) or some such, just use systemd instead.

and the associated listen-streams in your snapcraft.yaml:

apps:
  krellian-kiosk:
    daemon: simple
    restart-condition: always
    command: desktop-launch xwayland-kiosk-launch bin/drop-snap-daemon.sh $SNAP/krellian-kiosk/krellian-kiosk --no-sandbox -p 80
    sockets:
      http:
        listen-stream: 80
      https:
        listen-stream: 443

(in conjunction with a script bin/drop-snap-daemon.sh)

Have you tried the above? What happens?

@benfrancis ping, did you have a chance to try @ijohnson suggestion? Thanks!

@benfrancis - ping, could you try @ijohnson suggestion? Thanks!

@emitorino Thanks for the suggestion. I’m currently on parental leave. Will be back in about three weeks.

1 Like

@benfrancis - gentle ping - have you had a chance to follow up on this?

Apologies for the delay, I’ve been a bit distracted by working on the spin-out of webthings.io.

I’m hoping to get back to this this week.

1 Like

@benfrancis - another gentle ping, just to let you know this is still on our radar, please don’t forget to update us on your progress when you get a chance to look at this further.

Apologies for the extended delay, the WebThings transition project I was working on took over for a while there!

@ijohnson I’ve had a stab at your proposed solution here. Can you tell me whether I’m going in the right direction?

My application already accepts a port number as a command line argument so it was easy to use a special port for the snap package by changing it from “80” to “systemd” in the launch command. Forgive my ignorance, but is “systemd” a magic value that Node.js is supposed to know about, or is there some other setup required for this to work?

The snap with the above configuration builds successfully but it won’t run. The logs are here.

Some lines that look like they might be relevant:

Dec 17 14:23:46 Unknown-b8-27-eb-f2-7b-0b krellian-kiosk.krellian-kiosk[3085]: _XSERVTransSocketCreateListener: failed to bind listener
Dec 17 14:23:46 Unknown-b8-27-eb-f2-7b-0b krellian-kiosk.krellian-kiosk[3085]: _XSERVTransSocketUNIXCreateListener: ...SocketCreateListener() failed
Dec 17 14:23:46 Unknown-b8-27-eb-f2-7b-0b krellian-kiosk.krellian-kiosk[3085]: _XSERVTransMakeAllCOTSServerListeners: failed to create listener for local
Dec 17 14:23:11 Unknown-b8-27-eb-f2-7b-0b krellian-kiosk.krellian-kiosk[2570]: /snap/krellian-kiosk/x2/bin/xwayland-kiosk-launch: line 151: /snap/krellian-kiosk/x2/bin/drop-snap-daemon.sh: Permission denied

Dec 17 14:23:47 Unknown-b8-27-eb-f2-7b-0b krellian-kiosk.krellian-kiosk[3085]: /snap/krellian-kiosk/x2/bin/xwayland-kiosk-launch: line 151: /snap/krellian-kiosk/x2/bin/drop-snap-daemon.sh: Permission denied

Do you have any further advice?

Much appreciated.

Ben

Hi again @benfrancis are you able to get system journal denials as well from journalctl --no-pager | grep DENIED ?

I’m curious what the denials are, as it appears to be trying to listen on the local socket but failing due to EPERM? Or perhaps the drop-snap-daemon.sh file is not executable ?

Also did you add the util-linux package to your stage-packages so that the setpriv utility is available from your snap?

Does this help?

Jan 13 16:48:53 Unknown-b8-27-eb-f2-7b-0b audit[2213]: AVC apparmor="DENIED" operation="open" profile="snap.krellian-kiosk.krellian-kiosk" name="/var/cache/fontconfig/" pid=2213 comm="ls" requested_mask="r" denied_mask="r" fsuid=0 ouid=0
Jan 13 16:48:53 Unknown-b8-27-eb-f2-7b-0b kernel: audit: type=1400 audit(1610556533.545:427): apparmor="DENIED" operation="open" profile="snap.krellian-kiosk.krellian-kiosk" name="/var/cache/fontconfig/" pid=2213 comm="ls" requested_mask="r" denied_mask="r" fsuid=0 ouid=0
Jan 13 16:48:53 Unknown-b8-27-eb-f2-7b-0b audit[2215]: AVC apparmor="DENIED" operation="capable" profile="snap.krellian-kiosk.krellian-kiosk" pid=2215 comm="Xwayland" capability=1  capname="dac_override"
Jan 13 16:48:53 Unknown-b8-27-eb-f2-7b-0b audit[2215]: AVC apparmor="DENIED" operation="bind" profile="snap.krellian-kiosk.krellian-kiosk" pid=2215 comm="Xwayland" family="unix" sock_type="stream" protocol=0 requested_mask="bind" denied_mask="bind" addr="@/tmp/.X11-unix/X63"
Jan 13 16:48:53 Unknown-b8-27-eb-f2-7b-0b kernel: audit: type=1400 audit(1610556533.981:428): apparmor="DENIED" operation="capable" profile="snap.krellian-kiosk.krellian-kiosk" pid=2215 comm="Xwayland" capability=1  capname="dac_override"
Jan 13 16:48:53 Unknown-b8-27-eb-f2-7b-0b kernel: audit: type=1400 audit(1610556533.981:429): apparmor="DENIED" operation="bind" profile="snap.krellian-kiosk.krellian-kiosk" pid=2215 comm="Xwayland" family="unix" sock_type="stream" protocol=0 requested_mask="bind" denied_mask="bind" addr="@/tmp/.X11-unix/X63"
Jan 13 16:48:54 Unknown-b8-27-eb-f2-7b-0b audit[2215]: AVC apparmor="DENIED" operation="capable" profile="snap.krellian-kiosk.krellian-kiosk" pid=2215 comm="Xwayland" capability=21  capname="sys_admin"
Jan 13 16:48:54 Unknown-b8-27-eb-f2-7b-0b kernel: audit: type=1400 audit(1610556534.037:430): apparmor="DENIED" operation="capable" profile="snap.krellian-kiosk.krellian-kiosk" pid=2215 comm="Xwayland" capability=21  capname="sys_admin"

I’m still curious about the magic “systemd” port.

Nope, I will try that now.

Maybe this was missed somewhere along the way, but you also need to specify require('systemd'); in your javascript somewhere, as that’s the package which will inherit the ports from systemd via listen-streams, etc.

this denial (and the others except the fontconfig one) are interesting but doesn’t seem to be related to inheriting the http socket from systemd, these errors seem to be related to your app trying to use XWayland things and not binding to the port that systemd is providing, does your snap work if you install it in devmode?

Also if you just use the systemd port specification but not drop privileges to snap_daemon user does it work? My guess is that your snap 1) works in devmode and 2) works when run as root, but not as snap_daemon because it seems that the snap is trying to do XWayland things which afaict need to be done as root.