Path: blob/main/plugins/python/example_policy_plugin.py
3863 views
import sudo12import errno3import sys4import os5import pwd6import grp7import shutil8910VERSION = 1.0111213class SudoPolicyPlugin(sudo.Plugin):14"""Example sudo policy plugin1516Demonstrates how to use the sudo policy plugin API. All functions are added17as an example on their syntax, but note that most of them are optional18(except check_policy).1920On detailed description of the functions refer to sudo_plugin manual (man21sudo_plugin).2223Most functions can express error or reject through their "int" return value24as documented in the manual. The sudo module also has constants for these:25sudo.RC.ACCEPT / sudo.RC.OK 126sudo.RC.REJECT 027sudo.RC.ERROR -128sudo.RC.USAGE_ERROR -22930If the plugin encounters an error, instead of just returning sudo.RC.ERROR31result code it can also add a message describing the problem.32This can be done by raising the special exception:33raise sudo.PluginError("Message")34This added message will be used by the audit plugins.3536If the function returns "None" (for example does not call return), it will37be considered sudo.RC.OK. If an exception other than sudo.PluginError is38raised, its backtrace will be shown to the user and the plugin function39returns sudo.RC.ERROR. If that is not acceptable, catch it.40"""4142_allowed_commands = ("id", "whoami")43_safe_password = "12345"4445# -- Plugin API functions --4647def __init__(self, user_env: tuple, settings: tuple,48version: str, **kwargs):49"""The constructor matches the C sudo plugin API open() call5051Other variables you can currently use as arguments are:52user_info: tuple53plugin_options: tuple5455For their detailed description, see the open() call of the C plugin API56in the sudo manual ("man sudo").57"""58if not version.startswith("1."):59raise sudo.PluginError(60"This plugin is not compatible with python plugin"61"API version {}".format(version))6263self.user_env = sudo.options_as_dict(user_env)64self.settings = sudo.options_as_dict(settings)6566def check_policy(self, argv: tuple, env_add: tuple):67cmd = argv[0]68# Example for a simple reject:69if not self._is_command_allowed(cmd):70error_msg = "You are not allowed to run this command!"71sudo.log_error(error_msg)72raise sudo.PluginReject(error_msg)7374# The environment the command will be executed with (we allow any here)75user_env_out = sudo.options_from_dict(self.user_env) + env_add7677command_info_out = sudo.options_from_dict({78"command": self._find_on_path(cmd), # Absolute path of command79"runas_uid": self._runas_uid(), # The user id80"runas_gid": self._runas_gid(), # The group id81})8283return (sudo.RC.ACCEPT, command_info_out, argv, user_env_out)8485def init_session(self, user_pwd: tuple, user_env: tuple):86"""Perform session setup8788Beware that user_pwd can be None if user is not present in the password89database. Otherwise it is a tuple convertible to pwd.struct_passwd.90"""91# conversion example:92user_pwd = pwd.struct_passwd(user_pwd) if user_pwd else None9394# This is how you change the user_env:95return (sudo.RC.OK, user_env + ("PLUGIN_EXAMPLE_ENV=1",))9697# If you do not want to change user_env, you can just return (or None):98# return sudo.RC.OK99100def list(self, argv: tuple, is_verbose: int, user: str):101cmd = argv[0] if argv else None102as_user_text = "as user '{}'".format(user) if user else ""103104if cmd:105allowed_text = "" if self._is_command_allowed(cmd) else "NOT "106sudo.log_info("You are {}allowed to execute command '{}'{}"107.format(allowed_text, cmd, as_user_text))108109if not cmd or is_verbose:110sudo.log_info("Only the following commands are allowed:",111", ".join(self._allowed_commands), as_user_text)112113def validate(self):114pass # we have no cache115116def invalidate(self, remove: int):117pass # we have no cache118119def show_version(self, is_verbose: int):120sudo.log_info("Python Example Policy Plugin "121"version: {}".format(VERSION))122if is_verbose:123sudo.log_info("Python interpreter version:", sys.version)124125def close(self, exit_status: int, error: int) -> None:126if error == 0:127sudo.log_info("The command returned with exit_status {}".format(128exit_status))129else:130error_name = errno.errorcode.get(error, "???")131sudo.log_error(132"Failed to execute command, execve syscall returned "133"{} ({})".format(error, error_name))134135# -- Helper functions --136137def _is_command_allowed(self, cmd):138return os.path.basename(cmd) in self._allowed_commands139140def _find_on_path(self, cmd):141if os.path.isabs(cmd):142return cmd143144path = self.user_env.get("PATH", "/usr/bin:/bin")145absolute_cmd = shutil.which(cmd, path=path)146if not absolute_cmd:147raise sudo.PluginError("Can not find cmd '{}' on PATH".format(cmd))148return absolute_cmd149150def _runas_pwd(self):151runas_user = self.settings.get("runas_user") or "root"152try:153return pwd.getpwnam(runas_user)154except KeyError:155raise sudo.PluginError("Could not find user "156"'{}'".format(runas_user))157158def _runas_uid(self):159return self._runas_pwd().pw_uid160161def _runas_gid(self):162runas_group = self.settings.get("runas_group")163if runas_group is None:164return self._runas_pwd().pw_gid165166try:167return grp.getgrnam(runas_group).gr_gid168except KeyError:169raise sudo.PluginError(170"Could not find group '{}'".format(runas_group))171172173