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 authority ended 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:

  1. 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").
  2. 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.
  3. 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].
  4. 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.

Just embed JavaScript

The first requirement clearly indicates that we want some kind of programming language. However, inventing your own programming language is rarely a good idea so I decided to just embed a JavaScript interpreter (specifically SpiderMonkey) and try that out.

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.

25 comments:

  1. 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.

    ReplyDelete
  2. 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...)

    ReplyDelete
  3. 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?

    ReplyDelete
  4. Configuration should never be done with a Turing-complete language. This road might be paved with good intentions, but it leads straight to sendmail.cf hell.

    ReplyDelete
  5. 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).

    ReplyDelete
    Replies
    1. I did and found nothing. Then I asked Bing and it answered: pam.conf is Turing-complete because it loads arbitrary dynamic libraries in configuration files. You are comparing apples to oranges here.

      Delete
  6. FUCK YOU BLOGSPOT for eating my reply.

    See what happens when using crap? It craps.

    ReplyDelete
  7. 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.

    ReplyDelete
  8. Did you consider using lua instead of JS? It is much lighter and designed for embedding.

    ReplyDelete
  9. 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.

    The main consumer of this, obviously, is the enterprise desktop but don't forget things like KIOSK / lock-down (Internet Cafe, appliance / POS / industrial systems.

    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.

    Hope this clarifies. Thanks.

    ReplyDelete
  10. Hi David,

    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!

    ReplyDelete
  11. from man polkit:

    Each function should return a value from polkit.Result

    polkit.Result = {
    NO : "no",
    YES : "yes",
    AUTH_SELF : "auth_self",
    AUTH_SELF_KEEP : "auth_self_keep",
    AUTH_ADMIN : "auth_admin",
    AUTH_ADMIN_KEEP : "auth_admin_keep",
    NOT_HANDLED : null
    };

    In fact polkit.Result.YES returns nothing:

    polkit.addRule(function(action, subject) {
    if (action.id == "org.freedesktop.udisks.filesystem-mount-system-internal" && subject.isInGroup("wheel")) {
    return polkit.Result.YES;
    }
    });

    Got "Not authorized".

    return "yes" does the trick.

    polkit-0.107, gentoo

    ReplyDelete
  12. You should learn and experiment with udev instead of forcing users to use a language, JS, than nobody want to use for system administration.

    http://forums.gentoo.org/viewtopic-t-933724.html and a ton of other similar forum threads elsewhere!

    ReplyDelete
  13. Could you at least provide an easy to set option that will just disable the whole thing, but let the user that doesn't want to use this thing, to install and use programs that depend on it?

    As example, with pulse audio, by editing a single line into one configuration file, pulseaudio will never start, and the audio programs will use ALSA or jack instead.

    ReplyDelete
  14. 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.

    ReplyDelete
  15. "but since I have about 0% experience with it I decided to go for JS instead."

    When the only tool you have is a hammer, every problem looks like a nail.

    JS, seriously? How about learning to use the proper tool?

    I can only see drawbacks of using JS instead of Lua here.

    ReplyDelete
  16. Hi could you help me setup polkit authorization to access libvirt (to do virtmanagement) by local users. In the earlier versions, I was able to achieve by doing below:

    [Remote libvirt SSH access]
    Identity=unix-group:virt
    Action=org.libvirt.unix.manage;org.libvirt.unix.monitor
    ResultAny=yes
    ResultInactive=yes
    ResultActive=yes


    I would like to achieve the same in Fedora 18, i tried using below script

    [root@dhcp201-167 ~]# cat /etc/polkit-1/rules.d/10.virt.rules
    polkit.addAuthorizationRule(function(action, subject) {
    polkit.log("action=" + action);
    polkit.log("subject=" + subject);
    polkit.log("now=" + now)
    var now = new Date();
    polkit.log("now=" + now);
    if (action == "libvirt.unix.manage" || "org.libvirt.unix.monitor" && subject.isInGroup("virt")) {
    return yes;
    }
    return null;
    });

    but doesn't seem to work

    can you post some more example for the above scenario

    Thanks

    ReplyDelete
    Replies
    1. Hi Niranjan,

      I know nothing about polkit but was figuring it out for what you are trying to achieve.

      The polkit.addAuthorizationRule function shouldn't that just be "polkit.addRule"?

      and the "libvirt.unix.manage" shouldn't that be "org.libvirt.unix.manage"?

      I also believe it's action.id not action

      and in your if statement the second part after the OR condition should contain the action.id again

      And you should return a polkit.Result value so not just "yes" but polkit.Result.YES

      So you end up with something like:
      polkit.addRule(function(action, subject) {
      polkit.log("action=" + action);
      polkit.log("subject=" + subject);
      polkit.log("now=" + now)
      var now = new Date();
      polkit.log("now=" + now);
      if (action.id == "org.libvirt.unix.manage" || action.id == "org.libvirt.unix.monitor" &&
      subject.isInGroup("virt")) {
      return polkit.Result.YES;
      }
      }
      );

      Delete
    2. Thanks drecaise , Your solution worked for me and now i am able to access virt-manager using polkit authentication.

      Delete
  17. 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.

    Are you *trying* to force distros to fork PolicyKit?

    ReplyDelete
  18. This comment has been removed by the author.

    ReplyDelete
  19. Can external files be accesed by polkit for dynamic polkit rules please say a method for dynamic polkit rule

    ReplyDelete
  20. This comment has been removed by the author.

    ReplyDelete
  21. I tried to read your common-sense advice and here is what I got: "Usługa Google+ nie jest już dostępna"

    ReplyDelete