Using PolicyKit to allow non-root users to start and stop a service - systemd

I have a requirement to allow non-root users to start and stop a service. It was recommended to me to use PolicyKit rather than sudoers.d, which I am familiar with.
As I have no experience with PolicyKit, I thought I would experiment and create a rule to allow non-root users to start and stop the Docker service. I have created a file, /etc/polkit-1/rules.d/10-docker.rules containing:
polkit.addRule(function(action, subject) {
if (action.id == "org.freedesktop.systemd1.manage-units" &&
action.lookup("unit") == "docker.service")
{
return polkit.Result.YES;
}
})
However, whenever I execute systemctl start|stop|restart docker.service, I keep getting prompted for a password. What am I missing?
Also, I would like to limit non-root users to control this service who are in a specific group e.g. blah. How do I incorporate this into my rule?
My target OS is RHEL 7.7.

On CentOS7, action does not have access to the unit information. This was introduced on a later systemd version, v226.
https://github.com/systemd/systemd/commit/88ced61bf9673407f4b15bf51b1b408fd78c149d
I was also hit by this. You will need to allow the user to manage all units or go back to the stone age of having shell scripts on sudoers.
Also, I would like to limit non-root users to control this service who are in a specific group e.g. blah. How do I incorporate this into my rule?
Use subject.isInGroup("group").
See:
https://wiki.archlinux.org/index.php/Polkit#Authorization_rules
https://www.freedesktop.org/software/polkit/docs/latest/polkit.8.html

/etc/polkit-1/rules.d/10-docker.rules
polkit.addRule(function(action, subject) {
if (action.id == "org.freedesktop.systemd1.manage-units" &&
action.lookup("unit") == "docker.service") &&
subject.isInGroup("mygroup"))
{
return polkit.Result.YES;
}
})
Also you need to run visudo and add these lines
%mygroup ALL= NOPASSWD: /usr/bin/systemctl restart docker.service
%mygroup ALL= NOPASSWD: /usr/bin/systemctl stop docker.service
%mygroup ALL= NOPASSWD: /usr/bin/systemctl start docker.service
%mygroup ALL= NOPASSWD: /usr/bin/systemctl status docker.servicee
Tutorial: https://keshavarzreza.ir/create-linux-systemd-service-runable-for-non-root-users/

As pointed out by Simão, the unit information is not supplied to polkit by systemd on RHEL 7. One way around the problem is to use pkexec to wrap the call to systemctl. You would need a wrapper script for your specific service, then have the rules apply to pkexec. The users would execute the command
pkexec /path/to/script
and the polkit rule would look something like this:
polkit.addRule(
function(action,subject)
{
if ( (action.id == "org.freedesktop.policykit.exec") &&
(action.lookup("user") == "root") &&
(action.lookup("program") == "/path/to/script") &&
(subject.isInGroup("someGroup") ) )
return polkit.Result.YES;
return polkit.Result.NOT_HANDLED;
}
);
In a practical sense, this just re-creates sudo and scripts using the polkit framework. Whether this is "better" than using sudo is a value judgement I'll leave to others.

Related

Check if possible to run command as sudo in Bourne shell?

I'm writing a Bourne shell deployment script, which runs some commands as root and some as the current user. I want to not run all commands as root, and check upfront if the commands I'll need are available to root (to prevent aborted half-done deployments).
In order to do this, I want to make a function that checks if a command can be run as root. My idea was to do this:
sudo_command() {
sudo sh -c 'type "$1"'
}
And then to use it like so:
required_sudo_commands="cp rm apt"
for command in $required_sudo_commands do
sudo_command "$command" || (
echo "missing required command: $command;
exit 1;
)
done
As you might guess by my question here: it doesn't work. Does any of you see what I'm doing wrong here?
I tried running the command inside sudo_command by itself, but that miraculously (to me) did work. But when I put the command into a separate file, it didn't work.
There are two immediate problems:
The $1 not expanding in single quotes.
You can semi-fix this by expanding it in double quotes instead: sudo sh -c "type '$1'"
Your command not exiting. That's easily fixed by replacing your || (..) with || {..}.
(..) creates a subshell that limits the scope of everything inside it including exit. To group commands, use {..}
However, there is also the fundamental problem of trying to use sh -c 'type "$1" to do anything.
One of the major points of sudo is the ability to limit what a user can and can't do. You're assuming that a user has complete, unrestricted access to run arbitrary commands as root, and that any problems are due to root not having these commands available.
That may be a valid assumption for you, but you may want to instead run e.g. sudo apt --version to get a better (but still incomplete) picture of whether you're allowed and able to run apt with sudo without requiring complete and unrestricted access.

Invoking a process whenever someone logins through SSH

