Aptly Systemd Socket Activation

In the previous blog post I talked about Unix domain socket enablement in Aptly, a very popular deb repository management solution. In addition to adding socket support, I’ve also added support for systemd socket activation.

Systemd’s socket activation is an inordinately fancy feature of systemd where it will take over socket creation and management before the actual service is running. It ensures that incoming requests aren’t lost if the service crashes or isn’t started yet. When one has the opportunity to use systemd, letting it do socket activation for Aptly is super handy as it also gives easy access to chown capabilities.

Using socket activation requires you to create two systemd units. A service unit, describing the aptly service itself, and a socket unit, for listening for traffic. Examples files may look like this:

# aptly.service
[Unit]
Description=Aptly archive service

[Service]
ExecStart=/home/archive/bin/aptly api serve
WorkingDirectory=/home/archive
Restart=always
# aptly.socket
[Unit]
Description=Aptly archive service socket

[Socket]
ListenStream=/home/archive/aptly.sock
SocketMode=0660
SocketUser=archive
SocketGroup=archivesocket

[Install]
WantedBy=sockets.target

Enabling and starting the socket unit will create the socket file at /home/archive/aptly.sock and start the service upon incoming traffic, which you could cause, for example, using curl --unix-socket /home/archive/aptly.sock http:/api/version.

Fairly exciting already. But if you’ve previously been listening on a TCP port you may wish to establish a smoother migration path without having to change your entire infrastructure to work with sockets all at once. Aptly however only supports listening on one socket, so you can’t just have it listen on both a TCP socket and a unix socket. Fortunately, systemd also has a neat solution for this, called systemd-socket-proxyd. Using the socket activation systemd itself can proxy traffic from a TCP socket to our new aptly socket file.

Again example units for illustration:

# aptly-proxy.service
[Unit]
Description=Aptly Socket (Proxy Listener)

[Service]
ExecStart=/lib/systemd/systemd-socket-proxyd /home/archive/aptly.sock
PrivateTmp=yes
# aptly-proxy.socket
[Unit]
Description=Aptly Socket (Proxy Listener)
After=aptly.socket

[Socket]
ListenStream=127.0.0.1:8080

[Install]
WantedBy=sockets.target

When enabling and starting this socket unit systemd will listen on 127.0.0.1:8080 for traffic and proxy it through systemd-socket-proxyd to our actual Aptly socket file. At this point Aptly will be able to receive traffic from both the unix socket and the TCP socket (although of course effectively Aptly is only listening on the unix socket).

Now if only systemd could play audio it’d be quite the bundle of usefulness 😉

Advertisements

Aptly API over Unix Sockets

ruby

Last week I’ve added a bunch of code to enable use of Aptly’s API over Unix domain sockets.

Aptly is a Debian repository management software we use for KDE neon‘s deb repository. It is one of the more popular software for repo management as it is easy to deploy and features a handy REST API.

One of the more annoying shortcomings of Aptly’s API is the fact that it has no authentication mechanism. This makes using the API from remote hosts a fairly unwieldy affair as you first have to harden the beast.

Your options are basically limited to either run Apache (or some other HTTP server) that proxies Aptly’s API and adds HTTPAuth to it, or you pipe everything through SSH. Both of which are fine solutions, but they hide a fairly awkward security problem.

Aptly would be listening to a port on localhost and the lack of access control means that if any user gets compromised the Aptly instance is effectively compromised as well. It also means that if you do not read the documentation and explicitly set Aptly to listen on localhost only, you end up with a publicly writable Aptly instance. Ugh.

There is a really simple way out though: listening to a Unix domain socket rather than a TCP socket. Unix sockets are pseudo-files and therefore file access control applies. This makes access control easy to manage through the system’s user/group/all permissions of the socket file.

srw-rw----  1 user group    0 Mar  6 14:23 aptly.sock

Making use of this is super easy too!

Socket Listening

Aptly API’s serve invocation now supports a URI scheme for Unix sockets.

aptly api serve -listen="unix://tmp/aptly.sock"

This will (re)create a Unix domain socket at the defined path and listen to it.

Using

Using this you can restrict full access to the API service down to a user level. More fine-grained control still needs a frontend add-on such as an HTTP server with HTTPAuth, but for most purposes a per-user access control should be sufficient and vastly more secure than the previous TCP socket binding.

With OpenSSH’s direct-streamlocal@openssh.com SSH protocol extension you can then even communicate with a remote Aptly instance through a forwarded socket.

To enable myself to use this I’ve also added support for this to the Ruby net/ssh gem and my aptly-api gem.

Using latest master of everything involved we can now serve the API on a socket and then access that remotely via SSH like so:

require 'aptly'
require 'net/ssh'

Thread.new do
  Net::SSH.start('remotehost', 'user') do |ssh|
    ssh.forward.local_socket('/tmp/local.sock', '/home/user/aptly.sock')
    loop { ssh.process(0.1) }
  end
end

sleep 2 # wait for open

