Monday, June 4, 2012

Authorization Rules in polkit

For the past couple of weeks, I've been working on rewriting the part of polkit that actually makes the authorization decision: the polkit authority.

Some history

In its current implementation, the so-called polkit local authorityended up being one of these things that never really worked well for the (relatively small subset of) users with a need to configure it. First of all the local authority was never really supposed to be the main polkit authority... back then, we envisioned that in an Enterprise setting, you'd have something like FreeIPA to handle permissions and authorization and this would in turn provide a polkit backend responsible for answering authorization questions from applications (sadly this haven't materialized yet but with this new work it could be we're a lot closer).

Second of all, the local authority had some serious usability problems, in fact so many that I early on re-purposed a bug for the rewrite. I think I basically concluded that it's just too hard to even do simple things and the key-file based format and priority rule scheme for the .pkla file format wasn't really helping. It's also really hard to test .pkla files.

Defining the problem

After thinking about this problem on and off for a while (while working on other things, mostly udisks/GNOME Disks and all the GDBus stuff), I identified the following requirements:

There is no one-size-fits-all - for example some admins want to allow all users in a group to do the action xyz, while other admins want the opposite (forbid users in a group to do the action xyz). Similarly, some admins want black-lists ("allow anything but action xyz") and some want white-lists ("only allow actions xyz, abc, ..."). Some admins even want to have the result depend on the time ("don't allow xyz on school nights").

