Træfik backend for snapd

backlog

#1

Right now every service on Ubuntu Core typically opens a different non-standard port for HTTP. This is not very user-friendly for an IoT device.
Imagine running Nextcloud and OpenHAB on a Raspi. The user has to connect to https://<local.domain>:4201/ to access the snapweb frontend, to http://<local.domain>:8080/ to access OpenHAB and only Nextcloud is available via http://<local.domain>/.

Træfik would be a perfect companion to provide the different services via the same domain name, all on port 80 or 443. I opened a feature request for this: https://github.com/containous/traefik/issues/3911

Any other ideas how to implement this integration?


#2

I was having the same thought a few days ago, so thumbs up from me! I think there’s the potential for snapd to take things a step forward and provide an interface for server apps which automatically configures a proxy, plus letsencrypt etc. Interesting to see where this goes…


#3

I would like to thumbs up this too! I’m very willing to contribute to it. I’m not familiar with traefik, but from the quick start guide, it looks like for docker it’s as easy as setting a label on your container:

whoami:
   image: emilevauge/whoami # A container that exposes an API to show its IP address
   labels:
     - "traefik.frontend.rule=Host:whoami.docker.localhost"

I assume the analog for snaps would be setting the snap’s configs. I see traefik is available in the snap store. I suppose the first step would be to check it’s maintained/working.

As proof-of-concept, I would propose a third snap that watches snapd for changes. If there are any changed to installed snaps or their configurations, it checks if there any traefik configurations, then pass these configurations on to the traefik snap (over rest?).


#4

Since both traefik and snapd are written in Go the interfacing is quite simple. See https://github.com/containous/traefik/pull/3987


#5

Out of curiosity have you tried running this as a snap?


#6

Not yet, but should be possible.


#7

@thymythos That’s a pretty interesting idea.


#8

Please also show your support on GitHub as my PR was closed due to lack of interest: https://github.com/containous/traefik/pull/3987#issuecomment-429362074


#9

@niemeyer I made a snap from traefik (including my snapd backend), but it seems it needs some manual review: https://build.snapcraft.io/user/ThyMYthOS/traefik/379932


#10

This is really cool. I’ll give it a try over the weekend. I assume you’re building it from the same repo mentioned above?
I see you’re using the snapd-control interface. From reading other threads on here, i think you will have a hard time getting that approved. The problem being you effectively have complete control over a user’s device with that interface. They might also ask you change the name to something less “official” sounding (traefik-thymythos for example).


#11

My changes are in https://github.com/ThyMYthOS/traefik as my pull request was rejected due to lack of interest.

You’re right, using snapd-control is not optimal and I actually only need to read the properties of all snaps and be able to listen for changes. Maybe some kind of “read-only” interface needs to be added.


#12

I don’t know of any kind of kernel LSM that provides this, but having some kind of proxy sit in between snaps and the snapd socket would enable finer access control to the snapd socket, i.e. in this case the interface would just allow sending GET’s on /v2/snaps, and /v2/snaps/[name]/conf, and all other requests would fail from your snap specifically, whereas other snaps that can actually perform other tasks would be able to send a POST to other endpoints, to install snaps, change configuration, etc.

This would probably involve not only adding a proxy that handles the HTTP access control, but also some form of per-snap authentication method, i.e. OAuth or something like that and finally a full backend to snapd to specify what HTTP control is granted to what interfaces.

For operations outside of the snap, access to the snapd socket would still be authenticated using traditional unix socket access permissions, but inside the snap’s mount namespace, the socket that is presented (i.e. currently /run/snapd.socket) would not be the same socket, but instead a socket that the proxy listens on, and the proxy would then itself have access to the actual unix socket.

Anyways, this is all just a theoretical proposal. For the traefik snap currently to access snaps and their configuration you will need to plug snapd-control, which will need store approval.


#13

