Sudo 1.9: using the group plugin from Python

Using the sudo group plugin, you can connect sudo to external systems and approve commands based on non-UNIX groups. For example, Authentication Services by One Identity uses this solution. Starting with version sudo 1.9, you can also write group plugins in Python. You can use this to check databases or APIs if the admin trying to run a command is a member of a group. This way you can check, for example, if an admin is on duty.

In this blog, I will show you a simple example, just to get you started with the plugin. The groups are hard-coded in the Python code, and no password is required if the user is part of the group. You can normally solve this problem without using the Python plugin. The example below will help you understand and spot the difference in syntax.

Here is the configuration without the group plugin:

%wheel ALL=(ALL) NOPASSWD: ALL

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

First of all, you need a Python file that implements the group plugin. This one is very simple, it just checks a user name against a few hard-coded groups. If there is a match, it authorizes the command, otherwise it rejects it.

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/group.py:

import sudo

class SudoGroupPlugin(sudo.Plugin):
    def query(self, user: str, group: str, user_pwd):
        hardcoded_user_groups = {
            "testgroup": [ "testuser1", "testuser2" ],
            "mygroup": [ "czanik" ]
        }
        group_has_user = user in hardcoded_user_groups.get(group, [])
        return sudo.RC.ACCEPT if group_has_user else sudo.RC.REJECT

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. 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 sudoers file. There is one mandatory method, called query. It receives a few parameters from sudo, including the user and group name of the user trying to run sudo.

Here comes the interesting part. In a “real” group plugin this would be the part which queries a database or API about additional information. To keep the example simple here we use some hard-coded values, where the group “mygroup” contains a single entry for my user name, “czanik”. Replace it with your own. The next line checks if the user name is included in any of the hard-coded groups.

On the final line, the plugin returns its decision to sudo: ACCEPT if the user is member of a group, else REJECT.

While previous Python plugins had to be configured in /etc/sudo.conf, the group plugin is configured in the sudoers file itself. If you stored the file with the name and location I described earlier, you can add the following lines to sudoers. Otherwise, modify the example accordingly.

Defaults group_plugin="python_plugin.so \
    ModulePath=/root/group.py \
    ClassName=SudoGroupPlugin"

%:mygroup ALL=(ALL) NOPASSWD: ALL

Before testing the example, can you spot a minor but important difference compared to the rule in the introduction? There is an extra “:” before the group name, which indicates that this is a non-UNIX group.

Testing

You can easily test the above configuration by logging in as the user hard-coded in the Python code. It should look similar to this:

[czanik@centos7sudo ~]$ sudo -s
[root@centos7sudo czanik]#

Logging in as another user, sudo asks for a password:

[smith@centos7sudo ~]$ sudo -s
[sudo] password for smith:
[root@centos7sudo smith]#

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, not production-ready code. But once you’ve tested it in your environment, you can extend it with your own code to do some real checks.

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