Allowing non-root execution of a jailed application

Allowing non-root execution of a jailed application

Jailed programs can generally be executed by using jexec(8). However, you have to be root in order to do that. In this short article I present an approach on how you can allow a specific set of non-privileged users to execute a particular jailed application.

Motivation

Suppose you have created a jail jailedfoo whose only purpose is to run exactly one specific program foo. You don't want it on your host system, either because it needs an emulation layer (like Linuxulator) or you just don't want to pollute your host-system with lots of dependecies.

Suppose this program is installed inside the jail under /opt/bloatware.com/foo. The executable in that directory is foo-bin.

On the host, you can simply execute it by doing:
Code:
jexec jailedfoo /opt/bloatware.com/foo/foo-bin

You have to be root, however, to do that.

I'll now explain how to make this application accessible by a non-privileged user. Let's call that hypothetical user klaus.

Entering sudo

If klaus is allowed to execute programs using sudo(8), she could do
Code:
sudo jexec jailedfoo /opt/bloatware.com/foo/foo-bin

This is fine, but sudo(8) generally asks for a password. This is very inconvenient, especially if foo is a graphical application that is supposed to be run by a desktop launcher and not by a terminal.

Sure, you could prevent sudo from asking for a password for klaus in general, but you may not want to do that, because then she could also run any privileged programs (even on the host) without ever being asked for a password. If an attacker gets somehow access to klaus' terminal, that would be hazardous.

Command-based sudoer file

The solution is to allow klaus the sudoed execution of one specific command and to disable asking for a password for that specific command only.

What could that command be? Probably not jexec itself, because that would allow klaus to do that trick on any jail, not just jailedfoo.

So, first we create a custom command in the form of a simple shell script. “By abuse of notation”, let's call this script foo and place it under /usr/local/bin on the host.

In our first approach, the script is just the one-liner from above:
Code:
#!/bin/sh

jexec jailedfoo /opt/bloatware.com/foo/foo-bin

Now here comes the trick: We create a sudoer file for that specific script. Call that sudoer file foo (like the command it is refering to) and place it in the directory /usr/local/etc/sudoers.d. Do this as root with the visudo command:
Code:
visudo -f /usr/local/etc/sudoers.d/foo
Let it have content of the following form:
Code:
<user> <host> = (root) NOPASSWD: <cmd>
where <user> is our non-privileged user, namely klaus, <host> is the host we're on (assume that host is called mercury), and <cmd> is the actual command we want to execute; using the full path is important here. So, in our example, the finally content will look like this:
Code:
klaus mercury = (root) NOPASSWD: /usr/local/bin/foo

Once you have saved the file, klaus will be able to do the following without being asked for a password:
Code:
sudo foo

Non-privileged user inside the jail

All fine and good. The only thing to worry about is that foo inside the jail is executed as root. It's still inside the jail, so there is probably no problem with that, but some applications just don't like being run as root.

We'll fix that now.

First, enter the jail (as root) and create a jailed user for klaus:
Code:
jexec jailedfoo /bin/sh
adduser klaus

Now we have to tell our wrapper script on the host, /usr/local/bin/foo, to execute the program as the jailed user klaus, who has to have the same name as the host-klaus. jexec(8) has a special option -U for that, but this won't work, if the jail is an Ubuntu running on Linuxulator, for example. So we will use a more portable solution using su(1). The general syntax will be as follows:
Code:
su -c /opt/bloatware.com/foo/foo-bin - klaus

So we mingle that with our jexec invocation in /usr/local/bin/foo:
Code:
jexec jailedfoo "su -c /opt/bloatware.com/foo/foo-bin - klaus"

The general solution

Now klaus is fine. But what about other users, like marie, for exmple? He might want to run foo as well. Creating another entry in /usr/local/etc/sudoers.d/foo will be easy, but what about the non-privileged execution inside the jail? With our current solution, marie would run foo inside the jail as the user klaus, which is probably not what we want (unless you replace klaus inside the jail by a more general user like foouser, or something like that).

A first idea might be to replace klaus in /usr/local/bin/foo by a simple call to whoami(1). The problem is, however, that the script /usr/local/bin/foo is executed by sudo, so the user returned by whoami will always be root.

Fortunately, sudo is wise enough to provide us with the user-ID of the caller in the form of an environment variable called SUDO_UID. The only thing we have to do is to convert that UID to the actual user name. The program id(1) is the right tool for that job:
Code:
id -n -u <UID>
will give us the user name belonging to <UID>, e.g. klaus or marie.

So, the final version of our wrapper-script /usr/local/bin/foo looks as follows:
Code:
#!/bin/sh

jexec jailedfoo "su -c /opt/bloatware.com/foo/foo-bin - $(id -n -u ${SUDO_UID})"

Now both klaus and marie both can launch foo from the host by doing a sudo foo without being asked for a password.
 
Back
Top