Path: blob/main/plugins/python/example_policy_plugin.py
1532 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):70sudo.log_error("You are not allowed to run this command!")71return sudo.RC.REJECT7273raise sudo.PluginError("You are not allowed to run this command!")7475# The environment the command will be executed with (we allow any here)76user_env_out = sudo.options_from_dict(self.user_env) + env_add7778command_info_out = sudo.options_from_dict({79"command": self._find_on_path(cmd), # Absolute path of command80"runas_uid": self._runas_uid(), # The user id81"runas_gid": self._runas_gid(), # The group id82})8384return (sudo.RC.ACCEPT, command_info_out, argv, user_env_out)8586def init_session(self, user_pwd: tuple, user_env: tuple):87"""Perform session setup8889Beware that user_pwd can be None if user is not present in the password90database. Otherwise it is a tuple convertible to pwd.struct_passwd.91"""92# conversion example:93user_pwd = pwd.struct_passwd(user_pwd) if user_pwd else None9495# This is how you change the user_env:96return (sudo.RC.OK, user_env + ("PLUGIN_EXAMPLE_ENV=1",))9798# If you do not want to change user_env, you can just return (or None):99# return sudo.RC.OK100101def list(self, argv: tuple, is_verbose: int, user: str):102cmd = argv[0] if argv else None103as_user_text = "as user '{}'".format(user) if user else ""104105if cmd:106allowed_text = "" if self._is_command_allowed(cmd) else "NOT "107sudo.log_info("You are {}allowed to execute command '{}'{}"108.format(allowed_text, cmd, as_user_text))109110if not cmd or is_verbose:111sudo.log_info("Only the following commands are allowed:",112", ".join(self._allowed_commands), as_user_text)113114def validate(self):115pass # we have no cache116117def invalidate(self, remove: int):118pass # we have no cache119120def show_version(self, is_verbose: int):121sudo.log_info("Python Example Policy Plugin "122"version: {}".format(VERSION))123if is_verbose:124sudo.log_info("Python interpreter version:", sys.version)125126def close(self, exit_status: int, error: int) -> None:127if error == 0:128sudo.log_info("The command returned with exit_status {}".format(129exit_status))130else:131error_name = errno.errorcode.get(error, "???")132sudo.log_error(133"Failed to execute command, execve syscall returned "134"{} ({})".format(error, error_name))135136# -- Helper functions --137138def _is_command_allowed(self, cmd):139return os.path.basename(cmd) in self._allowed_commands140141def _find_on_path(self, cmd):142if os.path.isabs(cmd):143return cmd144145path = self.user_env.get("PATH", "/usr/bin:/bin")146absolute_cmd = shutil.which(cmd, path=path)147if not absolute_cmd:148raise sudo.PluginError("Can not find cmd '{}' on PATH".format(cmd))149return absolute_cmd150151def _runas_pwd(self):152runas_user = self.settings.get("runas_user") or "root"153try:154return pwd.getpwnam(runas_user)155except KeyError:156raise sudo.PluginError("Could not find user "157"'{}'".format(runas_user))158159def _runas_uid(self):160return self._runas_pwd().pw_uid161162def _runas_gid(self):163runas_group = self.settings.get("runas_group")164if runas_group is None:165return self._runas_pwd().pw_gid166167try:168return grp.getgrnam(runas_group).gr_gid169except KeyError:170raise sudo.PluginError(171"Could not find group '{}'".format(runas_group))172173174