rda-python-setuid 1.0.9__py3-none-any.whl → 3.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rda_python_setuid/cmwrapper.c +85 -0
- rda_python_setuid/install.py +210 -41
- rda_python_setuid/install.usg +77 -17
- rda_python_setuid/pgstart.py +3 -3
- rda_python_setuid/pywrapper.py +2 -0
- rda_python_setuid/setuid_setup.usg +75 -0
- rda_python_setuid/setup_guide.py +51 -0
- rda_python_setuid-3.0.1.dist-info/METADATA +275 -0
- rda_python_setuid-3.0.1.dist-info/RECORD +15 -0
- {rda_python_setuid-1.0.9.dist-info → rda_python_setuid-3.0.1.dist-info}/WHEEL +1 -1
- rda_python_setuid-1.0.9.dist-info/METADATA +0 -147
- rda_python_setuid-1.0.9.dist-info/RECORD +0 -12
- {rda_python_setuid-1.0.9.dist-info → rda_python_setuid-3.0.1.dist-info}/entry_points.txt +0 -0
- {rda_python_setuid-1.0.9.dist-info → rda_python_setuid-3.0.1.dist-info}/licenses/LICENSE +0 -0
- {rda_python_setuid-1.0.9.dist-info → rda_python_setuid-3.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/***************************************************************************************\
|
|
2
|
+
*
|
|
3
|
+
* Title: cmwrapper.c
|
|
4
|
+
* Author: Zaihua Ji, zji@ucar.edu
|
|
5
|
+
* Date: 2026-09-23
|
|
6
|
+
* Purpose: C wrapper to start ONE fixed python program as an effective user, for
|
|
7
|
+
* callers who are NOT in the common user's group (Mode 4755).
|
|
8
|
+
*
|
|
9
|
+
* Difference from pywrapper.c:
|
|
10
|
+
* pywrapper is installed 4750, so only members of the common user's group can
|
|
11
|
+
* execute it, and it picks the program to exec from basename(argv[0]) with the
|
|
12
|
+
* directory taken from /proc/self/exe. That is safe only because of the 4750
|
|
13
|
+
* group restriction: anyone who can run it can already run every setuid_* entry
|
|
14
|
+
* in the bin directory just by naming the symlink differently.
|
|
15
|
+
*
|
|
16
|
+
* cmwrapper is installed 4755, i.e. executable by EVERY user on the machine, so
|
|
17
|
+
* neither the program nor its directory may come from the caller:
|
|
18
|
+
* - the absolute path of the program to exec is baked in at compile time as
|
|
19
|
+
* CMEXEC, and the program name reported to the program is baked in as CMPROG;
|
|
20
|
+
* - the environment is replaced with a fixed whitelist, so PYTHONPATH,
|
|
21
|
+
* PYTHONHOME, LD_PRELOAD, LD_LIBRARY_PATH and the PGLOG path variables
|
|
22
|
+
* (DSDHOME, DSSHOME, LOGPATH, COMMONUSER, ...) cannot be used to run
|
|
23
|
+
* arbitrary code, or redirect where files are written, as the common user.
|
|
24
|
+
*
|
|
25
|
+
* One cmwrapper binary is therefore compiled per wrapped program; it can NOT be
|
|
26
|
+
* symlinked under another name to reach a different program.
|
|
27
|
+
*
|
|
28
|
+
* IMPORTANT: a program wrapped by cmwrapper is callable by anybody, so it must do
|
|
29
|
+
* its own authorization (as gdexdrop does with its access list) and must confine
|
|
30
|
+
* where it writes. Do NOT wrap a general purpose program such as gdexcp.
|
|
31
|
+
*
|
|
32
|
+
* Instruction:
|
|
33
|
+
* # Compile and install bin/PROGRAM as a 4755 setuid binary owned by CommonUser:
|
|
34
|
+
* pywrapper-install -m|--cmlink PROGRAM [-n|--username CommonUser] [-e|--envhome $ENVHOME]
|
|
35
|
+
*
|
|
36
|
+
\***************************************************************************************/
|
|
37
|
+
|
|
38
|
+
#include <unistd.h>
|
|
39
|
+
#include <stdio.h>
|
|
40
|
+
#include <string.h>
|
|
41
|
+
#include <stdlib.h>
|
|
42
|
+
|
|
43
|
+
#ifndef CMPROG
|
|
44
|
+
#error "CMPROG must be defined at compile time, e.g. -DCMPROG=\"gdexdrop\""
|
|
45
|
+
#endif
|
|
46
|
+
#ifndef CMEXEC
|
|
47
|
+
#error "CMEXEC must be defined at compile time, e.g. -DCMEXEC=\"/env/bin/setuid_gdexdrop\""
|
|
48
|
+
#endif
|
|
49
|
+
|
|
50
|
+
#define CMPATH "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
|
|
51
|
+
|
|
52
|
+
/* Environment variables passed through from the caller. Everything else is
|
|
53
|
+
dropped; in particular every PYTHON*, LD_* and PGLOG path variable. */
|
|
54
|
+
static const char *keepvars[] = {"HOME", "USER", "LOGNAME", "TERM", "LANG", "TZ", NULL};
|
|
55
|
+
|
|
56
|
+
/* main program */
|
|
57
|
+
int main(int argc, char *argv[]) {
|
|
58
|
+
(void)argc;
|
|
59
|
+
|
|
60
|
+
char *envp[16];
|
|
61
|
+
char *value, *entry;
|
|
62
|
+
size_t len;
|
|
63
|
+
int i, n = 0;
|
|
64
|
+
|
|
65
|
+
for(i = 0; keepvars[i] != NULL; i++) {
|
|
66
|
+
value = getenv(keepvars[i]);
|
|
67
|
+
if(value == NULL) continue;
|
|
68
|
+
len = strlen(keepvars[i]) + strlen(value) + 2;
|
|
69
|
+
entry = malloc(len);
|
|
70
|
+
if(entry == NULL) {
|
|
71
|
+
perror(CMPROG ": malloc");
|
|
72
|
+
exit(1);
|
|
73
|
+
}
|
|
74
|
+
snprintf(entry, len, "%s=%s", keepvars[i], value);
|
|
75
|
+
envp[n++] = entry;
|
|
76
|
+
}
|
|
77
|
+
envp[n++] = (char *)"PATH=" CMPATH;
|
|
78
|
+
envp[n++] = (char *)"PYTHONNOUSERSITE=1"; /* ignore ~/.local site-packages */
|
|
79
|
+
envp[n] = NULL;
|
|
80
|
+
|
|
81
|
+
argv[0] = (char *)CMPROG; /* the program identifies itself by this name */
|
|
82
|
+
execve(CMEXEC, argv, envp);
|
|
83
|
+
perror(CMEXEC); /* execve only returns on error */
|
|
84
|
+
exit(1);
|
|
85
|
+
}
|
rda_python_setuid/install.py
CHANGED
|
@@ -15,17 +15,27 @@
|
|
|
15
15
|
# pywrapper-install
|
|
16
16
|
#
|
|
17
17
|
# # 1. Compile and install pywrapper (run once per environment):
|
|
18
|
-
# pywrapper-install -c [-
|
|
18
|
+
# pywrapper-install -c [-n gdexdata] [-e $ENVHOME]
|
|
19
19
|
#
|
|
20
20
|
# # 2. Create pgstart_USER entry so USER can run commands as themselves:
|
|
21
|
-
# pywrapper-install -p [-
|
|
21
|
+
# pywrapper-install -p [-n zji] [-e $ENVHOME]
|
|
22
22
|
#
|
|
23
23
|
# # 3. Create a symlink so a program runs as CommonUser via pywrapper (setuid):
|
|
24
|
-
# pywrapper-install -l myprog [-
|
|
24
|
+
# pywrapper-install -l myprog [-n gdexdata] [-e $ENVHOME]
|
|
25
|
+
#
|
|
26
|
+
# # 3b. Auto-link all discovered setuid_* entries that are not yet linked:
|
|
27
|
+
# pywrapper-install -l all [-e $ENVHOME]
|
|
25
28
|
#
|
|
26
29
|
# # 4. Simple install: symlink PROGRAM -> setuid_PROGRAM (no setuid, runs as current user):
|
|
27
30
|
# pywrapper-install -l myprog -s [-e $ENVHOME]
|
|
28
31
|
#
|
|
32
|
+
# # 5. Update existing installation (recompile and reinstall all setuid binaries):
|
|
33
|
+
# pywrapper-install -u [-n gdexdata] [-e $ENVHOME]
|
|
34
|
+
#
|
|
35
|
+
# # 6. Compile a dedicated 4755 cmwrapper binary, so users outside the CommonUser
|
|
36
|
+
# # group can run one fixed program as CommonUser:
|
|
37
|
+
# pywrapper-install -m myprog [-t SCRIPT] [-d DESTDIR] [-n gdexdata] [-e $ENVHOME]
|
|
38
|
+
#
|
|
29
39
|
# Convention for wrapped programs:
|
|
30
40
|
# The target package must register its connector entry point with a setuid_ prefix:
|
|
31
41
|
# [project.scripts]
|
|
@@ -41,6 +51,7 @@
|
|
|
41
51
|
|
|
42
52
|
import argparse
|
|
43
53
|
import os
|
|
54
|
+
import re
|
|
44
55
|
import shutil
|
|
45
56
|
import subprocess
|
|
46
57
|
import sys
|
|
@@ -51,9 +62,9 @@ def get_bindir():
|
|
|
51
62
|
return os.path.dirname(os.path.abspath(sys.executable))
|
|
52
63
|
|
|
53
64
|
|
|
54
|
-
def get_c_source():
|
|
55
|
-
"""Return path to
|
|
56
|
-
return os.path.join(os.path.dirname(__file__),
|
|
65
|
+
def get_c_source(name='pywrapper.c'):
|
|
66
|
+
"""Return path to a C source file bundled with this package."""
|
|
67
|
+
return os.path.join(os.path.dirname(__file__), name)
|
|
57
68
|
|
|
58
69
|
|
|
59
70
|
def run(cmd):
|
|
@@ -77,7 +88,7 @@ def main():
|
|
|
77
88
|
help="Path to the venv root directory containing bin/ (default: parent of the current Python executable's bin/ dir)"
|
|
78
89
|
)
|
|
79
90
|
parser.add_argument(
|
|
80
|
-
'-
|
|
91
|
+
'-n', '--username', default=None,
|
|
81
92
|
help="User name to own the setuid binary (default: current login user for -p/--pgstart, gdexdata otherwise)"
|
|
82
93
|
)
|
|
83
94
|
parser.add_argument(
|
|
@@ -95,57 +106,117 @@ def main():
|
|
|
95
106
|
)
|
|
96
107
|
group.add_argument(
|
|
97
108
|
'-l', '--link', metavar='PROGRAM',
|
|
98
|
-
help="Create symlink PROGRAM -> pywrapper for running a fixed program as CommonUser (Mode 1)"
|
|
109
|
+
help="Create symlink PROGRAM -> pywrapper for running a fixed program as CommonUser (Mode 1); use 'all' to auto-link every setuid_* entry not yet linked"
|
|
110
|
+
)
|
|
111
|
+
group.add_argument(
|
|
112
|
+
'-m', '--cmlink', metavar='PROGRAM',
|
|
113
|
+
help="Compile a dedicated 4755 cmwrapper binary for PROGRAM, so users outside the CommonUser group can run it (Mode 3)"
|
|
114
|
+
)
|
|
115
|
+
group.add_argument(
|
|
116
|
+
'-u', '--update', action='store_true',
|
|
117
|
+
help="Update an existing installation: recompile pywrapper and reinstall all pgstart_USER setuid binaries"
|
|
118
|
+
)
|
|
119
|
+
parser.add_argument(
|
|
120
|
+
'-t', '--target', default=None,
|
|
121
|
+
help="Absolute path of the python script a cmwrapper binary execs (default: BINDIR/setuid_PROGRAM; use with -m/--cmlink)"
|
|
122
|
+
)
|
|
123
|
+
parser.add_argument(
|
|
124
|
+
'-d', '--destdir', default=None,
|
|
125
|
+
help="Directory to install a cmwrapper binary into, such as a common area on everyone's PATH (default: BINDIR; use with -m/--cmlink)"
|
|
99
126
|
)
|
|
100
127
|
args = parser.parse_args()
|
|
101
128
|
|
|
102
|
-
if not (args.compile or args.pgstart or args.link):
|
|
129
|
+
if not (args.compile or args.pgstart or args.link or args.cmlink or args.update):
|
|
103
130
|
show_usage()
|
|
104
131
|
|
|
105
|
-
if args.
|
|
132
|
+
if args.username is None and not args.simple:
|
|
106
133
|
import pwd
|
|
107
134
|
if args.pgstart:
|
|
108
|
-
args.
|
|
135
|
+
args.username = pwd.getpwuid(os.getuid()).pw_name
|
|
109
136
|
else:
|
|
110
|
-
args.
|
|
137
|
+
args.username = 'gdexdata'
|
|
111
138
|
|
|
112
139
|
bindir = os.path.join(args.envhome, 'bin') if args.envhome else get_bindir()
|
|
113
140
|
pywrapper = os.path.join(bindir, 'pywrapper')
|
|
114
141
|
|
|
115
142
|
if args.link:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if os.path.
|
|
124
|
-
print("
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
143
|
+
# For appname -> pywrapper links, run `ln -s` via pgstart_<commonuser> so
|
|
144
|
+
# the resulting symlink is owned by the common user (pywrapper owner).
|
|
145
|
+
pgstart_common = None
|
|
146
|
+
if not args.simple and os.path.exists(pywrapper):
|
|
147
|
+
import pwd
|
|
148
|
+
common_user = pwd.getpwuid(os.stat(pywrapper).st_uid).pw_name
|
|
149
|
+
pgstart_common = os.path.join(bindir, 'pgstart_' + common_user)
|
|
150
|
+
if not os.path.exists(pgstart_common):
|
|
151
|
+
print("Error: {} not found. Run pywrapper-install --pgstart --username {} first.".format(pgstart_common, common_user))
|
|
152
|
+
sys.exit(1)
|
|
153
|
+
|
|
154
|
+
if args.link.lower() == 'all':
|
|
155
|
+
# Discover all setuid_* entries in bindir and link any that are missing
|
|
156
|
+
appnames = sorted(
|
|
157
|
+
f[len('setuid_'):] for f in os.listdir(bindir) if f.startswith('setuid_')
|
|
158
|
+
)
|
|
159
|
+
if not appnames:
|
|
160
|
+
print("No setuid_* entries found in {}".format(bindir))
|
|
161
|
+
for appname in appnames:
|
|
162
|
+
target = os.path.join(bindir, appname)
|
|
163
|
+
script = os.path.join(bindir, 'setuid_' + appname)
|
|
164
|
+
if args.simple:
|
|
165
|
+
if os.path.lexists(target):
|
|
166
|
+
print("Already exists: {}".format(target))
|
|
167
|
+
else:
|
|
168
|
+
os.symlink(script, target)
|
|
169
|
+
print("Created: {} -> setuid_{}".format(target, appname))
|
|
170
|
+
else:
|
|
171
|
+
if os.path.lexists(target):
|
|
172
|
+
print("Already exists: {}".format(target))
|
|
173
|
+
else:
|
|
174
|
+
run([pgstart_common, 'ln', '-s', pywrapper, target])
|
|
175
|
+
print("Created: {} -> pywrapper".format(target))
|
|
128
176
|
else:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if os.path.
|
|
132
|
-
print("
|
|
177
|
+
target = os.path.join(bindir, args.link)
|
|
178
|
+
script = os.path.join(bindir, 'setuid_{}'.format(args.link))
|
|
179
|
+
if not os.path.exists(script):
|
|
180
|
+
print("Error: {} not found. Install the package that provides it first.".format(script))
|
|
181
|
+
sys.exit(1)
|
|
182
|
+
if args.simple:
|
|
183
|
+
# Simple install: symlink PROGRAM -> setuid_PROGRAM, no setuid, runs as current user.
|
|
184
|
+
if os.path.lexists(target):
|
|
185
|
+
print("Already exists: {}".format(target))
|
|
186
|
+
else:
|
|
187
|
+
os.symlink(script, target)
|
|
188
|
+
print("Created: {} -> setuid_{}".format(target, args.link))
|
|
133
189
|
else:
|
|
134
|
-
|
|
135
|
-
|
|
190
|
+
# Mode 1: symlink PROGRAM -> pywrapper. setuid_PROGRAM is left with its
|
|
191
|
+
# default ownership/permissions so it can be loaded and executed normally.
|
|
192
|
+
if os.path.lexists(target):
|
|
193
|
+
print("Already exists: {}".format(target))
|
|
194
|
+
else:
|
|
195
|
+
run([pgstart_common, 'ln', '-s', pywrapper, target])
|
|
196
|
+
print("Created: {} -> pywrapper".format(target))
|
|
136
197
|
|
|
137
198
|
elif args.pgstart:
|
|
138
|
-
# Mode 2:
|
|
199
|
+
# Mode 2: create pgstart_USER with setuid owned by USER.
|
|
200
|
+
# When USER already owns pywrapper (i.e. the common user), pywrapper is
|
|
201
|
+
# already setuid owned by USER, so a symlink is sufficient; otherwise
|
|
202
|
+
# copy pywrapper and chmod 4750 as USER.
|
|
139
203
|
if not os.path.exists(pywrapper):
|
|
140
|
-
print("Error: {} not found. Run pywrapper-install --
|
|
204
|
+
print("Error: {} not found. Run pywrapper-install --compile --username COMMONUSER first.".format(pywrapper))
|
|
141
205
|
sys.exit(1)
|
|
142
|
-
target = os.path.join(bindir, 'pgstart_{}'.format(args.
|
|
206
|
+
target = os.path.join(bindir, 'pgstart_{}'.format(args.username))
|
|
143
207
|
import pwd
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
208
|
+
pywrapper_owner = pwd.getpwuid(os.stat(pywrapper).st_uid).pw_name
|
|
209
|
+
if args.username == pywrapper_owner:
|
|
210
|
+
if os.path.lexists(target):
|
|
211
|
+
os.remove(target)
|
|
212
|
+
os.symlink(pywrapper, target)
|
|
213
|
+
print("Linked: {} -> pywrapper (setuid, owned by {})".format(target, args.username))
|
|
214
|
+
else:
|
|
215
|
+
curuser = pwd.getpwuid(os.getuid()).pw_name
|
|
216
|
+
sudo_prefix = [] if curuser == args.username else ['sudo', '-u', args.username]
|
|
217
|
+
run(sudo_prefix + ['cp', pywrapper, target])
|
|
218
|
+
run(sudo_prefix + ['chmod', '4750', target])
|
|
219
|
+
print("Installed: {} (setuid, owned by {})".format(target, args.username))
|
|
149
220
|
|
|
150
221
|
elif args.compile:
|
|
151
222
|
# Compile pywrapper.c and install pywrapper with setuid
|
|
@@ -153,9 +224,107 @@ def main():
|
|
|
153
224
|
src_dest = os.path.join(bindir, 'pywrapper.c')
|
|
154
225
|
shutil.copy(src, src_dest)
|
|
155
226
|
print("Copied: {}".format(src_dest))
|
|
156
|
-
run(['sudo', '-u', args.
|
|
157
|
-
run(['sudo', '-u', args.
|
|
158
|
-
print("Installed: {} (setuid, owned by {})".format(pywrapper, args.
|
|
227
|
+
run(['sudo', '-u', args.username, 'gcc', '-o', pywrapper, src_dest])
|
|
228
|
+
run(['sudo', '-u', args.username, 'chmod', '4750', pywrapper])
|
|
229
|
+
print("Installed: {} (setuid, owned by {})".format(pywrapper, args.username))
|
|
230
|
+
|
|
231
|
+
elif args.cmlink:
|
|
232
|
+
# Mode 3: compile a dedicated cmwrapper binary for one program and install it
|
|
233
|
+
# 4755, so users outside the CommonUser group can run it. The program path is
|
|
234
|
+
# baked in at compile time and the environment is sanitized by cmwrapper.c, so
|
|
235
|
+
# no symlink under another name can reach a different program.
|
|
236
|
+
if not re.match(r'^\w+$', args.cmlink):
|
|
237
|
+
print("Error: invalid program name '{}', expecting word characters only.".format(args.cmlink))
|
|
238
|
+
sys.exit(1)
|
|
239
|
+
script = args.target if args.target else os.path.join(bindir, 'setuid_' + args.cmlink)
|
|
240
|
+
if not (os.path.isabs(script) and re.match(r'^[\w/.-]+$', script)):
|
|
241
|
+
print("Error: {} of -t/--target must be an absolute path of word characters, '/', '.' and '-'.".format(script))
|
|
242
|
+
sys.exit(1)
|
|
243
|
+
if not os.path.exists(script):
|
|
244
|
+
print("Error: {} not found. Install the package that provides it first.".format(script))
|
|
245
|
+
sys.exit(1)
|
|
246
|
+
destdir = args.destdir if args.destdir else bindir
|
|
247
|
+
target = os.path.join(destdir, args.cmlink)
|
|
248
|
+
src = get_c_source('cmwrapper.c')
|
|
249
|
+
src_dest = os.path.join(bindir, 'cmwrapper.c')
|
|
250
|
+
shutil.copy(src, src_dest)
|
|
251
|
+
print("Copied: {}".format(src_dest))
|
|
252
|
+
run(['sudo', '-u', args.username, 'gcc',
|
|
253
|
+
'-DCMPROG="{}"'.format(args.cmlink), '-DCMEXEC="{}"'.format(script),
|
|
254
|
+
'-o', target, src_dest])
|
|
255
|
+
run(['sudo', '-u', args.username, 'chmod', '4755', target])
|
|
256
|
+
print("Installed: {} (setuid, owned by {}, execs {})".format(target, args.username, script))
|
|
257
|
+
|
|
258
|
+
elif args.update:
|
|
259
|
+
# Update an existing installation: recompile pywrapper and reinstall all setuid binaries
|
|
260
|
+
pgstart_files = sorted(f for f in os.listdir(bindir) if f.startswith('pgstart_'))
|
|
261
|
+
if not pgstart_files:
|
|
262
|
+
print("Error: No pgstart_* binaries found in {}".format(bindir))
|
|
263
|
+
sys.exit(1)
|
|
264
|
+
if not os.path.exists(pywrapper):
|
|
265
|
+
print("Error: {} not found.".format(pywrapper))
|
|
266
|
+
sys.exit(1)
|
|
267
|
+
|
|
268
|
+
gdexuser = args.username
|
|
269
|
+
pgstart_gdexdata = os.path.join(bindir, 'pgstart_' + gdexuser)
|
|
270
|
+
if not os.path.exists(pgstart_gdexdata):
|
|
271
|
+
print("Error: {} not found.".format(pgstart_gdexdata))
|
|
272
|
+
sys.exit(1)
|
|
273
|
+
|
|
274
|
+
update_tmp = os.path.join(bindir, 'update_tmp')
|
|
275
|
+
os.makedirs(update_tmp, exist_ok=True)
|
|
276
|
+
print("Created: {}".format(update_tmp))
|
|
277
|
+
|
|
278
|
+
# Symlink pgstart.py into update_tmp so pywrapper instances running from
|
|
279
|
+
# update_tmp can find it via the fpath/pgstart.py fallback lookup.
|
|
280
|
+
bindir_pgstart_py = os.path.join(bindir, 'pgstart.py')
|
|
281
|
+
update_pgstart_py = os.path.join(update_tmp, 'pgstart.py')
|
|
282
|
+
if not os.path.lexists(update_pgstart_py):
|
|
283
|
+
os.symlink(bindir_pgstart_py, update_pgstart_py)
|
|
284
|
+
print("Linked: {} -> {}".format(update_pgstart_py, bindir_pgstart_py))
|
|
285
|
+
|
|
286
|
+
# Copy each non-gdex pgstart_USERNAME into update_tmp using itself, then chmod 4750
|
|
287
|
+
for fname in pgstart_files:
|
|
288
|
+
username = fname[len('pgstart_'):]
|
|
289
|
+
if username == gdexuser:
|
|
290
|
+
continue
|
|
291
|
+
src_pgstart = os.path.join(bindir, fname)
|
|
292
|
+
dst_pgstart = os.path.join(update_tmp, fname)
|
|
293
|
+
run([src_pgstart, 'cp', src_pgstart, update_tmp + '/'])
|
|
294
|
+
run([src_pgstart, 'chmod', '4750', dst_pgstart])
|
|
295
|
+
|
|
296
|
+
# Copy pywrapper into update_tmp as gdexdata, chmod, then hardlink as pgstart_gdexdata
|
|
297
|
+
update_pywrapper = os.path.join(update_tmp, 'pywrapper')
|
|
298
|
+
update_pgstart_gdexdata = os.path.join(update_tmp, 'pgstart_' + gdexuser)
|
|
299
|
+
run([pgstart_gdexdata, 'cp', pywrapper, update_tmp + '/'])
|
|
300
|
+
run([pgstart_gdexdata, 'chmod', '4750', update_pywrapper])
|
|
301
|
+
run([pgstart_gdexdata, 'ln', update_pywrapper, update_pgstart_gdexdata])
|
|
302
|
+
|
|
303
|
+
# Compile new pywrapper using update_tmp/pgstart_gdexdata
|
|
304
|
+
src = get_c_source()
|
|
305
|
+
src_dest = os.path.join(bindir, 'pywrapper.c')
|
|
306
|
+
shutil.copy(src, src_dest)
|
|
307
|
+
print("Copied: {}".format(src_dest))
|
|
308
|
+
run([update_pgstart_gdexdata, 'gcc', '-o', pywrapper, src_dest])
|
|
309
|
+
run([update_pgstart_gdexdata, 'chmod', '4750', pywrapper])
|
|
310
|
+
print("Compiled: {} (setuid, owned by {})".format(pywrapper, gdexuser))
|
|
311
|
+
|
|
312
|
+
# Recreate each pgstart_* in bindir using the corresponding update_tmp/pgstart_*
|
|
313
|
+
for fname in sorted(f for f in os.listdir(update_tmp) if f.startswith('pgstart_')):
|
|
314
|
+
username = fname[len('pgstart_'):]
|
|
315
|
+
update_pgstart = os.path.join(update_tmp, fname)
|
|
316
|
+
target = os.path.join(bindir, fname)
|
|
317
|
+
if username == gdexuser:
|
|
318
|
+
run([update_pgstart_gdexdata, 'ln', '-sf', pywrapper, target])
|
|
319
|
+
print("Linked: {} -> pywrapper (setuid, owned by {})".format(target, gdexuser))
|
|
320
|
+
else:
|
|
321
|
+
run([update_pgstart, 'cp', pywrapper, target])
|
|
322
|
+
run([update_pgstart, 'chmod', '4750', target])
|
|
323
|
+
print("Updated: {} (setuid, owned by {})".format(target, username))
|
|
324
|
+
|
|
325
|
+
# Clean up the temporary working directory
|
|
326
|
+
shutil.rmtree(update_tmp)
|
|
327
|
+
print("Removed: {}".format(update_tmp))
|
|
159
328
|
|
|
160
329
|
|
|
161
330
|
if __name__ == '__main__': main()
|
rda_python_setuid/install.usg
CHANGED
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
that execute Python scripts as a common or effective user via the setuid mechanism.
|
|
3
3
|
Must be run inside the target Python virtual environment.
|
|
4
4
|
|
|
5
|
-
Usage: pywrapper-install [-
|
|
5
|
+
Usage: pywrapper-install [-n|--username USERNAME] [-e|--envhome ENVHOME]
|
|
6
6
|
( -c|--compile
|
|
7
7
|
| -p|--pgstart
|
|
8
|
-
| -l|--link PROGRAM [-s|--simple]
|
|
8
|
+
| -l|--link PROGRAM|all [-s|--simple]
|
|
9
|
+
| -m|--cmlink PROGRAM [-t|--target SCRIPT] [-d|--destdir DIR]
|
|
10
|
+
| -u|--update )
|
|
9
11
|
|
|
10
12
|
Run pywrapper-install with no arguments to display this user guide. Exactly
|
|
11
|
-
one of -c/--compile, -p/--pgstart,
|
|
12
|
-
action.
|
|
13
|
+
one of -c/--compile, -p/--pgstart, -l/--link, -m/--cmlink, or -u/--update
|
|
14
|
+
must be given to perform an action.
|
|
13
15
|
|
|
14
|
-
- Option -
|
|
16
|
+
- Option -n or --username USERNAME
|
|
15
17
|
The user name to own the setuid binary. For Mode 1 (CommonUser program),
|
|
16
18
|
this is the common user (e.g. gdexdata). For Mode 2 (pgstart), this is
|
|
17
19
|
the specialist user (e.g. zji). Defaults to 'gdexdata' unless -p/--pgstart
|
|
@@ -31,20 +33,59 @@
|
|
|
31
33
|
|
|
32
34
|
- Option -p or --pgstart
|
|
33
35
|
Mode 2: copy the compiled pywrapper binary to pgstart_USER (owned by USER,
|
|
34
|
-
chmod 4750). Allows USER
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
chmod 4750). Allows USER (any login user in the same group as the
|
|
37
|
+
common user PGLOG['COMMONUSER']) to run arbitrary commands as themselves
|
|
38
|
+
via the setuid wrapper. Two paths for setting up pgstart_USER:
|
|
39
|
+
(a) if PGLOG['ADMINUSER'] (default zji) can 'sudo -u USER', the admin
|
|
40
|
+
runs 'pywrapper-install -p -n USER' on behalf of USER; or
|
|
41
|
+
(b) USER runs the same command themselves (no sudo required).
|
|
42
|
+
Cannot be combined with -c/--compile or -l/--link.
|
|
43
|
+
|
|
44
|
+
- Option -l or --link PROGRAM|all
|
|
38
45
|
Mode 1: create a symlink PROGRAM -> pywrapper in the bin/ directory so
|
|
39
46
|
that running PROGRAM invokes the setuid wrapper, which then execs
|
|
40
47
|
setuid_PROGRAM as CommonUser. setuid_PROGRAM keeps its default
|
|
41
48
|
ownership and permissions. Cannot be combined with -c/--compile or
|
|
42
49
|
-p/--pgstart.
|
|
50
|
+
Use 'all' instead of a program name to scan bin/ for every setuid_*
|
|
51
|
+
entry and add any missing PROGRAM -> pywrapper symlinks in one pass.
|
|
52
|
+
|
|
53
|
+
- Option -m or --cmlink PROGRAM
|
|
54
|
+
Mode 3: compile cmwrapper.c (bundled with this package) into a dedicated
|
|
55
|
+
binary named PROGRAM, owned by USER with chmod 4755 (setuid), so that
|
|
56
|
+
users who are NOT in the common user's group can run PROGRAM as
|
|
57
|
+
CommonUser. Unlike pywrapper, the script to exec is baked into the
|
|
58
|
+
binary at compile time and the environment is replaced with a fixed
|
|
59
|
+
whitelist, so the binary cannot be symlinked under another name to reach
|
|
60
|
+
a different program, and PYTHONPATH, LD_PRELOAD or the PGLOG path
|
|
61
|
+
variables cannot be used to run code as CommonUser. One binary is
|
|
62
|
+
compiled per program. Cannot be combined with the other actions.
|
|
63
|
+
|
|
64
|
+
Only wrap a program that does its own authorization and confines where
|
|
65
|
+
it writes, such as gdexdrop; never wrap a general purpose program such
|
|
66
|
+
as gdexcp, which would let any user on the machine read or overwrite
|
|
67
|
+
any file of the common user.
|
|
68
|
+
|
|
69
|
+
- Option -t or --target SCRIPT (use with -m/--cmlink)
|
|
70
|
+
Absolute path of the python script the cmwrapper binary execs.
|
|
71
|
+
Defaults to bin/setuid_PROGRAM of the environment.
|
|
72
|
+
|
|
73
|
+
- Option -d or --destdir DIR (use with -m/--cmlink)
|
|
74
|
+
Directory to install the cmwrapper binary into, typically a common area
|
|
75
|
+
on everyone's PATH, such as /glade/u/home/gdexdata/bin. Defaults to the
|
|
76
|
+
bin/ directory of the environment.
|
|
77
|
+
|
|
78
|
+
- Option -u or --update
|
|
79
|
+
Update an existing installation. Discovers all pgstart_* and pywrapper
|
|
80
|
+
binaries already present in bin/, saves them to a temporary update_tmp/
|
|
81
|
+
subdirectory, recompiles pywrapper using update_tmp/pgstart_COMMONUSER, and
|
|
82
|
+
reinstalls every pgstart_* binary from update_tmp. Use -n/--username to
|
|
83
|
+
specify the gdex common user (default: gdexdata).
|
|
43
84
|
|
|
44
85
|
- Option -s or --simple (use with -l/--link)
|
|
45
86
|
Simple install: create a symlink PROGRAM -> setuid_PROGRAM directly,
|
|
46
87
|
skipping the setuid mechanism entirely. The program runs as the current
|
|
47
|
-
user with no privilege change. -
|
|
88
|
+
user with no privilege change. -n/--username is not required with this option.
|
|
48
89
|
Useful for users who do not need or cannot set up the setuid wrapper.
|
|
49
90
|
|
|
50
91
|
Convention for wrapped programs:
|
|
@@ -111,10 +152,14 @@
|
|
|
111
152
|
pywrapper-install -c
|
|
112
153
|
|
|
113
154
|
3. Wire up each program as a setuid entry:
|
|
114
|
-
pywrapper-install -l dsarch
|
|
155
|
+
pywrapper-install -l dsarch # one program
|
|
156
|
+
pywrapper-install -l all # or all setuid_* entries at once
|
|
115
157
|
|
|
116
|
-
4. Optionally,
|
|
117
|
-
|
|
158
|
+
4. Optionally, install a pgstart_<loginname> binary so <loginname> (any
|
|
159
|
+
user in the same group as PGLOG['COMMONUSER']) can run commands as
|
|
160
|
+
themselves. Either PGLOG['ADMINUSER'] (default zji, if it has
|
|
161
|
+
'sudo -u <loginname>'), or <loginname> directly, runs:
|
|
162
|
+
pywrapper-install -p -n <loginname>
|
|
118
163
|
|
|
119
164
|
Option B - Simple install (no sudo required, runs as current user):
|
|
120
165
|
|
|
@@ -122,7 +167,8 @@
|
|
|
122
167
|
pip install rda_python_dsarch
|
|
123
168
|
|
|
124
169
|
2. Create a direct symlink to the connector script:
|
|
125
|
-
pywrapper-install -l dsarch -s
|
|
170
|
+
pywrapper-install -l dsarch -s # one program
|
|
171
|
+
pywrapper-install -l all -s # or all setuid_* entries at once
|
|
126
172
|
|
|
127
173
|
Examples:
|
|
128
174
|
|
|
@@ -135,8 +181,22 @@
|
|
|
135
181
|
3. Wire up dsarch to run as gdexdata via pywrapper:
|
|
136
182
|
pywrapper-install -l dsarch
|
|
137
183
|
|
|
138
|
-
4.
|
|
139
|
-
pywrapper-install -
|
|
184
|
+
4. Wire up all setuid_* entries at once:
|
|
185
|
+
pywrapper-install -l all
|
|
186
|
+
|
|
187
|
+
5. Install pgstart_zji so login user zji can run commands as themselves
|
|
188
|
+
via pgstart (run by ADMINUSER with 'sudo -u zji' available, or by zji):
|
|
189
|
+
pywrapper-install -p -n zji
|
|
140
190
|
|
|
141
|
-
|
|
191
|
+
6. Update an existing installation (recompile and reinstall all setuid binaries):
|
|
192
|
+
pywrapper-install -u
|
|
193
|
+
|
|
194
|
+
7. Simple install of dsarch for a user who does not need setuid:
|
|
142
195
|
pywrapper-install -l dsarch -s
|
|
196
|
+
|
|
197
|
+
8. Simple install of all setuid_* entries at once:
|
|
198
|
+
pywrapper-install -l all -s
|
|
199
|
+
|
|
200
|
+
9. Let users outside the gdexdata group run gdexdrop as gdexdata, installing
|
|
201
|
+
the 4755 binary into a common area on everyone's PATH:
|
|
202
|
+
pywrapper-install -m gdexdrop -d /glade/u/home/gdexdata/bin
|
rda_python_setuid/pgstart.py
CHANGED
|
@@ -40,7 +40,7 @@ def main():
|
|
|
40
40
|
pglog = PgLOG()
|
|
41
41
|
permit = False
|
|
42
42
|
pglog.PGLOG['LOGFILE'] = "pgstart.log"
|
|
43
|
-
aname =
|
|
43
|
+
aname = pglog.get_command()
|
|
44
44
|
bckgrd = False
|
|
45
45
|
workdir = None
|
|
46
46
|
argv = sys.argv[1:]
|
|
@@ -49,7 +49,7 @@ def main():
|
|
|
49
49
|
euid = pglog.PGLOG['EUID']
|
|
50
50
|
ruser = pwd.getpwuid(ruid).pw_name
|
|
51
51
|
euser = pwd.getpwuid(euid).pw_name
|
|
52
|
-
if ruser
|
|
52
|
+
if ruser in [pglog.PGLOG['ADMINUSER'], euser, pglog.PGLOG['COMMONUSER']] or euser == pglog.PGLOG['COMMONUSER']: permit = True
|
|
53
53
|
pglog.set_suid(euid)
|
|
54
54
|
|
|
55
55
|
while argv:
|
|
@@ -69,7 +69,7 @@ def main():
|
|
|
69
69
|
print("* Your Login Name is {}({}) & Effective User Name is {}({}).".format(ruser, ruid, euser, euid))
|
|
70
70
|
print("* Pass a command or options -(bg|fg|cwd|env|inc|plg) to run '{}'.".format(aname))
|
|
71
71
|
if not permit:
|
|
72
|
-
print("* You must be '{}' or '{}' to execute a command as user '{}'.".format(euser, pglog.PGLOG['
|
|
72
|
+
print("* You must be '{}' or '{}' to execute a command as user '{}'.".format(euser, pglog.PGLOG['COMMONUSER'], euser))
|
|
73
73
|
print("********************************************************************")
|
|
74
74
|
sys.exit(0)
|
|
75
75
|
|
rda_python_setuid/pywrapper.py
CHANGED
|
@@ -32,6 +32,8 @@ def main():
|
|
|
32
32
|
-plg -- print PGLOG variables and exit
|
|
33
33
|
"""
|
|
34
34
|
pglog = PgLOG()
|
|
35
|
+
from rda_python_setuid.setup_guide import show_setup_guide
|
|
36
|
+
show_setup_guide(pglog, 'rda_python_setuid', ['pywrapper'])
|
|
35
37
|
pglog.set_suid(pglog.PGLOG['EUID'])
|
|
36
38
|
inc = True
|
|
37
39
|
print("********************************************************************")
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
|
|
2
|
+
{PKGNAME} - Setuid Setup Guide
|
|
3
|
+
===============================
|
|
4
|
+
|
|
5
|
+
The following programs must run as the common user 'gdexdata' via the
|
|
6
|
+
rda_python_setuid mechanism:
|
|
7
|
+
|
|
8
|
+
{APPNAMES}
|
|
9
|
+
|
|
10
|
+
rda_python_setuid is installed automatically as a dependency.
|
|
11
|
+
|
|
12
|
+
If you are seeing this message after running a setuid_ connector script
|
|
13
|
+
directly, the setuid wrapper has not been set up yet. Follow the steps
|
|
14
|
+
below.
|
|
15
|
+
|
|
16
|
+
Run 'pywrapper-install' with no arguments for the full pywrapper user guide.
|
|
17
|
+
|
|
18
|
+
Environment Setup
|
|
19
|
+
-----------------
|
|
20
|
+
|
|
21
|
+
Option A - Python venv (DECS machines):
|
|
22
|
+
python3 -m venv $ENVHOME # e.g. /glade/u/home/gdexdata/gdexmsenv
|
|
23
|
+
source $ENVHOME/bin/activate
|
|
24
|
+
|
|
25
|
+
Option B - Conda (DAV/Casper):
|
|
26
|
+
conda create --prefix $ENVHOME python=3.12 # e.g. /glade/work/gdexdata/conda-envs/pg-gdex
|
|
27
|
+
conda activate $ENVHOME
|
|
28
|
+
|
|
29
|
+
Full Setuid Setup (requires sudo access to gdexdata)
|
|
30
|
+
----------------------------------------------------
|
|
31
|
+
|
|
32
|
+
# 1. Install the target package (pulls in rda_python_setuid automatically):
|
|
33
|
+
pip install {PKGNAME}
|
|
34
|
+
|
|
35
|
+
# 2. Compile the pywrapper C binary (once per environment):
|
|
36
|
+
pywrapper-install -c|--compile -n|--username gdexdata
|
|
37
|
+
|
|
38
|
+
# 3. Wire up each program as a setuid entry (specify name or use 'all'):
|
|
39
|
+
pywrapper-install -l|--link <program>
|
|
40
|
+
pywrapper-install -l|--link all # auto-link every setuid_* entry not yet linked
|
|
41
|
+
|
|
42
|
+
# 4. Optionally, install a pgstart_<loginname> binary so <loginname>
|
|
43
|
+
# (any user in the same group as PGLOG['COMMONUSER']) can run commands
|
|
44
|
+
# as themselves via the setuid wrapper. Same command in both cases,
|
|
45
|
+
# only the invoker differs:
|
|
46
|
+
#
|
|
47
|
+
# 4a. If PGLOG['ADMINUSER'] (default zji) can 'sudo -u <loginname>',
|
|
48
|
+
# the admin sets it up on the user's behalf:
|
|
49
|
+
pywrapper-install -p|--pgstart -n|--username <loginname>
|
|
50
|
+
#
|
|
51
|
+
# 4b. Otherwise <loginname> runs the same command themselves (no sudo
|
|
52
|
+
# from ADMINUSER required, since they already are <loginname>):
|
|
53
|
+
pywrapper-install -p|--pgstart -n|--username <loginname>
|
|
54
|
+
|
|
55
|
+
Update Existing Installation (no sudo required)
|
|
56
|
+
-----------------------------------------------
|
|
57
|
+
|
|
58
|
+
When the package is upgraded and a new pywrapper.c is bundled, use
|
|
59
|
+
-u/--update to recompile and reinstall all setuid binaries without
|
|
60
|
+
needing sudo. The existing pgstart_* binaries in bin/ are used to
|
|
61
|
+
perform the privileged operations:
|
|
62
|
+
|
|
63
|
+
pywrapper-install -u|--update [-n|--username gdexdata] [-e|--envhome $ENVHOME]
|
|
64
|
+
|
|
65
|
+
Simple Install (no sudo required, runs as current user)
|
|
66
|
+
-------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
Users who do not need the setuid mechanism can create direct symlinks
|
|
69
|
+
from <name> to setuid_<name>:
|
|
70
|
+
|
|
71
|
+
pywrapper-install -l|--link <program> -s|--simple
|
|
72
|
+
pywrapper-install -l|--link all -s|--simple # or link all setuid_* entries at once
|
|
73
|
+
|
|
74
|
+
The programs run as the current user with no privilege change.
|
|
75
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
##################################################################################
|
|
4
|
+
#
|
|
5
|
+
# Title: setup_guide
|
|
6
|
+
# Author: Zaihua Ji, zji@ucar.edu
|
|
7
|
+
# Date: 2026-05-21
|
|
8
|
+
# Purpose: Shared setuid setup guide displayed when a package's setuid_* entry
|
|
9
|
+
# point is invoked directly (i.e. before pywrapper symlinks are
|
|
10
|
+
# configured). Each package's setuid entry point calls
|
|
11
|
+
# show_setup_guide(pkgname, appnames) with its own metadata.
|
|
12
|
+
#
|
|
13
|
+
# Github: https://github.com/NCAR/rda-python-setuid.git
|
|
14
|
+
#
|
|
15
|
+
##################################################################################
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def show_setup_guide(obj, pkgname, appnames):
|
|
22
|
+
"""Display the setuid setup guide if invoked directly, otherwise return.
|
|
23
|
+
|
|
24
|
+
When a package's setuid entry point (e.g. ``setuid_dsarch``) is invoked
|
|
25
|
+
directly before pywrapper symlinks are set up, ``obj.get_command()``
|
|
26
|
+
returns the literal ``setuid_<appname>`` (no prefix stripping, since
|
|
27
|
+
euid is the real user, not COMMONUSER). In that case this function reads
|
|
28
|
+
``setuid_setup.usg`` bundled with rda_python_setuid, substitutes
|
|
29
|
+
``{PKGNAME}`` and ``{APPNAMES}``, prints the guide, and exits.
|
|
30
|
+
|
|
31
|
+
When invoked via the pywrapper symlink (euid = COMMONUSER), the
|
|
32
|
+
``setuid_`` prefix is stripped by ``get_command()``, the membership
|
|
33
|
+
check fails, and this function returns silently so the program runs
|
|
34
|
+
normally.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
obj: An instance derived from PgLOG (e.g. DsArch, RdaCp); provides
|
|
38
|
+
``get_command()`` with access to ``self.PGLOG['COMMONUSER']``.
|
|
39
|
+
pkgname: Distribution name (e.g. ``rda_python_dsarch``).
|
|
40
|
+
appnames: List of program names provided by the package that need
|
|
41
|
+
setuid (e.g. ``['dsarch']`` or ``['rdacp', 'rdakill', 'rdamod']``).
|
|
42
|
+
"""
|
|
43
|
+
if obj.get_command(sys.argv[0]) not in ['setuid_' + a for a in appnames]:
|
|
44
|
+
return
|
|
45
|
+
usgfile = os.path.join(os.path.dirname(__file__), 'setuid_setup.usg')
|
|
46
|
+
with open(usgfile) as f:
|
|
47
|
+
text = f.read()
|
|
48
|
+
text = text.replace('{PKGNAME}', pkgname)
|
|
49
|
+
text = text.replace('{APPNAMES}', ' '.join(appnames))
|
|
50
|
+
print(text)
|
|
51
|
+
sys.exit(0)
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rda_python_setuid
|
|
3
|
+
Version: 3.0.1
|
|
4
|
+
Summary: RDA Python Package to setuid for program executions as an effective or common user
|
|
5
|
+
Author-email: Zaihua Ji <zji@ucar.edu>
|
|
6
|
+
Project-URL: Homepage, https://github.com/NCAR/rda-python-setuid
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: rda_python_common
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
RDA Python package, including a C code wrapper, to execute commandline applications
|
|
18
|
+
via setuid for effective and common user names.
|
|
19
|
+
|
|
20
|
+
## Overview
|
|
21
|
+
|
|
22
|
+
`rda_python_setuid` provides a C binary (`pywrapper`) that acquires a setuid effective
|
|
23
|
+
user, then `execv`s a Python entry point script. This allows Python programs to run
|
|
24
|
+
as a designated common user (e.g. `gdexdata`) without requiring `sudo` access.
|
|
25
|
+
|
|
26
|
+
Three modes are supported:
|
|
27
|
+
|
|
28
|
+
- **Mode 1 (CommonUser program):** a symlink `dsarch -> pywrapper` runs `setuid_dsarch`
|
|
29
|
+
as the common user.
|
|
30
|
+
- **Mode 2 (pgstart specialist):** a copy `pgstart_<loginname>` (e.g. `pgstart_zji`)
|
|
31
|
+
runs any command as `<loginname>` via `pgstart.py`. `<loginname>` can be any
|
|
32
|
+
user that belongs to the same group as `PGLOG['COMMONUSER']`. Execution is
|
|
33
|
+
restricted to authorized callers (see `pgstart.py` below).
|
|
34
|
+
- **Mode 3 (cmwrapper, callers outside the group):** a dedicated binary compiled
|
|
35
|
+
from `cmwrapper.c` and installed `4755` runs ONE fixed program as the common
|
|
36
|
+
user for any user on the machine, including users outside the common user's
|
|
37
|
+
group (see "cmwrapper" below).
|
|
38
|
+
|
|
39
|
+
Two Python entry points are packaged alongside the C wrapper:
|
|
40
|
+
|
|
41
|
+
- **`pywrapper.py`** — the default fallback target executed when `pywrapper.c`
|
|
42
|
+
cannot resolve a matching `setuid_<program>` entry point. Acquires the
|
|
43
|
+
effective UID via `PgLOG.set_suid()`, prints the caller's real and effective
|
|
44
|
+
user names, and shows the `pyproject.toml` snippet plus the
|
|
45
|
+
`pywrapper-install -l <program>` command needed to wrap a new script.
|
|
46
|
+
Diagnostic flags `-env`, `-inc`, and `-plg` dump the environment variables,
|
|
47
|
+
`sys.path`, and `PGLOG` dictionary respectively — handy for verifying the
|
|
48
|
+
setuid environment before wiring up a real program.
|
|
49
|
+
|
|
50
|
+
- **`pgstart.py`** — the Mode 2 launcher invoked through a `pgstart_<loginname>`
|
|
51
|
+
copy of `pywrapper`. Reads the real/effective UIDs from `PGLOG`, then
|
|
52
|
+
permits execution only if the real user is in
|
|
53
|
+
`[PGLOG['ADMINUSER'], euser, PGLOG['COMMONUSER']]`
|
|
54
|
+
(i.e. the admin specialist `PGLOG['ADMINUSER']` — default `zji` — the
|
|
55
|
+
effective user themselves, or the shared common user); unauthorized callers receive
|
|
56
|
+
an informational message and exit. After authorization it parses leading
|
|
57
|
+
flag tokens — `-bg` (background via `subprocess.Popen`), `-fg` (explicit
|
|
58
|
+
foreground, default), `-cwd <dir>` (chdir before exec), and the same
|
|
59
|
+
`-env`/`-inc`/`-plg` diagnostics as `pywrapper.py` — and then runs the
|
|
60
|
+
remaining arguments as a command (`subprocess.run`/`Popen`) under the
|
|
61
|
+
effective UID, logging a host/program/timestamp/user line to `pgstart.log`.
|
|
62
|
+
|
|
63
|
+
## Dependency requirement
|
|
64
|
+
|
|
65
|
+
Any Python package whose programs are to be run via the setuid mechanism must declare
|
|
66
|
+
`rda_python_setuid` as a dependency in its `pyproject.toml`:
|
|
67
|
+
|
|
68
|
+
```toml
|
|
69
|
+
[project]
|
|
70
|
+
dependencies = [
|
|
71
|
+
"rda_python_setuid",
|
|
72
|
+
...
|
|
73
|
+
]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
It must also register each wrapped program's connector entry point with a `setuid_`
|
|
77
|
+
prefix:
|
|
78
|
+
|
|
79
|
+
```toml
|
|
80
|
+
[project.scripts]
|
|
81
|
+
"setuid_dsarch" = "rda_python_dsarch.dsarch:main"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`pip install` then places `setuid_dsarch` in the environment's `bin/` directory
|
|
85
|
+
automatically. `pywrapper-install -l/--link` creates the symlink
|
|
86
|
+
`dsarch -> pywrapper`; running `dsarch` goes through the setuid wrapper, which
|
|
87
|
+
execs `setuid_dsarch` as CommonUser.
|
|
88
|
+
|
|
89
|
+
The `main()` of each wrapped program (e.g. `rda_python_dsarch/dsarch.py`) must
|
|
90
|
+
also call `show_setup_guide()` at the top of `main()`, passing an instance of
|
|
91
|
+
the program's class along with the package name and list of setuid program
|
|
92
|
+
names:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
def main():
|
|
96
|
+
from rda_python_setuid.setup_guide import show_setup_guide
|
|
97
|
+
object = DsArch()
|
|
98
|
+
show_setup_guide(object, 'rda_python_dsarch', ['dsarch'])
|
|
99
|
+
...
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
When `setuid_dsarch` is invoked directly (before pywrapper symlinks are set
|
|
103
|
+
up, so euid ≠ CommonUser), `show_setup_guide()` prints the shared setuid setup
|
|
104
|
+
guide and exits. When invoked via the `dsarch -> pywrapper` symlink (euid =
|
|
105
|
+
CommonUser), `get_command()` strips the `setuid_` prefix, the check inside
|
|
106
|
+
`show_setup_guide()` fails, and the program runs normally.
|
|
107
|
+
|
|
108
|
+
## Environment setup
|
|
109
|
+
|
|
110
|
+
Create a Python environment first; package installs in the next section run
|
|
111
|
+
inside whichever environment you activate here.
|
|
112
|
+
|
|
113
|
+
### Option A — Python venv (DECS machines)
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
python3 -m venv $ENVHOME # e.g. /glade/u/home/gdexdata/gdexmsenv
|
|
117
|
+
source $ENVHOME/bin/activate
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Option B — Conda (DAV/Casper)
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
conda create --prefix $ENVHOME python=3.12 # e.g. /glade/work/gdexdata/conda-envs/pg-gdex
|
|
124
|
+
conda activate $ENVHOME
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Installing rda-python-setuid
|
|
128
|
+
|
|
129
|
+
Pick whichever install mode fits your workflow. All four pull in the
|
|
130
|
+
transitive dependency (`rda_python_common`) automatically. Once installed,
|
|
131
|
+
the `pywrapper-install` CLI is available for the setuid wiring steps below.
|
|
132
|
+
|
|
133
|
+
For local development, clone this repo alongside your project and install it
|
|
134
|
+
in editable mode so that changes are picked up without re-installing:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
git clone https://github.com/NCAR/rda-python-setuid.git
|
|
138
|
+
cd rda-python-setuid
|
|
139
|
+
pip install -e .
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
To test a specific branch (e.g. an in-progress feature or fix branch), pass
|
|
143
|
+
`-b/--branch` to `git clone`:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
git clone -b <branch-name> https://github.com/NCAR/rda-python-setuid.git
|
|
147
|
+
cd rda-python-setuid
|
|
148
|
+
pip install -e .
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
For a regular (non-editable) install from a checkout:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
pip install /path/to/rda-python-setuid
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
For a production install on a system that uses the published distribution:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
pip install rda_python_setuid
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Setuid wrapper setup
|
|
164
|
+
|
|
165
|
+
With `rda_python_setuid` installed in the active environment, run
|
|
166
|
+
`pywrapper-install` with no arguments to display the full user guide:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
pywrapper-install
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Full setuid setup (requires sudo access to CommonUser)
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# 1. Install the target package (pulls in rda_python_setuid automatically):
|
|
176
|
+
pip install rda_python_dsarch
|
|
177
|
+
|
|
178
|
+
# 2. Compile pywrapper C binary (once per environment):
|
|
179
|
+
pywrapper-install -c|--compile
|
|
180
|
+
|
|
181
|
+
# 3. Wire up each program as a setuid entry (specify name or use 'all'):
|
|
182
|
+
pywrapper-install -l|--link dsarch
|
|
183
|
+
pywrapper-install -l|--link all # auto-link every setuid_* entry not yet linked
|
|
184
|
+
|
|
185
|
+
# 4. Optionally, install a pgstart_<loginname> binary so <loginname> (any user
|
|
186
|
+
# in the same group as PGLOG['COMMONUSER']) can run commands as themselves
|
|
187
|
+
# via the setuid wrapper. Same command in both cases — only the invoker
|
|
188
|
+
# differs:
|
|
189
|
+
#
|
|
190
|
+
# 4a. If PGLOG['ADMINUSER'] (default zji) can `sudo -u <loginname>`, the
|
|
191
|
+
# admin sets it up on the user's behalf:
|
|
192
|
+
pywrapper-install -p|--pgstart -n|--username <loginname>
|
|
193
|
+
#
|
|
194
|
+
# 4b. Otherwise <loginname> runs the same command themselves (no sudo
|
|
195
|
+
# from ADMINUSER required, since they already are <loginname>):
|
|
196
|
+
pywrapper-install -p|--pgstart -n|--username <loginname>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Update an existing installation (no sudo required)
|
|
200
|
+
|
|
201
|
+
When the package is upgraded and a new `pywrapper.c` is bundled, use `-u/--update`
|
|
202
|
+
to recompile and reinstall all setuid binaries without needing `sudo`. The existing
|
|
203
|
+
`pgstart_*` binaries in `bin/` are used to perform the privileged operations:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
pywrapper-install -u|--update [-n|--username gdexdata] [-e|--envhome $ENVHOME]
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Simple install (no sudo required, runs as current user)
|
|
210
|
+
|
|
211
|
+
Users who do not need the setuid mechanism can skip steps 2–4 and create a
|
|
212
|
+
direct symlink from `dsarch` to `setuid_dsarch`:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
pip install rda_python_dsarch
|
|
216
|
+
pywrapper-install -l|--link dsarch -s|--simple
|
|
217
|
+
pywrapper-install -l|--link all -s|--simple # or link all setuid_* entries at once
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### cmwrapper (Mode 3, for callers outside the CommonUser group)
|
|
221
|
+
|
|
222
|
+
`pywrapper` is installed `4750`, so only members of the common user's group can
|
|
223
|
+
execute it. That group restriction is what makes it safe for `pywrapper` to pick
|
|
224
|
+
the program to run from `basename(argv[0])`: anyone who can execute it can already
|
|
225
|
+
reach every `setuid_*` entry in `bin/` just by naming the symlink differently.
|
|
226
|
+
|
|
227
|
+
`cmwrapper` is for the opposite case — letting users who are **not** in the common
|
|
228
|
+
user's group run one specific program as the common user. It is installed `4755`,
|
|
229
|
+
i.e. executable by everyone, so nothing about what it runs may come from the caller:
|
|
230
|
+
|
|
231
|
+
- the absolute path of the script to exec, and the program name, are baked into the
|
|
232
|
+
binary at compile time, so it cannot be symlinked under another name to reach a
|
|
233
|
+
different program;
|
|
234
|
+
- the environment is replaced with a fixed whitelist (`HOME`, `USER`, `LOGNAME`,
|
|
235
|
+
`TERM`, `LANG`, `TZ`, a fixed `PATH` and `PYTHONNOUSERSITE=1`), so `PYTHONPATH`,
|
|
236
|
+
`PYTHONHOME`, `LD_PRELOAD`, `LD_LIBRARY_PATH` and the `PGLOG` path variables
|
|
237
|
+
(`DSDHOME`, `DSSHOME`, `LOGPATH`, `COMMONUSER`, ...) cannot be used to run
|
|
238
|
+
arbitrary code, or redirect where files are written, as the common user.
|
|
239
|
+
|
|
240
|
+
One binary is compiled per wrapped program:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# Install bin/gdexdrop as a 4755 binary that execs bin/setuid_gdexdrop:
|
|
244
|
+
pywrapper-install -m|--cmlink gdexdrop
|
|
245
|
+
|
|
246
|
+
# Same, but installed into a common area already on everyone's PATH:
|
|
247
|
+
pywrapper-install -m|--cmlink gdexdrop -d|--destdir /glade/u/home/gdexdata/bin
|
|
248
|
+
|
|
249
|
+
# Point it at a script somewhere other than bin/setuid_gdexdrop:
|
|
250
|
+
pywrapper-install -m|--cmlink gdexdrop -t|--target /path/to/setuid_gdexdrop
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Only wrap a program that does its own authorization and confines where it writes,
|
|
254
|
+
such as `gdexdrop`, which checks its caller against an access list and copies only
|
|
255
|
+
into the requested dataset directory. Never wrap a general purpose program such as
|
|
256
|
+
`gdexcp`: at `4755` that would let any user on the machine read or overwrite any
|
|
257
|
+
file of the common user.
|
|
258
|
+
|
|
259
|
+
## Runtime flow
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
user runs: dsarch [args]
|
|
263
|
+
| (symlink -> pywrapper, setuid bit -> EUID=gdexdata)
|
|
264
|
+
pywrapper.c: execv(bin/setuid_dsarch, args)
|
|
265
|
+
setuid_dsarch: calls dsarch:main() as gdexdata
|
|
266
|
+
|
|
267
|
+
user runs: gdexdrop [args]
|
|
268
|
+
| (dedicated 4755 binary, setuid bit -> EUID=gdexdata)
|
|
269
|
+
cmwrapper.c: execve(bin/setuid_gdexdrop, args, sanitized env)
|
|
270
|
+
setuid_gdexdrop: calls gdexdrop:main() as gdexdata
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Github
|
|
274
|
+
|
|
275
|
+
<https://github.com/NCAR/rda-python-setuid>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
rda_python_setuid/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
2
|
+
rda_python_setuid/cmwrapper.c,sha256=37pRJSOsC88r6muH4IQZhGfN6wcENUd0o65-5TPdY78,3536
|
|
3
|
+
rda_python_setuid/install.py,sha256=H2km3ei_BAC3eC4_6x399gQcCmt0ESfjrlozzEMY0GA,14872
|
|
4
|
+
rda_python_setuid/install.usg,sha256=-z15I_2ytKShXyMNoz4ei6XARV807R5SoZfamheZrHM,9198
|
|
5
|
+
rda_python_setuid/pgstart.py,sha256=tYCOTJedqE5Lc6hUD2tyQparsOK9VGJ2alHHLNWqF20,3968
|
|
6
|
+
rda_python_setuid/pywrapper.c,sha256=Dp9EOFU9IShduQ4WMeFRiiIJLQvgZHsiQlc89vJgtII,4190
|
|
7
|
+
rda_python_setuid/pywrapper.py,sha256=7RHpWeyHGLP_7Vx3KJ_yy-3qYObYg_ar6meuworA0MA,3023
|
|
8
|
+
rda_python_setuid/setuid_setup.usg,sha256=nJaS08GtlCQmXiy-udp6cxCFgZZJuBaZ36DicUn5Q7I,2915
|
|
9
|
+
rda_python_setuid/setup_guide.py,sha256=thdTlYJwhID63G7G9NWte0qeXwH6ytQPLFbjesWodNQ,2158
|
|
10
|
+
rda_python_setuid-3.0.1.dist-info/licenses/LICENSE,sha256=1dck4EAQwv8QweDWCXDx-4Or0S8YwiCstaso_H57Pno,1097
|
|
11
|
+
rda_python_setuid-3.0.1.dist-info/METADATA,sha256=QzPPHYBqLKoKi6YhtGiv8DHRNHA5q1_R06ihwMEQo84,10714
|
|
12
|
+
rda_python_setuid-3.0.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
13
|
+
rda_python_setuid-3.0.1.dist-info/entry_points.txt,sha256=mWZUCa2KYzGsYVa33M7W03CY9SMsJYsywlIDF-4iMaQ,165
|
|
14
|
+
rda_python_setuid-3.0.1.dist-info/top_level.txt,sha256=ONMhKLagyTBktuz5dTyipSnRC0YhomTMs8eFRjM9kHQ,18
|
|
15
|
+
rda_python_setuid-3.0.1.dist-info/RECORD,,
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: rda_python_setuid
|
|
3
|
-
Version: 1.0.9
|
|
4
|
-
Summary: RDA Python Package to setuid for program executions as an effective or common user
|
|
5
|
-
Author-email: Zaihua Ji <zji@ucar.edu>
|
|
6
|
-
Project-URL: Homepage, https://github.com/NCAR/rda-python-setuid
|
|
7
|
-
Classifier: Programming Language :: Python :: 3
|
|
8
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
-
Classifier: Operating System :: OS Independent
|
|
10
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
-
Requires-Python: >=3.7
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
License-File: LICENSE
|
|
14
|
-
Requires-Dist: rda_python_common
|
|
15
|
-
Dynamic: license-file
|
|
16
|
-
|
|
17
|
-
RDA Python package, including a C code wrapper, to execute commandline applications
|
|
18
|
-
via setuid for effective and common user names.
|
|
19
|
-
|
|
20
|
-
## Overview
|
|
21
|
-
|
|
22
|
-
`rda_python_setuid` provides a C binary (`pywrapper`) that acquires a setuid effective
|
|
23
|
-
user, then `execv`s a Python entry point script. This allows Python programs to run
|
|
24
|
-
as a designated common user (e.g. `gdexdata`) without requiring `sudo` access.
|
|
25
|
-
|
|
26
|
-
Two modes are supported:
|
|
27
|
-
|
|
28
|
-
- **Mode 1 (CommonUser program):** a symlink `dsarch -> pywrapper` runs `setuid_dsarch`
|
|
29
|
-
as the common user.
|
|
30
|
-
- **Mode 2 (pgstart specialist):** a copy `pgstart_zji` runs any command as specialist
|
|
31
|
-
`zji` via `pgstart.py`, restricted to authorized users.
|
|
32
|
-
|
|
33
|
-
Two Python entry points are packaged alongside the C wrapper:
|
|
34
|
-
|
|
35
|
-
- **`pywrapper.py`** — the default fallback target executed when `pywrapper.c`
|
|
36
|
-
cannot resolve a matching `setuid_<program>` entry point. Acquires the
|
|
37
|
-
effective UID via `PgLOG.set_suid()`, prints the caller's real and effective
|
|
38
|
-
user names, and shows the `pyproject.toml` snippet plus the
|
|
39
|
-
`pywrapper-install -l <program>` command needed to wrap a new script.
|
|
40
|
-
Diagnostic flags `-env`, `-inc`, and `-plg` dump the environment variables,
|
|
41
|
-
`sys.path`, and `PGLOG` dictionary respectively — handy for verifying the
|
|
42
|
-
setuid environment before wiring up a real program.
|
|
43
|
-
|
|
44
|
-
- **`pgstart.py`** — the Mode 2 launcher invoked through a `pgstart_<USER>`
|
|
45
|
-
copy of `pywrapper`. Reads the real/effective UIDs from `PGLOG`, then
|
|
46
|
-
permits execution only if the real user matches the effective user or the
|
|
47
|
-
shared GDEX common user (`PGLOG['GDEXUSER']`); unauthorized callers receive
|
|
48
|
-
an informational message and exit. After authorization it parses leading
|
|
49
|
-
flag tokens — `-bg` (background via `subprocess.Popen`), `-fg` (explicit
|
|
50
|
-
foreground, default), `-cwd <dir>` (chdir before exec), and the same
|
|
51
|
-
`-env`/`-inc`/`-plg` diagnostics as `pywrapper.py` — and then runs the
|
|
52
|
-
remaining arguments as a command (`subprocess.run`/`Popen`) under the
|
|
53
|
-
effective UID, logging a host/program/timestamp/user line to `pgstart.log`.
|
|
54
|
-
|
|
55
|
-
## Dependency requirement
|
|
56
|
-
|
|
57
|
-
Any Python package whose programs are to be run via the setuid mechanism must declare
|
|
58
|
-
`rda_python_setuid` as a dependency in its `pyproject.toml`:
|
|
59
|
-
|
|
60
|
-
```toml
|
|
61
|
-
[project]
|
|
62
|
-
dependencies = [
|
|
63
|
-
"rda_python_setuid",
|
|
64
|
-
...
|
|
65
|
-
]
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
It must also register each wrapped program's connector entry point with a `setuid_`
|
|
69
|
-
prefix:
|
|
70
|
-
|
|
71
|
-
```toml
|
|
72
|
-
[project.scripts]
|
|
73
|
-
"setuid_dsarch" = "rda_python_dsarch.dsarch:main"
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
`pip install` then places `setuid_dsarch` in the environment's `bin/` directory
|
|
77
|
-
automatically. `pywrapper-install -l/--link` creates the symlink
|
|
78
|
-
`dsarch -> pywrapper`; running `dsarch` goes through the setuid wrapper, which
|
|
79
|
-
execs `setuid_dsarch` as CommonUser.
|
|
80
|
-
|
|
81
|
-
## Environment setup
|
|
82
|
-
|
|
83
|
-
### Option A — Python venv (DECS machines)
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
python3 -m venv $ENVHOME # e.g. /glade/u/home/gdexdata/gdexmsenv
|
|
87
|
-
source $ENVHOME/bin/activate
|
|
88
|
-
pip install rda_python_setuid rda_python_dsarch ...
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
### Option B — Conda (DAV/Casper)
|
|
92
|
-
|
|
93
|
-
```bash
|
|
94
|
-
conda create -n pg-gdex python=3.10
|
|
95
|
-
conda activate pg-gdex
|
|
96
|
-
pip install rda_python_setuid rda_python_dsarch ...
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
The conda environment is typically at `/glade/work/gdexdata/conda-envs/pg-gdex`.
|
|
100
|
-
|
|
101
|
-
## Installation
|
|
102
|
-
|
|
103
|
-
After setting up the environment and installing packages, run `pywrapper-install`
|
|
104
|
-
with no arguments to display the full user guide:
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
pywrapper-install
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### Full setuid setup (requires sudo access to CommonUser)
|
|
111
|
-
|
|
112
|
-
```bash
|
|
113
|
-
# 1. Install the target package (pulls in rda_python_setuid automatically):
|
|
114
|
-
pip install rda_python_dsarch
|
|
115
|
-
|
|
116
|
-
# 2. Compile pywrapper C binary (once per environment):
|
|
117
|
-
pywrapper-install -c|--compile
|
|
118
|
-
|
|
119
|
-
# 3. Wire up each program as a setuid entry:
|
|
120
|
-
pywrapper-install -l|--link dsarch
|
|
121
|
-
|
|
122
|
-
# 4. Optionally, allow a specialist to run commands as themselves:
|
|
123
|
-
pywrapper-install -p|--pgstart -u|--user zji
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### Simple install (no sudo required, runs as current user)
|
|
127
|
-
|
|
128
|
-
Users who do not need the setuid mechanism can skip steps 2–4 and create a
|
|
129
|
-
direct symlink from `dsarch` to `setuid_dsarch`:
|
|
130
|
-
|
|
131
|
-
```bash
|
|
132
|
-
pip install rda_python_dsarch
|
|
133
|
-
pywrapper-install -l|--link dsarch -s|--simple
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
## Runtime flow
|
|
137
|
-
|
|
138
|
-
```
|
|
139
|
-
user runs: dsarch [args]
|
|
140
|
-
| (symlink -> pywrapper, setuid bit -> EUID=gdexdata)
|
|
141
|
-
pywrapper.c: execv(bin/setuid_dsarch, args)
|
|
142
|
-
setuid_dsarch: calls dsarch:main() as gdexdata
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
## Github
|
|
146
|
-
|
|
147
|
-
<https://github.com/NCAR/rda-python-setuid>
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
rda_python_setuid/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
2
|
-
rda_python_setuid/install.py,sha256=0tY0zo7WRyQMm0iUi1_XYyjR8SDVU17R729ehATZNKw,6071
|
|
3
|
-
rda_python_setuid/install.usg,sha256=m0tgMS_Jh9-1wxCkbbgDNVJb3MGlMSp0Ecnwc9UeQcI,5582
|
|
4
|
-
rda_python_setuid/pgstart.py,sha256=qM-ntQbYfEK1VYigXbNFBDOfW9nyaezjsjyomauMTbw,3945
|
|
5
|
-
rda_python_setuid/pywrapper.c,sha256=Dp9EOFU9IShduQ4WMeFRiiIJLQvgZHsiQlc89vJgtII,4190
|
|
6
|
-
rda_python_setuid/pywrapper.py,sha256=pyQZfITTpxQ_lVkWc_0ZJRF_sZnq8NQdOvR5lObu6mw,2898
|
|
7
|
-
rda_python_setuid-1.0.9.dist-info/licenses/LICENSE,sha256=1dck4EAQwv8QweDWCXDx-4Or0S8YwiCstaso_H57Pno,1097
|
|
8
|
-
rda_python_setuid-1.0.9.dist-info/METADATA,sha256=jFM7488gCNataDKePCLkILCVPD1xZ0i_2iAOSx46piA,5027
|
|
9
|
-
rda_python_setuid-1.0.9.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
10
|
-
rda_python_setuid-1.0.9.dist-info/entry_points.txt,sha256=mWZUCa2KYzGsYVa33M7W03CY9SMsJYsywlIDF-4iMaQ,165
|
|
11
|
-
rda_python_setuid-1.0.9.dist-info/top_level.txt,sha256=ONMhKLagyTBktuz5dTyipSnRC0YhomTMs8eFRjM9kHQ,18
|
|
12
|
-
rda_python_setuid-1.0.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|