Aptly.configure do |c|
  c.uri = URI.parse('unix:///tmp/local.sock')
end
Aptly::Repository.list

Lovely, isn’t it?

Plasma in a Snap?

…why not!

Shortly before FOSDEM, Aleix Pol asked if I had ever put Plasma in a Snap. While I was a bit perplexed by the notion itself, I also found this a rather interesting idea.

So, the past couple of weeks I spent a bit of time here and there on trying to see if it is possible.

img_20170220_154814

It is!

But let’s start in the beginning. Snap is one of the Linux bundle formats that are currently very much en-vogue. Basically, whatever is necessary to run an application is put into a self-contained archive from which the application then gets run. The motivation is to isolate application building and delivery from the operating system building and delivery. Or in short, you do not depend on your Linux distribution to provide a package, as long as the distribution can run the middleware for the specific bundle format you can get a bundle from the source author and it will run. As an added bonus these bundles usually also get confined. That means that whatever is inside can’t access system files or other programs unless permission for this was given in some form or fashion.

Putting Plasma, KDE’s award-winning desktop workspace, in a snap is interesting for all the same reasons it is interesting for applications. Distributing binary builds would be less of a hassle, testing is more accessible and confinement in various ways can lessen the impact of security issues in the confined software.

With the snap format specifically Plasma has two challenges:

  1. The snapped software is mounted in a changing path that is different from the installation directory.
  2. Confining Plasma is a bit tricky because of how many actors are involved in a Plasma session and some of them needing far-reaching access to system services.

As it turns out problem 1, in particular, is biting Plasma fairly hard. Not exactly a great surprise, after all, relocating (i.e. changing paths of) an installed Plasma isn’t exactly something we’ve done in the past. In fact, it goes further than that as ultimately Plasma’s dependencies need to be relocatable as well, which for example Xwayland is not.

But let’s talk about the snapping itself first. For the purposes of this proof of concept, I simply recycled KDE neon‘s deb builds. Snapcraft, the build tool for snaps, has built-in support for installing debs into a snap, so that is a great timesaver to get things off the ground as it were. Additionally, I used the Plasma Wayland stack instead of the X11 stack. Confinement makes lots more sense with Wayland compared to X11.

Relocatability

Relocatability is a tricky topic. A lot of times one compiles fixed paths into the binary because it is easy to do and it is somewhat secure. Notably, depending on the specific environment at the time of invocation one could be tricked into executing a malicious binary in $PATH instead of the desired one. Explicitly specifying the path is a well-understood safeguard against this sort of problem. Unfortunately, it also means that you cannot move your installed tree anywhere but where it was installed. The relocatable and safe solution is slightly more involved in terms of code as you need to resolve what you want to invoke relative from your location, it being more code and also not exactly trivial to get right is why often times one opts to simply hard-compile paths. This is a problem in terms of packing things into a relocatable snap though. I had to apply a whole bunch of hacks to either resolve binaries from PATH or resolve their location relative. None of these are particularly useful patches but here ya go.

Session

Once all relocatability issues were out of the way I finally had an actual Plasma session. Weeeh!

Confinement

Confining Plasma as a whole is fairly straightforward, albeit a bit of a drag since it’s basically a matter of figuring out what is or isn’t required to make things fly. A lot of logouts and logins is what it takes. Fortunately, snaps have a built-in mechanism to expose DBus session services offered by them. A full blown Plasma session has an enormous amount of services it offers on DBus, from the general purpose notification service to the special interest Plasma Activity service. Being able to expose them efficiently is a great help in tweaking confinement.

Not everything is about DBus though! Sometimes a snap needs to talk with a system service, and obviously, a workspace as powerful as Plasma would need to talk to a bunch of them. Doing advanced access control needs to be done in snapd (the thing that manages installed snaps). Snapd’s interfaces control what is and is not allowed for a snap. To get Plasma to start and work with confinement a bunch of holes need to be poked in the confinement that are outside the scope of existing interface. KWin, in particular, is taking the role of a fairly central service in the Plasma Wayland world, so it needs far-reaching access so it can do its job. Unfortunately, interfaces currently can only be built with snapd’s source tree itself. I made an example interface which covers most of the relevant core services but unless you build a snapd this won’t be particularly easy to try 😉

Summary

All in all, Plasma is easily bundled up once one gets relocatability problems out of the way. And thanks to the confinement control snap and snapd offer, it is also perfectly possible to restrict the workspace through confinement.

I did not at all touch on integration issues however. Running the workspace from a confined bundle is all nice and dandy but not very useful since Plasma won’t have any applications it can launch as they either live on the system or in other snaps. A confined Plasma would know about neither right now.

There is also the lingering question of whether confining like this makes sense at all. Putting all of Plasma into the same snap means this one snap will need lots of permissions and interaction with the host system. At the same time it also means that keeping confinement profiles up to date would be a continuous feat as there are so many things offered and used by this one snap.

One day perhaps we’ll see this in production quality. Certainly not today 🙂

mascot_konqi-app-dev