Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/plugins/python/example_policy_plugin.py
3863 views
1
import sudo
2
3
import errno
4
import sys
5
import os
6
import pwd
7
import grp
8
import shutil
9
10
11
VERSION = 1.0
12
13
14
class SudoPolicyPlugin(sudo.Plugin):
15
"""Example sudo policy plugin
16
17
Demonstrates how to use the sudo policy plugin API. All functions are added
18
as an example on their syntax, but note that most of them are optional
19
(except check_policy).
20
21
On detailed description of the functions refer to sudo_plugin manual (man
22
sudo_plugin).
23
24
Most functions can express error or reject through their "int" return value
25
as documented in the manual. The sudo module also has constants for these:
26
sudo.RC.ACCEPT / sudo.RC.OK 1
27
sudo.RC.REJECT 0
28
sudo.RC.ERROR -1
29
sudo.RC.USAGE_ERROR -2
30
31
If the plugin encounters an error, instead of just returning sudo.RC.ERROR
32
result code it can also add a message describing the problem.
33
This can be done by raising the special exception:
34
raise sudo.PluginError("Message")
35
This added message will be used by the audit plugins.
36
37
If the function returns "None" (for example does not call return), it will
38
be considered sudo.RC.OK. If an exception other than sudo.PluginError is
39
raised, its backtrace will be shown to the user and the plugin function
40
returns sudo.RC.ERROR. If that is not acceptable, catch it.
41
"""
42
43
_allowed_commands = ("id", "whoami")
44
_safe_password = "12345"
45
46
# -- Plugin API functions --
47
48
def __init__(self, user_env: tuple, settings: tuple,
49
version: str, **kwargs):
50
"""The constructor matches the C sudo plugin API open() call
51
52
Other variables you can currently use as arguments are:
53
user_info: tuple
54
plugin_options: tuple
55
56
For their detailed description, see the open() call of the C plugin API
57
in the sudo manual ("man sudo").
58
"""
59
if not version.startswith("1."):
60
raise sudo.PluginError(
61
"This plugin is not compatible with python plugin"
62
"API version {}".format(version))
63
64
self.user_env = sudo.options_as_dict(user_env)
65
self.settings = sudo.options_as_dict(settings)
66
67
def check_policy(self, argv: tuple, env_add: tuple):
68
cmd = argv[0]
69
# Example for a simple reject:
70
if not self._is_command_allowed(cmd):
71
error_msg = "You are not allowed to run this command!"
72
sudo.log_error(error_msg)
73
raise sudo.PluginReject(error_msg)
74
75
# The environment the command will be executed with (we allow any here)
76
user_env_out = sudo.options_from_dict(self.user_env) + env_add
77
78
command_info_out = sudo.options_from_dict({
79
"command": self._find_on_path(cmd), # Absolute path of command
80
"runas_uid": self._runas_uid(), # The user id
81
"runas_gid": self._runas_gid(), # The group id
82
})
83
84
return (sudo.RC.ACCEPT, command_info_out, argv, user_env_out)
85
86
def init_session(self, user_pwd: tuple, user_env: tuple):
87
"""Perform session setup
88
89
Beware that user_pwd can be None if user is not present in the password
90
database. Otherwise it is a tuple convertible to pwd.struct_passwd.
91
"""
92
# conversion example:
93
user_pwd = pwd.struct_passwd(user_pwd) if user_pwd else None
94
95
# This is how you change the user_env:
96
return (sudo.RC.OK, user_env + ("PLUGIN_EXAMPLE_ENV=1",))
97
98
# If you do not want to change user_env, you can just return (or None):
99
# return sudo.RC.OK
100
101
def list(self, argv: tuple, is_verbose: int, user: str):
102
cmd = argv[0] if argv else None
103
as_user_text = "as user '{}'".format(user) if user else ""
104
105
if cmd:
106
allowed_text = "" if self._is_command_allowed(cmd) else "NOT "
107
sudo.log_info("You are {}allowed to execute command '{}'{}"
108
.format(allowed_text, cmd, as_user_text))
109
110
if not cmd or is_verbose:
111
sudo.log_info("Only the following commands are allowed:",
112
", ".join(self._allowed_commands), as_user_text)
113
114
def validate(self):
115
pass # we have no cache
116
117
def invalidate(self, remove: int):
118
pass # we have no cache
119
120
def show_version(self, is_verbose: int):
121
sudo.log_info("Python Example Policy Plugin "
122
"version: {}".format(VERSION))
123
if is_verbose:
124
sudo.log_info("Python interpreter version:", sys.version)
125
126
def close(self, exit_status: int, error: int) -> None:
127
if error == 0:
128
sudo.log_info("The command returned with exit_status {}".format(
129
exit_status))
130
else:
131
error_name = errno.errorcode.get(error, "???")
132
sudo.log_error(
133
"Failed to execute command, execve syscall returned "
134
"{} ({})".format(error, error_name))
135
136
# -- Helper functions --
137
138
def _is_command_allowed(self, cmd):
139
return os.path.basename(cmd) in self._allowed_commands
140
141
def _find_on_path(self, cmd):
142
if os.path.isabs(cmd):
143
return cmd
144
145
path = self.user_env.get("PATH", "/usr/bin:/bin")
146
absolute_cmd = shutil.which(cmd, path=path)
147
if not absolute_cmd:
148
raise sudo.PluginError("Can not find cmd '{}' on PATH".format(cmd))
149
return absolute_cmd
150
151
def _runas_pwd(self):
152
runas_user = self.settings.get("runas_user") or "root"
153
try:
154
return pwd.getpwnam(runas_user)
155
except KeyError:
156
raise sudo.PluginError("Could not find user "
157
"'{}'".format(runas_user))
158
159
def _runas_uid(self):
160
return self._runas_pwd().pw_uid
161
162
def _runas_gid(self):
163
runas_group = self.settings.get("runas_group")
164
if runas_group is None:
165
return self._runas_pwd().pw_gid
166
167
try:
168
return grp.getgrnam(runas_group).gr_gid
169
except KeyError:
170
raise sudo.PluginError(
171
"Could not find group '{}'".format(runas_group))
172
173