Netplan apply inside snaps

NOTE: this has been superseded by a new plan, see below for details

To support applying netplan configuration from inside a snap, we have a 2 phase plan to implement this, with the eventual goal of “it just works” transparently by calling netplan apply inside snaps. The reason we can’t just provide access for netplan to apply configuration directly is because it was decided that the necessary accesses were effectively full control and access to systemctl/systemd (see discussion on https://github.com/snapcore/snapd/pull/5915).

Phase 1

We will implement a hidden command to snapctl: snapctl netplan-apply, which will simply have snapd run netplan apply on behalf of the snap outside of confinement if and only if the calling snap:

  • has network-setup-control connected
  • has the netplan-apply attribute on the network-setup-control interface specified as true (this will require store approval)

This will enable a snap to call netplan apply by calling snapctl netplan-apply, but this is only provided as a temporary measure until Phase 2 is available.

See https://github.com/snapcore/snapd/pull/7107 for an open PR which is close to implementing it.

Note that the netplan-apply attribute is analogous to the allow-sandbox attribute for browser-support interface, granting additional accesses to a store-vetted, trusted consumer of the network-setup-control interface.

Phase 2

Once snapctl is able to proxy calls to netplan apply, then we will teach netplan itself to check if it is running inside a snap, and if so, netplan will call snapctl netplan-apply directly so that a application can use netplan apply inside a snap and it just works. This is how xdg-open currently works inside snap confinement.

Note that in this phase, we still have the hidden snapctl command, which will only be meant to be called by netplan itself.

Related to this topic there are:

We discussed it and decided to abandon the original plan I proposed above in favor of a new one. The reasons against this plan were:

  • snapd should not be in the business of mediating application logic (i.e. today it’s netplan apply, tomorrow it’s something else)
  • in other situations, we simply provide for AppArmor and other security backend mediation to allow the application snap to communicate with something else that has the real privilege
  • adding even a hidden snapctl command is still too much maintenance burden since even though it’s hidden we can’t remove it later on in Phase 2 and would be forever committed to it
  • the xdg-open example is a bad example because it’s implemented in a different way primarily because it needs to run as the calling user to talk to userd
  • even if we allow a snap to manage systemd units (such as systemd-networkd and NetworkManager), it’s conceivable that one day snapd might manage or allow configuration of the network, in which case now there’s another avenue outside of snapd that would be controlling this. For example, snapd might want to restart the renderer, but another snap with permissions to access systemctl/systemd would then stop that renderer for some reason and now snapd is stuck unable to proceed.

As such the new plan of action is to have upstream netplan work on creating a more easily security mediated interaction to implement netplan apply, probably by creating a new netpland daemon, which performs the actual operation of restarting the needed network renders (such as systemd-networkd and NetworkManager) outside of confinement. There are a few ways we thought of to make this as easy to implement as possible:

  • Have a simple dbus activated daemon which just calls netplan apply outside of confinement - snaps inside confinement can be provided access to send dbus commands to this daemon with already available dbus policy in AppArmor profiles
  • Have netplan ship a simple systemd Path unit file, which when a particular netplan configuration is about to be applied and is prepared in a temporary file location, that file modification would trigger a systemd unit to run which performs the restart of the necessary renderers outside of confinement.

Note that on Ubuntu Core, this daemon/service would run outside of confinement, and as such would be a new service shipped inside the core/snapd snaps but that’s an implementation detail to be worked out with the netplan team.

This provides snapd with a way to enable netplan apply to be called without adding too much security policy to an interface and also without requiring snapd to track how netplan evolves with new commands/arguments/formats, etc. outside of the dbus commands or files that netplan uses.

Finally, this also leaves open the possibility that netplan becomes it’s own snap and these application snaps talk to netplan snap, again over dbus or something mediated through security policy from a netplan-support interface which also allows the privileged operations necessary to manage other kinds of renderers. Note that this is safer than providing all of the rules to network-setup-control because there would only be one single snap to worry about managing such things and presumably the netplan snap would be in a similar maintenance situation to netplan now. [note to @mvo, @pedronis, and @niemeyer we didn’t talk about this in the meeting, I just thought of this now afterwards, feel free to nack this if there’s more context I’m missing that makes this not work]

1 Like

why would that need to be a tememporary dir here ? if the unit just watched /etc/netplan for added/changed files and called netplan generate, netplan apply one after the other this would totally be sufficient … /etc/netplan will only change if root creates a new config in there and i think we can assume that if a new configuration is created (or an existing one is changed) that the admin also wants it applied (it will (at least in the case of core) be applied anyway on next reboot).

this sounds really like a pretty simple, straightforward and maintenance-free solution IMHO.

Sure, it doesn’t need to be a temporary directory, if /etc/netplan is the right canonical file to watch then it should be fine to watch that instead.

here is a little proof of concept:

/etc/systemd/system/netplan-watcher.path:

[Path]
PathModified=/etc/netplan/

[Install]
WantedBy=multi-user.target

/etc/systemd/system/netplan-watcher.service

[Unit]
Description=netplan watcher

[Service]
Type=oneshot
ExecStart=/root/netplan-applier.sh

[Install]
WantedBy=multi-user.target

/root/netplan-applier.sh

#! /bin/sh

echo "/etc/netplan changed !!!!!!!!!!!!!!!"

the .path unit file applies an inotify watch on the dir,
the echo is executed every time anything in that dir changes (on a final implementation the echo would then simply become “netplan generate && netplan apply” (and indeed the script would allow for more, like yaml verification and the like))

1 Like

While I agree with the new direction, this is overstated. userd, part of snapd, is already doing this for
‘autostart’. I see no reason why it couldn’t do this for other things, but only when they make sense.

This probably cannot be overstated. :slight_smile: I agree with this point because while we could wrap systemctl, etc to allow a snap to restart/etc specific services, then declare in some manner what can be restarted/etc and then provide some mechanism to mediate that, once we open that door, snapd can’t close it without breaking snaps. It also doesn’t work particularly well for restarting snaps that provide similar functionality but are differently named: is a snap supposed to enumerate restarting network-manager on classic, systemd-networkd on classic, network-manager snap, network-manager-foo snap, the connman snap, etc, etc, etc? By not allowing a snap to restart systemd services outside of its own services (regardless of mediation and scoping), we allow for the opportunity for snapd to manage things robustly without breaking snaps.

I understand why you suggested this since the new proposal currently doesn’t allow for a netplan snap without itself using the ‘outside of confinement’ restart mechanism, but by suggesting this you are “kicking the can” and we are back to either allowing netplan-support to use systemctl directly, reintroducing the hidden snapctl command, or similar.

Also, I’m a little concerned about the trigger mechanism that is allowed by network-setup-control since a naive implementation requires the connecting snap to be careful to write its configuration atomicly. Eg, perhaps it needs to modify a file, but does so through multiple edits; the trigger should only happen at the end. Also, what if it needs to modify two files in the directory and the trigger fires after the first is done and networking breaks?

It was decided with the netplan team that netplan will gain a D-Bus API for calling netplan apply (in the form of a D-Bus activated systemd service), and that when netplan is running inside a snap, it will use that instead of performing any operations itself. The D-Bus activated service which runs outside of confinement, then will basically just call netplan apply directly without issue.

Is there an ETA from netplan upstream for providing this new DBus API and wiill it be SRU’d to xenial and bionic?

Also which snapd interface will grant permission to use this new DBus API, ‘network-control’?

I don’t have an ETA, but all of the work upstream has landed and the first step is a release to Eoan, which I’m told will happen ASAP and certainly before the freeze tomorrow. After that the SRU process will start, and we have communicated that it needs to be SRU’d to xenial and bionic.

The interface for using netplan apply will be network-setup-control.

Has this API been integrated yet? Is it part of the core snap? I tried the following in the snap but keep getting permission denied

import shutil
import subprocess
busctl = shutil.which('busctl')
res = subprocess.call([busctl, "call", "--quiet", "--system","io.netplan.Netplan","/io/netplan/Netplan","io.netplan.Netplan","Apply",])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/snap/mysnap/x1/usr/lib/python3.6/subprocess.py", line 287, in call
    with Popen(*popenargs, **kwargs) as p:
  File "/snap/mysnap/x1/usr/lib/python3.6/subprocess.py", line 729, in __init__
    restore_signals, start_new_session)
  File "/snap/mysnap/x1/usr/lib/python3.6/subprocess.py", line 1364, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
