Sudo 1.9: using the new approval API from Python

Version 1.9 of sudo introduced the approval plugin API, making it possible to have extra restrictions before executing a command. These only run after the policy plugin has succeeded, so you can effectively add additional layers of policy without replacing the policy plugin and sudoers. Multiple approval plugins may be defined, and all must succeed in order for the command to be executed.

In this blog you will find a simple Python script utilizing the approval API. It implements a simple check: if the current time is within a certain range the command is allowed. This script is part of the sudo documentation under the name example_approval_plugin.py.

Before you begin

In order to use the sudo Python bindings, you need to have sudo version 1.9 with Python support enabled. This is available, for example, in openSUSE Tumbleweed, although most distributions use an earlier version. For many distros and UNIX variants, there are ready to use packages on the sudo website. For others, you can easily compile it yourself based on information from one of my earlier blogs.

Configuring sudo

Python plugins must be accessible only by root. For simplicity, I have stored it in the home directory of the root user, but with the right amount of chown and chmod magic you can store it anywhere on the file system. Here is the content of /root/example_approval_plugin.py:

import sudo

from datetime import datetime

class BusinessHoursApprovalPlugin(sudo.Plugin):
    def check(self, command_info: tuple, run_argv: tuple,
              run_env: tuple) -> int:
        error_msg = ""
        now = datetime.now()
        if now.weekday() >= 5:
            error_msg = "That is not allowed on the weekend!"
        if now.hour < 8 or now.hour > 17:
            error_msg = "That is not allowed outside the business hours!"

        if error_msg:
            sudo.log_info(error_msg)
            raise sudo.PluginReject(error_msg)

It starts by importing the sudo module. You won’t find this in the file system, it is provided by the sudo Python plugin itself. As this plugin implements date and time checking, we import datetime from the datetime module. Next, we create a class based on the sudo.Plugin class. You can name it whatever you want as long as you use that name in the sudo.conf file.

The approval plugin has a single mandatory method called check(). It receives information from sudo about the environment and the command to be executed and can reject command execution by returning sudo.RC.REJECT or raising a special exception. In this code we do not use any of the information supplied by sudo and use an exception to return an error message to the user.

The method starts by creating an empty error message and creating a variable that holds the current time. It is followed by three checks. The first one checks if the day of the week is on a weekend, and if it is, then the error message is changed to: “That is not allowed on the weekend!”. The second one checks the hour of the day, and changes the error message to “That is not allowed outside the business hours!” if it is too early or too late. The last one checks whether the error message variable has been set. If it is set, an event is logged with the error message, an exception is raised and sudo will refuse to execute the command.

The plugin must be enabled in the sudo.conf file for it to take effect. Assuming that you used the same path and file name as I did, you should append the following lines to sudo.conf:

Plugin python_approval python_plugin.so \
    ModulePath=/root/example_approval_plugin.py \
    ClassName=BusinessHoursApprovalPlugin

Testing

Once sudo has been configured with the new approval plugin, it is possible to test it. As usual, before testing anything with sudo, make sure that you know the root password, otherwise it is easy to lock yourself out of your own system. Choose a user for testing which has the ALL privilege in sudoers to keep things simple.

Assuming that you are testing during regular working hours, even after enabling the Python script in the configuration everything should work normally. Now edit the Python script to make sure that the current time is considered to be outside of normal working hours. Try running sudo again:

[czanik@centos7sudo ~]$ sudo ls /root/
[sudo] password for czanik: 
That is not allowed outside the business hours!
[czanik@centos7sudo ~]$ 

When you check your logs, you will see the above error message there too:

Aug  4 14:56:39 centos7sudo sudo[13358]:   czanik : That is not allowed outside the business hours! ; TTY=pts/1 ; PWD=/home/czanik ; USER=root ; TSID=00000R ; COMMAND=/bin/ls /root/

Don’t forget to disable the approval plugin in sudo.conf after you are done testing or you may get a surprise the next time you try to run sudo!

What is next?

Based on the feedback I’ve gotten from demos in my sudo talks, I cannot emphasize enough that this is just an introductory example and not production-ready code. For example, some banking holidays are on workdays and in my country, Hungary, Saturday is sometimes a work day. But based on this example you can get started experimenting with the approval Plugin API. The possibilities are endless: you can, for example, connect sudo to an issue tracking system and approve a session only if the ticket number supplied by the user is both valid and open. Or check an HR database to verify that the given admin is on duty. And so on.

If you would like to be notified about new posts and sudo news, sign up for the sudo blog announcement mailing list.