It would be good to have more information than just the action available when making a decision. For example for mounting or formatting a disk, it would be nice to have the device file or serial number or name and e.g. make the decision depend on this. For connecting to a wireless access point, having access to its ESSID would be great (to only allow connections to ESSID's in a whitelist) and so on.

Some admins may want to use external programs so there should be some facility available for this. Although this can't be the primary interface because forking a process every time someone calls CheckAuthorization() is a recipe for disaster [1].

Ideally, it should be easy test authorization rules - after all, this has to do with security so being able to easily (and automatically) test that your rules Does The Right Thing(tm) would be, uhm, great

A lot of these requirements are actually similar to the requirements you have for udev rules. And a lot of the constraints are similar as well, especially the fork-fest part.

Another thing that's important to identify is who is going to end up using this. The answer here is mostly "Enterprise Admin" although in reality a lot hobbyist end up using it because, well, they like to tinker around. Some users of Linux distributions with broken defaults (see below) may also need to undo the damage done by the distribution so they can actually use their computer. All in all, one can probably assume that the target user here is relatively skilled, ie. can read the provided documentation and is at least capable of copy-pasting some snippet from a website into a file in /etc as root and check that it has an effect.

[1] : History lesson: before we had udevd(8), the kernel forked /sbin/hotplug for every hotplug event. And /sbin/hotplug, being a shell script and all, itself forked another ten shell scripts or so in /etc/hotplug.d/and these forked other shell-scripts and... the result was that hotplugging a USB hub full of devices could easily take minutes because tens of thousands of /bin/sh-instances were forked. Awesometown. Today udevd(8) does the same in less than a second without forking a lot of extraneous processes.

For the second requirement, I just exposed information we already have to the rules engine. I also rewrote the "Writing polkit applications" chapter to mention that mechanisms should use this feature as well as a ton of other advice. [2]

For the third requirement, spawning programs, I added a simple polkit.spawn() method.

The fourth requirement, testing, is fulfilled by just observing that the polkit authorization rules are JavaScript files that the user can test any way they want by trivially mocking the Polkit, Action and Subject types via Duck Typing in their favorite server-side JavaScript environment (gjs, node.js, seed etc.).

After a couple of prototyping attempts, I ended up with something that isn't too awful - see for yourself in the polkit(8) man page in the AUTHORIZATION RULES section. I also ended up adding a number of tests for this, including a test to ensure that even runaway-scripts are terminated. Over all, I'm very satisfied with the result.

[2] : I also included common-sense advice like "don't ask for a root password for adding printer queues" in this section

Wait, isn't embedding a JS interpreter inherently dangerous?

Embedding a JS interpreter is actually perfectly safe. First of all, it all runs inside the polkitd(8) system daemon (which runs without privileges, see below). Second of all, all data passed to the rules are either trusted or from a process that is trusted (except where designated untrusted, e.g. pkexec(1)'s command_line variable). Third, being an interpreted language, we can actually sensibly terminate runaway scripts.

Hmm, OK, but you are bloating Linux anyway. You suck.

The only new dependency here is libmozjs185.so.1 which in turn depends on the C++ runtime and NSPR (which NSS also depends on and most Linux installs have this library). Note that you also already need a JS interpreter for proxy server auto-configuration. It's also possible (or will be, at least) to just not install polkitd(8) at all (or disable/mask it using systemd) while still having the client-side libraries installed.

Other features

Apart from the new authorization rules engine, I also made the polkitd(8) system daemon run as the unprivileged polkitd user instead of root (much safer for obvious reasons). The main reason why this hadn't been done before had to do with the fact that polkitd(8) was loading backends via an extension system and we didn't really know if some future backend had to run as root. Since I decided nuke the extension system, we no longer need to make such assumptions so changing it was straightforward.

A while ago, I also added the pkttyagent(1) command on request from Lennart for optional use in systemctl(1). Optional here means that it's a soft dependency - systemctl(1) works fine even when polkit is not installed.

Next steps

I've been doing all this work on the wip/js-rule-files branch and today I merged this branch to the master branch. I plan to do a new 0.106 release shortly and put it in what will end up being Fedora 18.

Once that's available, I plan to file bugs against the most important mechanisms (such as NetworkManager) so they can start exporting variables to be used in authorization rules and also properly document this.

20 comments:

I wonder if, instead of libmozjs, you couldn't have gone with the embedding v8 route. apart from the inherent mess of including another code base, I mean. this would have kept the size and dependencies in check, at least. not that it's a huge problem anyway: NSS and NSPR are going to be already paged in by any session.

Yeah, I only used SpiderMonkey because of familiarity and the fact that I have 3+ people in a 10-feet radius with experience of embedding it in GNOME Shell. It should be pretty easy to switch to another JS engine - the polkit(8) man page even mentions that as a possibility when discussing that rules should not use e.g. the let keyword.

(And I was actually reading through /usr/include/v8.h the other day and unless you are allergic to c++ (which I'm not) it doesn't look to bad...)

In the post (and the bug report) you mention similarity to udev rules and even have an example of what using such a syntax for polkit would look like. What was the reason for going with js over this in the end - a preference for a turing complete config language?

Charles: I like udev a lot but its rules format is really quirky and byzantine (e.g. GOTO). I decided to use JavaScript because a) it's a very simple language; b) has a lot of good implementations; c) widely used and understood; d) doesn't drag in a huge platform of its own.

np237: Yes, it's a slippery slope which I why I initially did the whole .pkla file format thing because I hoped it would be "good enough". The key here is really to identify that the spectrum of use-cases is so wide that you really end up wanting a programming language instead of a file format. Because if you _don't_ make realizations like this, you'll end up with a Turing-complete language disguised in a file format _anyway_ (just Google for "pam.conf turing complete" if you don't believe me).

What I wanted to say is (shorter because blogspot might eat it again): - Nobody really needs stuff like “forbid users in a group to do the action xyz” or “don't allow xyz on school nights”. If your sysadmin says he needs this, you need to change the sysadmin, not the configuration system. - It is better to use a simple configuration system at least *by default*. - If you really want to allow such stupid things, why not allowing custom configuration through an external program instead of forcing to use a specific API? Simpler design, less lines of code for you. - Sysadmins don’t know a thing about JavaScript. They work on shell, perl, python, sometimes even C, but JS? Nah. - Complexity leads to bugs. In security components, bugs lead to security vulnerabilities. Yes, I read what you wrote and I seriously disagree. - Embedding a JS interpreter? Seriously, regardless of the rest, WTF? Wasn’t the gnome-shell/gnome-games experiments enough to tell this is a bad idea? - Blogspot is really pissing me off.

John: I briefly did consider lua since (since RPM relies on it as well, it can be argued it's not an extra dep on RPM-based OSes) but since I have about 0% experience with it I decided to go for JS instead.

np237: Sorry that Blogspot is making your life miserable - I don't like the commenting system either.

As for "nobody really needs stuff" comment: Oh, I wish that was true (if it was we wouldn't even need polkit at all which would be even better). My experience, however, being on the receiving end of bug reports for a large Linux distributor, clearly indicates otherwise.

There's also prior art in this area - look at other mainstream OSes like OS X and Windows - in particular, look at Group Policy which has an overlap with polkit (GP arguably does a lot more).

As for your claim that sysadmins know zilch about JS, two comments: First, I don't think that's true (I think you are selling them short) ... even if you know only a little shell or perl or python, JS is not a lot different, in fact it's probably even simpler. The other comment is that often rules will be only a couple of lines, on the level of the examples in polkit(8) man page I linked to.

The rest of your comment is mostly the kind of opinionated piece that I had hoped the section "Hmm, OK, but you are bloating Linux anyway. You suck." would avoid. I don't really know how to respond to that.

I have a lot of respect for your coding abilities, but I believe some of the claims that lead to the changes you made might be disputable.

First, once I figured out the pkla format I found it to be entirely reasonable. The reasons that caused me anguish initially were tangential to the file format, though.

1. Difficulty knowing that pklocalauthority(8) was the documentation I should be looking at to adjust the local policy. I can't recall how I got there (I think google), but even looking in polkit(8) again what it says is "See pklocalauthority(8) for information about the Local Authority - the default authority implementation shipped with PolicyKit." I wouldn't know this means "here's how you adjust the local policy". You've addressed this now by putting the authorization rules documentation directly in polkit(8). This change did not require a major change in the configuration system.

2. After getting to pklocalauthority(8), a lot of the documentation is about the unusual directory structure used. There are configuration files under /var and multiple sorted subdirectories in each polkit-1 directory. This seems like unnecessary obfuscation, and you since fixed this with a short section explaining lexical sorting in /etc/polkit-1/rules.d and /usr/share/polkit-1/rules.d. This change did not require a major change in the configuration system.

3. The description of auth_admin and friends is missing from pklocalauthority(8). Again, this is fixed by documenting the configuration system in polkit(8) and doesn't require a major change.

Second, I think your assertion that a programming language is required for your configuration system doesn't match the reality I've seen. I look around /etc and I don't see any other important service that uses a programming language to control it. systemd and udev come to mind. There are quite a few knobs on systemd and they're all managed by a key file just like pkla. I'm not aware than anyone has suggested that a programming language is necessary to achieve the proper policy for systemd or udev, and they likely receive a lot of local tweaking.

Finally, I believe the idea that sysadmins want to be using a programming language for configuration could probably use some backing. My experience is that sysadmins want simple declarative statements such as a key file and do not want to be writing a program to configure their systems. Maybe that's incorrect, but I don't think either of us have actually spent any time looking into it.

Anyway, my point is that this seems like a whole lot of code churn and interface changes for things that were not the problem in the first place. I could certainly be wrong there and will cope with whatever end up on my system, though. Good luck!

I don't have time to loose with the login of this site. I am dominique_71 on the gentoo forum link above, Dominique Michel is my real name.

You can thing I am foolish, but I am not. At my work, it use a mix of windows and unix, and I have no access at all at the administration level. That imply than I am using linux exclusively at home, and that back from my 386 box. My first decent PC was an Amiga 2000, and this box is my reference even today: The OS was simple to use, simple to administrate, very efficient due the proximity of its unique toolkit with the hardware level, very stable for desktop use, very easy to program.

If I understand very well the need of large corporations for tools like *kit in a modern environment, what I just don't get is: Why the linux community can be enough naive to accept stuffs like that, programs needed only by large companies which, because of their complexity and opacity, remove the essence of free software - freedom of choice - to the average joe user!

I just have, like many joe users, better things to do than learning a language like JS, that in order to get permissions to do things to work, things that can work another and much simpler way for me. So, *kit will always be, for me, not only unnecessary, but also something that is unmanageable. So, 2 good reasons why I don't want to install and use it.

It is more: This is sad because a lot of efforts have been spend in order to lower the level entry of GNU/linux for the average joe user for its desktop. A program like *kit is following exactly the inverse path. Which joe user want to learn JS if it is not interested by web development? No one!

The consequence is than a lot of desktop users are running X as root, that just in order to get things like "when I plug-in an USB disk, I want to be able to access it automatically" to work. So, GNU/linux is becoming like windows for many desktop users: unmanageable. And this is exactly the inverse of what GNU/linux is globally expected to become on the long run.

To resume, *kit is making simple things so complex than it remove the essence of free software - freedom of choice - to the average user, and make much easier to sell a support-contract (just ask Microsoft).

For the sys admins, JS is a totally different language to shell, C, awk or anything most admins are used to, making it to require an investment of time that most good admins don't have, over a significant retraining period, and in the meantime you're looking at less reliability, and less security, since you're not really sure what the implications of everything you're doing are.

Thank you ever so much for ripping out the simple configuration language that I was using for a dozen or so .pkla files and replacing it with (nigh-unreadable) JavaScript -- a language most sysadmins are unfamiliar with -- and picking a JavaScript implementation with a wildly unstable ABI and API, and then piling security fixes and API changes on top of that so that everyone is forced to upgrade. This will be *ever* so much fun for everyone.

This was a very, very bad decision. What on earth were you thinking? Every single aspect of this was badly thought out. And I speak as an Emacs user who loves Turing-complete languages in configuration files.