PermissionError: [Errno 13] Permission denied: '/usr/bin/busctl'

I have access to the following core slots network, network-bind, network-control, network-observe, network-setup-control, network-setup-observe

Hi, busctl from the base snap is not currently allowed by any interface, but it certainly could be I think. @jdstrand what do you think?

Also, for reference, instead of using bustcl you can use dbus-send, which is allowed by default policy but has slightly different syntax. Here’s an example to use dbus-send:

$ dbus-send --system --type=method_call --print-reply --dest=io.netplan.Netplan /io/netplan/Netplan io.netplan.Netplan.Apply

Hi @ijohnson, I tried using this command on ubuntu core througha snap with network-setup-control and system-observe plugs.

it returned "method return time=1654501769.202891 sender=:1.13 -> destination=:1.93 serial=44 reply_serial=2 boolean true "

But it does not apply the netplan. When i directly set it on terminal “sudo netplan apply” that works. Am i missing something?

I’m encountering a similar issue. When I call sudo netplan apply everything works, but calling the dbus apply either from inside a snap or from command line causes both ethernet interfaces to stop working until I unplug the cable and plug it back in. After restoring one interface, ip a shows that the other interface is up and has an ipv6 address but not an ipv4 address. Attempting to ping 8.8.8.8 using the other interface causes the working interface to break again.

It appears that the dbus apply works just fine if I snap stop network-manager first.