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
1532 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
sudo.log_error("You are not allowed to run this command!")
72
return sudo.RC.REJECT
73
74
raise sudo.PluginError("You are not allowed to run this command!")
75
76
# The environment the command will be executed with (we allow any here)
77
user_env_out = sudo.options_from_dict(self.user_env) + env_add
78
79
command_info_out = sudo.options_from_dict({
80
"command": self._find_on_path(cmd), # Absolute path of command
81
"runas_uid": self._runas_uid(), # The user id
82
"runas_gid": self._runas_gid(), # The group id
83
})
84
85
return (sudo.RC.ACCEPT, command_info_out, argv, user_env_out)
86
87
def init_session(self, user_pwd: tuple, user_env: tuple):
88
"""Perform session setup
89
90
Beware that user_pwd can be None if user is not present in the password
91
database. Otherwise it is a tuple convertible to pwd.struct_passwd.
92
"""
93
# conversion example:
94
user_pwd = pwd.struct_passwd(user_pwd) if user_pwd else None
95
96
# This is how you change the user_env:
97
return (sudo.RC.OK, user_env + ("PLUGIN_EXAMPLE_ENV=1",))
98
99
# If you do not want to change user_env, you can just return (or None):
100
# return sudo.RC.OK
101
102
def list(self, argv: tuple, is_verbose: int, user: str):
103
cmd = argv[0] if argv else None
104
as_user_text = "as user '{}'".format(user) if user else ""
105
106
if cmd:
107
allowed_text = "" if self._is_command_allowed(cmd) else "NOT "
108
sudo.log_info("You are {}allowed to execute command '{}'{}"
109
.format(allowed_text, cmd, as_user_text))
110
111
if not cmd or is_verbose:
112
sudo.log_info("Only the following commands are allowed:",
113
", ".join(self._allowed_commands), as_user_text)
114
115
def validate(self):
116
pass # we have no cache
117
118
def invalidate(self, remove: int):
119
pass # we have no cache
120
121
def show_version(self, is_verbose: int):
122
sudo.log_info("Python Example Policy Plugin "
123
"version: {}".format(VERSION))
124
if is_verbose:
125
sudo.log_info("Python interpreter version:", sys.version)
126
127
def close(self, exit_status: int, error: int) -> None:
128
if error == 0:
129
sudo.log_info("The command returned with exit_status {}".format(
130
exit_status))
131
else:
132
error_name = errno.errorcode.get(error, "???")
133
sudo.log_error(
134
"Failed to execute command, execve syscall returned "
135
"{} ({})".format(error, error_name))
136
137
# -- Helper functions --
138
139
def _is_command_allowed(self, cmd):
140
return os.path.basename(cmd) in self._allowed_commands
141
142
def _find_on_path(self, cmd):
143
if os.path.isabs(cmd):
144
return cmd
145
146
path = self.user_env.get("PATH", "/usr/bin:/bin")
147
absolute_cmd = shutil.which(cmd, path=path)
148
if not absolute_cmd:
149
raise sudo.PluginError("Can not find cmd '{}' on PATH".format(cmd))
150
return absolute_cmd
151
152
def _runas_pwd(self):
153
runas_user = self.settings.get("runas_user") or "root"
154
try:
155
return pwd.getpwnam(runas_user)
156
except KeyError:
157
raise sudo.PluginError("Could not find user "
158
"'{}'".format(runas_user))
159
160
def _runas_uid(self):
161
return self._runas_pwd().pw_uid
162
163
def _runas_gid(self):
164
runas_group = self.settings.get("runas_group")
165
if runas_group is None:
166
return self._runas_pwd().pw_gid
167
168
try:
169
return grp.getgrnam(runas_group).gr_gid
170
except KeyError:
171
raise sudo.PluginError(
172
"Could not find group '{}'".format(runas_group))
173
174