Monday, January 9, 2006

New year and Policy Daemons

New Year, Same Old


It’s a new year and I returned late last week from Europe - nice with relaxation but maybe a bit too much travelling… Anyway, pictures speak much more than a thousand words, or something, so nuff’ said. I’m not sure I like 2006 yet, maybe things will change.



From lunch of the day of christmas eve
Danish food


For new years I went to Hamburg, Germany to visit my good friend Kay Sievers - awesome to see Kay again. With Kay working for Novell and me working for Red Hat we talked a lot about the current state of our various distributions and how we can work closer together in the trenches. Specifically we discussed how to manage policy enforcing daemons and below are some of my thoughts on that.


On Enforcing Policy


Background


Starting with Fedora Core 1, Red Hat started shipping the system-wide bus (D-BUS) in Fedora Core, but it wasn’t until Fedora Core 3 with the advent of HAL and NetworkManager that the bus started to see extensive use.


Today, in what will become Fedora Core 5, unpriviliged applications running in a desktop session on the console can invoke methods for putting the system to sleep, configure display brightness on laptops, configure networking and mounting storage devices. With time, more and more methods will be available for configuring the hardware and/or system. All such method invocation happens in a secure way through the (unprivileged) system bus daemon that checks, validates and passes messages between the sender and receiver. Additional lock-down via e.g. SELinux security contexts are possible too but I’ve yet not seen anything use it.


Configuration


Back in the day, the traditional UNIX way for system-wide daemons prescribed using a textual configuration file somewhere in /etc - acpid and networking scripts comes to mind. With this, users had to resort to using a text editor and learn about the configuration file format; very educating and l33t for the geek hacker, but not ideal for ordinary users; not ideal for grandma. When available, UI tools simply rewrote the configuration file which most people agree is error prone and just plain wrong.


Allowing applications in the desktop session to enforce policy makes a lot of sense from a configuration point of view. Today, in the development tree of Fedora we now ship gnome-power-manager. Settings are read from gconf at /apps/gnome-power-manager. We have a nice, not horrible at least, user interface (Desktop -> Preferences -> Power Management) for configuring the policy. It is per-user, and since it uses gconf it’s possible to configure both default and mandatory settings (for lock-down), these days this can even nicely be achieved with e.g. Sabayon. In almost every way possible way it’s superior to textual configuration files. John has some good stuff about gconf here.


Also, gnome-power-manager sports a notification icon along with a menu for performing actions such as putting the system to sleep or hibernation. These days, when the kernel is in a good mood, power management just works. It’s like using my Mac.


However, there are two obvious downsides to running policy enforcing daemons in the desktop session, namely




  1. when no user is logged in, no copy of gnome-power-manager is ever running; hence no policy is maintained; hence your laptop won’t suspend after e.g. 15 minutes of inactivity when at the login screen.


  2. when multiple users are logged in (either via fast-user-switching or via multiple physical consoles) multiple copies of gnome-power-manager are running. This is bad as e.g. inactivity on one console might trigger e.g. a system suspend.


Exactly the same problems apply for the screen saver (gnome-screen-saver), networking (nm-applet), storage management (gnome-volume-manager) and probably other things too. Wonder why there is no screen saver on the login screen? Wonder why your wireless network isn’t configured before login? That’s why.



Opera house and the Canal
The Opera House in Copenhagen, Denmark


A framework


To solve problems 1) and 2) above we first need some infrastructure for tracking what sessions are at the consoles attached to the system. Remember that several users can be logged in at the same console through fast user switching (even multiple sessions from the same user on one console) so we need a notion of who and what is active at each console.


The proposal is a simple D-BUS service on the system bus; in quick and dirty pseudo code it would look like this



service org.freedesktop.DBus.ConsoleTracker {
methods:
array{tupple{string sessionId,int console, bool isActive}} GetSessions();
int GetNumberOfConsoles();
int GetUnixUidFromSessionID(string sessionId);
signals:
SessionBegin(string sessionId, int console);
SessionEnd(string sessionId, int console);
SessionActivityChanged(string sessionId, int console, bool isActive);
NumberOfConsolesChanged(int numConsoles);
}

The sessionId is simply a unique identifier maintained by the ConsoleTracker service - most likely this is the X11 display number though ConsoleTracker shouldn’t rely on a certain windowing system. The isActive boolean denotes where the session is currently visible on the console. Along the way the ConsoleTracker service may also provide methods such as GetSessionBusAddress(string sessionId), it might provide properties to tell what kind of session is running (GNOME, KDE, …) but that is beyond the scope of this write-up.


A service like this (I’m sure the choice of names and methods / signals can be much improved) simply tracks sessions and, as such users logging in and out. Ideally this service is shipped with the D-BUS tarball and ideally we’d make the system bus dependent on this service insofar that we can introduce policy directives such as “at_active_console” etc. in addition to the “at_console” directive we already have.


The ConsoleTracker service should also be possible to implement without pam_console which means that we can get rid of the optional pam_console dependency in D-BUS. This also means that e.g. HAL and NetworkManager can ship D-BUS configuration files with “at_console” without the Debian people having to patch things out and only users logged in at the console may invoke methods like e.g. Suspend() to put the system to sleep. Of course, the Debian people may still change the policy because they think group membership is more correct. It’s their OS distribution after all.



Sketchy Rupert
Rupert spotted in Germany


Now, with the ability to track logged in users and sessions, it becomes almost trivial to write the next system-wide service, let’s call it org.freedesktop.PolicyManager. This is a daemon that reads configuration files in /etc/PolicyManager.d/ (only programs will dump files here, not people)

/etc/PolicyManager.d/power-management.conf
storage-management.conf
...


where e.g. power-management.conf contains