I'm trying to invoke a ruby script whenever anyone tries to SSH into the server(say Ubuntu 14.04).
Like for example someone is trying to login with ssh root#serverip and my ruby script should be called upon and the login process should continue only if the script returns true and if returns false the connection should be closed.
How can I achieve this?
Thanks.
To fulfill your requirements, you should also work with $SSH_ORIGINAL_COMMAND environment variable, if you use ForceCommand option:
ForceCommand /path/to/script && $SSH_ORIGINAL_COMMAND
This will run your script and based on the exit status of the /path/to/script, it will run the user command or exit session.
You can try using the ForceCommand parameter in the sshd_config file of your server. it's fairly straightforward and well documented online :)
You enter at the bottom of the config file:
ForceCommand /path/to/script
Not so sure if you can check for certain conditions and terminate the connection, however this might guide you.
As mentioned by a friend in an answer, exit code based execution didn't work for me.
Here's a workaround :-
As mentioned by some folks, I used this in /etc/ssh/sshd_config
ForceCommand /path/to/script
Then in the script I opened the bash if they satisfied certain conditions. For example in bash
#!/bin/bash
if [ condition ]
then
bash #This will open bash if condition is satisfied.
//login
fi
Thanks everyone!

Starting service as non root user

Can someone help us understand how to properly start our programs service as the services user (marty for example).
We're using init.d to start our process (java application), but when the system(s) boot (Ubuntu and Debian) because the service script is run as root, we're having problems where the application is starting as root too and the PID file is being created by root which is messing things up.
We tried using sudo, but this is not a great solution as we dont want the sudo process running too with our application as a child process plus we need this to work on other systems that may not have sudo. Please help.
In the init script, you can check the $UID of the calling user.
If it is root, you can run the service with "runuser". If it is marty - run it directly, if it is another user - exit with error for example.
Here's some example bash (untested):
start() {
if [ $UID -eq 0 ]; then
runuser -s /bin/bash marty -c "$DAEMON start $DAEMONOPTS"
elif [ "$USER" = "marty" ]; then
$DAEMON start $DAEMONOPTS
else
print "Please run me with root or marty."
exit 2
fi
}
Same for stop and any other functions as required.
Feel free to modify the runuser command as necessary, maybe you won't need the shell for example.
Use start-stop-daemon which accepts a user name and an executable as parameters.

Call a function with root's permission in ruby?

I know in shell script I can do this:
#!/bin/sh
foo() {
rm -rf /
}
foo # fail
sudo foo # succeed
For implementing this in ruby, I now use a individual script file to store those operations that need root privileges, and then call in the main script like system(['sudo', 'ruby', 'sudo_operations.rb', 'do_rm_rf_root']).
It would be much better if I can directly invoke the function without separating it out. For example, I wonder something like this:
def sudo(&method_needs_root_privilege)
# ...
end
Then I can use that like:
sudo do
puts ENV['UID'] # print 0
system('rm -rf /') # successfully executed
end
Is there any gems that helps or any idea for implementing this?
Thanks in advance.
No. UID/GID only exist at the process level; functions cannot run as a different user (e.g, root) from the rest of a process.
While it is possible for a process to change its uid (using the set*id family of system calls), a process must already be running as root to do so.

How to code elasticsearch status checks in ruby with Chef?

I want to accomplish two things:
1) clean out any pointless pid files (if elasticsearch is not running) and then start it, and
2) check that ES has started up before proceeding
Now between what Chef offers out-of-box and what Ruby allows, I can only figure out a pseudo-code like syntax for making it happen but its not going to run so I need some help/advice writing the real thing.
Pseudo-Code For (1):
bash "start it up" do
user "root"
only_if { # pretty sure this syntax is all incorrect, any ideas to make it happen?
(sudo service elasticsearch status).match(/^elasticsearch not running/)
}
code <<-EOS
sudo rm -rf /usr/local/var/run/elasticsearch/*.pid
sudo service elasticsearch restart
EOS
end
Pseudo-Code For (2):
bash "wait for it to start up" do
user "root"
only_if { # pretty sure this syntax is all incorrect, any ideas to make it happen?
(sudo service elasticsearch status).match(/^elasticsearch running with PID/)
}
retries 20
retry_delay 5
code <<-EOS
echo "I can now go on with my life..."
EOS
end
If you wish to ensure a certain particular status before continuing, insert this in a recipe (this is an example and not tested):
service "elasticsearch" do
action [ :enable, :start ]
status_command "/usr/sbin/service elasticsearch status | grep 'running with PID'"
end
It's the job of the init script's start command to wait for the service to be actually started.
Chef docs says:
There is no reason to use the execute resource to control a service because the service resource exposes the start_command attribute directly, which gives a recipe full control over the command issued in a much cleaner, more direct manner.

Resources