This is certainly a useful technique on services you don’t control, but we control snapd it. It could implement polkit, callouts to libapparmor (with extensions to the apparmor syntax), or a separate socket (it already does that for snapctl) to name a few.


#14

snapd-control grants device ownership to the snap for all systems it is installed on. This seems unreasonable considering what it is trying to do. -1 for use of this interface for the global store.

That said, perhaps an interface can be created for this where snapd can mediate the access itself by checking the security label of the connecting process and then checking to see if the interface is connected for this snap. Alternatively, a 3rd socket could be created by snapd for handling these sorts of requests. I suggest working with @pedronis, @niemeyer and @mvo (here in this topic) on how the design might look.


Request for snapd-control interface: traefik-snap
#15

I see basically two possible approaches:

  1. Going through snapd via a limited access interface
  2. Direct connection between service and proxy via a new “proxy” interface

The advantage with 1) is that the service does not need to actively support the connection to a proxy, but instead the user can configure this on his system. If however a significant amount of service snaps do support the new interface this is much more convenient for the user.


#16

Does polkit or AppArmor allow you to filter by HTTP request content? I didn’t think it could, but as you explained there may be ways to avoid having to present authentication information in the HTTP request itself and instead check the security label of the process that is connecting to it to determine what snap the request came from and authenticate based on that.
It looks like that is at implementable on Linux at least using SO_PEERCRED and can be implemented in Go: https://gist.github.com/jsimonetti/d2a822ff8c5b95297b2f#gistcomment-2221225

This is really just one possible way to implement 1, there are other ways that don’t involve a proxy as mentioned by Jamie above. The current state of filtering access AIUI is just limiting file access to the unix socket, which isn’t enough for a “limited snapd-control interface” because you really need to filter by HTTP request content, and with AppArmor, you can only filter read/write (but you need both read/write to make HTTP work at all as you need to write to make the HTTP request, and you need read to get the response).

Yet another way to implement the limited feature set of the snapd-control interface could be to expose some of the information that Traefik needs to the snap through snapctl, but this would again need some other mechanism to only allow snaps that plug some specific new interface access to the HTTP endpoints needed which I don’t think currently exists. That’s because snapctl is also implemented by making HTTP calls to a unix socket.


#17

Sorry, @ijohnson I was probably not precise enough. We 2) I really mean not going through snapd at all, but instead use the interface hooks to tell traefik on which port a service is listening. With “proxy” I actually mean traefik itself which is an HTTP proxy.

The disadvantage with interface hooks right now is that only the participating snaps can set attributes on an interface but the user can’t. So a service snap that should be routed through the traefik HTTP proxy needs to support this kind of interface and provide the right attributes by itself.


#18

No (I mean it could be made to, but that isn’t what I’m suggesting). My point is, snapd knows what the request is so it can do the mediation itself.

One way to do this is to create a snapd interface (eg, snapd-foo) and when a snap connects to the socket and does requests for ‘foo’-like things, snapd does a libapparmor call to obtain the peer’s security label (which corresponds to the snap command) the snapd checks if the snap has the snapd-foo interface connected. If so, allow it, if not, don’t and if requesting a ‘bar’-like thing, disallow it.

Another way to do it is to create apparmor syntax for the mediatable apis, then create apparmor policy in the snapd interfaces (eg, snapd get foo-this, snapd put foo-that, whatever makes for good syntax), load it into the kernel like any other policy, then when a snap connects to the socket and does requests, snapd uses libapparmor to ask the kernel if it is allowed or not (this is what dbus-daemon does today, for example; it is sorta treating the kernel as a database of rules to be queried).

There are of course other options. Point is, a proxy could be used, but I don’t think it’s strictly necessary.


#19

This is indeed very interesting.

Does this need looking at arbitrary snap configurations? one issue there is that we haven’t implemented schemas for configuration which means we have no way to know if a part of the configuration is port/connection information vs some kind of secret. We do plan to introduce schemas but that work is not designed yet nor scheduled soon.