[policy-service]
exec=/usr/bin/my-power-manager-when-no-one-is-logged-in
id=PowerManagement
per_console=false


which simply states that the PolicyManager daemon should run the program /usr/bin/my-power-manager-when-no-one-is-logged-in when no user is logged in (the console number is passed in the environment). This configuration file also provides a unique identifier for the kind of policy service it is, in this case power management. Part of the “specification” for the PolicyManager daemon contains a list of well-defined policy services, e.g. PowerManagement, StorageManagement, ScreenSaver, NetworkManagement and so forth.



Conny the killer pony!
My sister has a Pony called Conny


The per_console field requires further discussion, but let us first take a look at the D-BUS interface offered by the PolicyManager daemon:



service org.freedesktop.PolicyManager {
methods:
bool ClaimPolicyService(string id);
bool ReleasePolicyService(string id);
signal:
PolicyServiceClaimed(string id, string sessionId, int console);
PolicyServiceReleased(string id, string sessionId, int console);
}


The thinking here is that when I login to the GNOME desktop, then gnome-power-manager is started and it invokes ClaimPolicyService("PowerManagement") on the PolicyManager system service. Since D-BUS now sports the ConsoleTracker service, the PolicyManager can determine what console, if any, the method call from the gnome-power-manager instance stems from. Specifically, if the method invocation stems from a process not at the console it is denied.


The PolicyManager service now kills the existing process responsible for power management, e.g. /usr/bin/my-power-manager-when-no-one-is-logged-in, and returns TRUE which means that our copy of gnome-power-manager is set to go. This daemon starts enforcing policy, e.g. suspends my laptop after inactivity and so forth.


Whenever my copy of gnome-power-manager terminates (normally when logging out), then the PolicyManager is notified through a signal on the system message bus (already part of the system bus) and starts up /usr/bin/my-power-manager-when-no-one-is-logged-in to enforce policy. Also, at any point my gnome-power-manager copy may invoke ReleasePolicyService("PowerManagement") if the user e.g. manually disables it (though it is unlikely why g-p-m would provide such functionality).


Suppose that user A is logged in at console 0, it’s session "s100", and has a copy of gnome-power-manager runnning. Now suppose that user B logs in via fast user switching at console 0, session "s101". The following happens.


First, the ConsoleTracker service emits a signal telling that session "s100" is no longer active, SessionActivityChanged("s100", 0, FALSE), and the gnome-power-manager copy in session "s100" receives this signal and stops enforcing policy (it doesn’t invoke ReleasePolicyService() though). Now another copy of gnome-power-manager for user B starts up in session "s101". It also invokes ClaimPolicyService("PowerManagement") on PolicyManager and it recieves TRUE and this copy starts enforcing policy.


When switching back to session "s100", the g-p-m copy in session "s101" stops enforcing policy, due to SessionActivityChanged("s101", 0, FALSE), and the g-p-m copy for session "s100" starts enforcing policy in response to the SessionActivityChanged("s100", 0, TRUE) signal.



Dark skies and white snow
Frightening Sky


The per_console field, which is FALSE in the power management example, defines whether the policy enforcing daemon is per console or per system.


For example, consider a system with two consoles, 0 and 1. Since per_console is FALSE for power management only one copy of /usr/bin/my-power-manager-when-no-one-is-logged-in is launched. When there are two user sessions, one on each console, only one of them get to enforce policy, e.g. ClaimPolicyService("PowerManagement") will fail for one of the two copies of gnome-power-manager. The loser will probably revert to only showing battery information, not enforcing policy, and it is up to the two copies to negotiate when to suspend the system. This may be done in a number of ways.


So when is per_console=TRUE useful? It’s useful for policy enforcing pieces like the screen saver or the storage manager. In general, it’s useful when you want a copy running for each console. For example, when PolicyManager starts up on a system with two consoles, it will launch two copies of the /usr/bin/my-screen-saver-when-no-one-is-logged-in (how it gets access to the X display is another matter though). Specifically, for storage management, e.g. gnome-volume-manager, one also wants to ensure that policy is only enforced for drives local to the console, e.g. only mount USB storage for the USB ports local to the console. How this is determined is beyond the scope of this write-up for ConsoleTracker and PolicyManager but it’s not difficult to imagine that this information could come from HAL.


Reusing desktop policy daemons for system use


One problem remains to be solved. How do you configure the policy for the policy daemon when no user is logged in? Also, why would you want to maintain two code bases; one for desktop case and one for the case when no use is logged in? The answer here is simply to reuse the desktop policy daemon. For instance, for power management we get



[policy-service]
exec=/usr/bin/gnome-power-manager --no-one-is-logged-in
id=PowerManagement
per_console=false


and gnome-power-manager simply don’t provide any UI (or maybe it paints the UI on top of the login screen, e.g. gdm). Since this copy of gnome-power-manager is invoked by the PolicyManager daemon it runs as nobody or some other unprivileged system user. It still reads it’s setting from gconf but since this is not a real user it will get the default settings.


With this approach the UI for gnome-power-manager may include a button “Set as system default” that simply copies the settings from the logged in users gconf schema to the system default ones in /etc/gconf/gconf.xml.defaults. This of course requires administrative privileges and we may even hide this button if we can detect whether the user is an admin or not. Notably this will also work for other policy enforcing daemons, e.g. NetworkManager’s nm-applet, the GNOME screen saver and so forth.



Confetti is everywhere
Confetti is Everywhere


Not tied to one desktop


I’ve used GNOME as an example above, mostly because I’m a GNOME user and developer myself, but nothing prevents KDE or XFCE to do the same. Indeed, if there is agreement on all desktops using the ConsoleTracker and PolicyManager things will “just work”. Of course, someone will have to write this stuff :-) , I’m just rambling about one possible framework.