rda-python-setuid 1.0.8__py3-none-any.whl → 3.0.0__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/install.py +167 -44
- rda_python_setuid/install.usg +54 -23
- 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.0.dist-info/METADATA +227 -0
- rda_python_setuid-3.0.0.dist-info/RECORD +14 -0
- rda_python_setuid-1.0.8.dist-info/METADATA +0 -147
- rda_python_setuid-1.0.8.dist-info/RECORD +0 -12
- {rda_python_setuid-1.0.8.dist-info → rda_python_setuid-3.0.0.dist-info}/WHEEL +0 -0
- {rda_python_setuid-1.0.8.dist-info → rda_python_setuid-3.0.0.dist-info}/entry_points.txt +0 -0
- {rda_python_setuid-1.0.8.dist-info → rda_python_setuid-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {rda_python_setuid-1.0.8.dist-info → rda_python_setuid-3.0.0.dist-info}/top_level.txt +0 -0
rda_python_setuid/install.py
CHANGED
|
@@ -15,24 +15,31 @@
|
|
|
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
|
+
#
|
|
29
35
|
# Convention for wrapped programs:
|
|
30
36
|
# The target package must register its connector entry point with a setuid_ prefix:
|
|
31
37
|
# [project.scripts]
|
|
32
38
|
# "setuid_dsarch" = "rda_python_dsarch.dsarch:main"
|
|
33
39
|
# pip install places setuid_dsarch in the bin dir automatically.
|
|
34
|
-
# pywrapper-install --link dsarch
|
|
35
|
-
#
|
|
40
|
+
# pywrapper-install --link dsarch creates the symlink dsarch -> pywrapper, so
|
|
41
|
+
# users invoking dsarch go through the setuid wrapper which execs setuid_dsarch
|
|
42
|
+
# as CommonUser.
|
|
36
43
|
# pywrapper-install --link dsarch --simple creates dsarch -> setuid_dsarch directly,
|
|
37
44
|
# skipping setuid; the program runs as the current user.
|
|
38
45
|
#
|
|
@@ -76,7 +83,7 @@ def main():
|
|
|
76
83
|
help="Path to the venv root directory containing bin/ (default: parent of the current Python executable's bin/ dir)"
|
|
77
84
|
)
|
|
78
85
|
parser.add_argument(
|
|
79
|
-
'-
|
|
86
|
+
'-n', '--username', default=None,
|
|
80
87
|
help="User name to own the setuid binary (default: current login user for -p/--pgstart, gdexdata otherwise)"
|
|
81
88
|
)
|
|
82
89
|
parser.add_argument(
|
|
@@ -94,60 +101,105 @@ def main():
|
|
|
94
101
|
)
|
|
95
102
|
group.add_argument(
|
|
96
103
|
'-l', '--link', metavar='PROGRAM',
|
|
97
|
-
help="Create symlink PROGRAM -> pywrapper for running a fixed program as CommonUser (Mode 1)"
|
|
104
|
+
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"
|
|
105
|
+
)
|
|
106
|
+
group.add_argument(
|
|
107
|
+
'-u', '--update', action='store_true',
|
|
108
|
+
help="Update an existing installation: recompile pywrapper and reinstall all pgstart_USER setuid binaries"
|
|
98
109
|
)
|
|
99
110
|
args = parser.parse_args()
|
|
100
111
|
|
|
101
|
-
if not (args.compile or args.pgstart or args.link):
|
|
112
|
+
if not (args.compile or args.pgstart or args.link or args.update):
|
|
102
113
|
show_usage()
|
|
103
114
|
|
|
104
|
-
if args.
|
|
115
|
+
if args.username is None and not args.simple:
|
|
105
116
|
import pwd
|
|
106
117
|
if args.pgstart:
|
|
107
|
-
args.
|
|
118
|
+
args.username = pwd.getpwuid(os.getuid()).pw_name
|
|
108
119
|
else:
|
|
109
|
-
args.
|
|
120
|
+
args.username = 'gdexdata'
|
|
110
121
|
|
|
111
122
|
bindir = os.path.join(args.envhome, 'bin') if args.envhome else get_bindir()
|
|
112
123
|
pywrapper = os.path.join(bindir, 'pywrapper')
|
|
113
124
|
|
|
114
125
|
if args.link:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if os.path.
|
|
123
|
-
print("
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
126
|
+
# For appname -> pywrapper links, run `ln -s` via pgstart_<commonuser> so
|
|
127
|
+
# the resulting symlink is owned by the common user (pywrapper owner).
|
|
128
|
+
pgstart_common = None
|
|
129
|
+
if not args.simple and os.path.exists(pywrapper):
|
|
130
|
+
import pwd
|
|
131
|
+
common_user = pwd.getpwuid(os.stat(pywrapper).st_uid).pw_name
|
|
132
|
+
pgstart_common = os.path.join(bindir, 'pgstart_' + common_user)
|
|
133
|
+
if not os.path.exists(pgstart_common):
|
|
134
|
+
print("Error: {} not found. Run pywrapper-install --pgstart --username {} first.".format(pgstart_common, common_user))
|
|
135
|
+
sys.exit(1)
|
|
136
|
+
|
|
137
|
+
if args.link.lower() == 'all':
|
|
138
|
+
# Discover all setuid_* entries in bindir and link any that are missing
|
|
139
|
+
appnames = sorted(
|
|
140
|
+
f[len('setuid_'):] for f in os.listdir(bindir) if f.startswith('setuid_')
|
|
141
|
+
)
|
|
142
|
+
if not appnames:
|
|
143
|
+
print("No setuid_* entries found in {}".format(bindir))
|
|
144
|
+
for appname in appnames:
|
|
145
|
+
target = os.path.join(bindir, appname)
|
|
146
|
+
script = os.path.join(bindir, 'setuid_' + appname)
|
|
147
|
+
if args.simple:
|
|
148
|
+
if os.path.lexists(target):
|
|
149
|
+
print("Already exists: {}".format(target))
|
|
150
|
+
else:
|
|
151
|
+
os.symlink(script, target)
|
|
152
|
+
print("Created: {} -> setuid_{}".format(target, appname))
|
|
153
|
+
else:
|
|
154
|
+
if os.path.lexists(target):
|
|
155
|
+
print("Already exists: {}".format(target))
|
|
156
|
+
else:
|
|
157
|
+
run([pgstart_common, 'ln', '-s', pywrapper, target])
|
|
158
|
+
print("Created: {} -> pywrapper".format(target))
|
|
127
159
|
else:
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if os.path.
|
|
131
|
-
print("
|
|
160
|
+
target = os.path.join(bindir, args.link)
|
|
161
|
+
script = os.path.join(bindir, 'setuid_{}'.format(args.link))
|
|
162
|
+
if not os.path.exists(script):
|
|
163
|
+
print("Error: {} not found. Install the package that provides it first.".format(script))
|
|
164
|
+
sys.exit(1)
|
|
165
|
+
if args.simple:
|
|
166
|
+
# Simple install: symlink PROGRAM -> setuid_PROGRAM, no setuid, runs as current user.
|
|
167
|
+
if os.path.lexists(target):
|
|
168
|
+
print("Already exists: {}".format(target))
|
|
169
|
+
else:
|
|
170
|
+
os.symlink(script, target)
|
|
171
|
+
print("Created: {} -> setuid_{}".format(target, args.link))
|
|
132
172
|
else:
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
print("Locked: {} (chmod 700, owned by {})".format(script, args.user))
|
|
173
|
+
# Mode 1: symlink PROGRAM -> pywrapper. setuid_PROGRAM is left with its
|
|
174
|
+
# default ownership/permissions so it can be loaded and executed normally.
|
|
175
|
+
if os.path.lexists(target):
|
|
176
|
+
print("Already exists: {}".format(target))
|
|
177
|
+
else:
|
|
178
|
+
run([pgstart_common, 'ln', '-s', pywrapper, target])
|
|
179
|
+
print("Created: {} -> pywrapper".format(target))
|
|
141
180
|
|
|
142
181
|
elif args.pgstart:
|
|
143
|
-
# Mode 2:
|
|
182
|
+
# Mode 2: create pgstart_USER with setuid owned by USER.
|
|
183
|
+
# When USER already owns pywrapper (i.e. the common user), pywrapper is
|
|
184
|
+
# already setuid owned by USER, so a symlink is sufficient; otherwise
|
|
185
|
+
# copy pywrapper and chmod 4750 as USER.
|
|
144
186
|
if not os.path.exists(pywrapper):
|
|
145
|
-
print("Error: {} not found. Run pywrapper-install --
|
|
187
|
+
print("Error: {} not found. Run pywrapper-install --compile --username COMMONUSER first.".format(pywrapper))
|
|
146
188
|
sys.exit(1)
|
|
147
|
-
target = os.path.join(bindir, 'pgstart_{}'.format(args.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
189
|
+
target = os.path.join(bindir, 'pgstart_{}'.format(args.username))
|
|
190
|
+
import pwd
|
|
191
|
+
pywrapper_owner = pwd.getpwuid(os.stat(pywrapper).st_uid).pw_name
|
|
192
|
+
if args.username == pywrapper_owner:
|
|
193
|
+
if os.path.lexists(target):
|
|
194
|
+
os.remove(target)
|
|
195
|
+
os.symlink(pywrapper, target)
|
|
196
|
+
print("Linked: {} -> pywrapper (setuid, owned by {})".format(target, args.username))
|
|
197
|
+
else:
|
|
198
|
+
curuser = pwd.getpwuid(os.getuid()).pw_name
|
|
199
|
+
sudo_prefix = [] if curuser == args.username else ['sudo', '-u', args.username]
|
|
200
|
+
run(sudo_prefix + ['cp', pywrapper, target])
|
|
201
|
+
run(sudo_prefix + ['chmod', '4750', target])
|
|
202
|
+
print("Installed: {} (setuid, owned by {})".format(target, args.username))
|
|
151
203
|
|
|
152
204
|
elif args.compile:
|
|
153
205
|
# Compile pywrapper.c and install pywrapper with setuid
|
|
@@ -155,9 +207,80 @@ def main():
|
|
|
155
207
|
src_dest = os.path.join(bindir, 'pywrapper.c')
|
|
156
208
|
shutil.copy(src, src_dest)
|
|
157
209
|
print("Copied: {}".format(src_dest))
|
|
158
|
-
run(['sudo', '-u', args.
|
|
159
|
-
run(['sudo', '-u', args.
|
|
160
|
-
print("Installed: {} (setuid, owned by {})".format(pywrapper, args.
|
|
210
|
+
run(['sudo', '-u', args.username, 'gcc', '-o', pywrapper, src_dest])
|
|
211
|
+
run(['sudo', '-u', args.username, 'chmod', '4750', pywrapper])
|
|
212
|
+
print("Installed: {} (setuid, owned by {})".format(pywrapper, args.username))
|
|
213
|
+
|
|
214
|
+
elif args.update:
|
|
215
|
+
# Update an existing installation: recompile pywrapper and reinstall all setuid binaries
|
|
216
|
+
pgstart_files = sorted(f for f in os.listdir(bindir) if f.startswith('pgstart_'))
|
|
217
|
+
if not pgstart_files:
|
|
218
|
+
print("Error: No pgstart_* binaries found in {}".format(bindir))
|
|
219
|
+
sys.exit(1)
|
|
220
|
+
if not os.path.exists(pywrapper):
|
|
221
|
+
print("Error: {} not found.".format(pywrapper))
|
|
222
|
+
sys.exit(1)
|
|
223
|
+
|
|
224
|
+
gdexuser = args.username
|
|
225
|
+
pgstart_gdexdata = os.path.join(bindir, 'pgstart_' + gdexuser)
|
|
226
|
+
if not os.path.exists(pgstart_gdexdata):
|
|
227
|
+
print("Error: {} not found.".format(pgstart_gdexdata))
|
|
228
|
+
sys.exit(1)
|
|
229
|
+
|
|
230
|
+
update_tmp = os.path.join(bindir, 'update_tmp')
|
|
231
|
+
os.makedirs(update_tmp, exist_ok=True)
|
|
232
|
+
print("Created: {}".format(update_tmp))
|
|
233
|
+
|
|
234
|
+
# Symlink pgstart.py into update_tmp so pywrapper instances running from
|
|
235
|
+
# update_tmp can find it via the fpath/pgstart.py fallback lookup.
|
|
236
|
+
bindir_pgstart_py = os.path.join(bindir, 'pgstart.py')
|
|
237
|
+
update_pgstart_py = os.path.join(update_tmp, 'pgstart.py')
|
|
238
|
+
if not os.path.lexists(update_pgstart_py):
|
|
239
|
+
os.symlink(bindir_pgstart_py, update_pgstart_py)
|
|
240
|
+
print("Linked: {} -> {}".format(update_pgstart_py, bindir_pgstart_py))
|
|
241
|
+
|
|
242
|
+
# Copy each non-gdex pgstart_USERNAME into update_tmp using itself, then chmod 4750
|
|
243
|
+
for fname in pgstart_files:
|
|
244
|
+
username = fname[len('pgstart_'):]
|
|
245
|
+
if username == gdexuser:
|
|
246
|
+
continue
|
|
247
|
+
src_pgstart = os.path.join(bindir, fname)
|
|
248
|
+
dst_pgstart = os.path.join(update_tmp, fname)
|
|
249
|
+
run([src_pgstart, 'cp', src_pgstart, update_tmp + '/'])
|
|
250
|
+
run([src_pgstart, 'chmod', '4750', dst_pgstart])
|
|
251
|
+
|
|
252
|
+
# Copy pywrapper into update_tmp as gdexdata, chmod, then hardlink as pgstart_gdexdata
|
|
253
|
+
update_pywrapper = os.path.join(update_tmp, 'pywrapper')
|
|
254
|
+
update_pgstart_gdexdata = os.path.join(update_tmp, 'pgstart_' + gdexuser)
|
|
255
|
+
run([pgstart_gdexdata, 'cp', pywrapper, update_tmp + '/'])
|
|
256
|
+
run([pgstart_gdexdata, 'chmod', '4750', update_pywrapper])
|
|
257
|
+
run([pgstart_gdexdata, 'ln', update_pywrapper, update_pgstart_gdexdata])
|
|
258
|
+
|
|
259
|
+
# Compile new pywrapper using update_tmp/pgstart_gdexdata
|
|
260
|
+
src = get_c_source()
|
|
261
|
+
src_dest = os.path.join(bindir, 'pywrapper.c')
|
|
262
|
+
shutil.copy(src, src_dest)
|
|
263
|
+
print("Copied: {}".format(src_dest))
|
|
264
|
+
run([update_pgstart_gdexdata, 'gcc', '-o', pywrapper, src_dest])
|
|
265
|
+
run([update_pgstart_gdexdata, 'chmod', '4750', pywrapper])
|
|
266
|
+
print("Compiled: {} (setuid, owned by {})".format(pywrapper, gdexuser))
|
|
267
|
+
|
|
268
|
+
# Recreate each pgstart_* in bindir using the corresponding update_tmp/pgstart_*
|
|
269
|
+
for fname in sorted(f for f in os.listdir(update_tmp) if f.startswith('pgstart_')):
|
|
270
|
+
username = fname[len('pgstart_'):]
|
|
271
|
+
update_pgstart = os.path.join(update_tmp, fname)
|
|
272
|
+
target = os.path.join(bindir, fname)
|
|
273
|
+
if username == gdexuser:
|
|
274
|
+
run([update_pgstart_gdexdata, 'ln', '-sf', pywrapper, target])
|
|
275
|
+
print("Linked: {} -> pywrapper (setuid, owned by {})".format(target, gdexuser))
|
|
276
|
+
else:
|
|
277
|
+
run([update_pgstart, 'cp', pywrapper, target])
|
|
278
|
+
run([update_pgstart, 'chmod', '4750', target])
|
|
279
|
+
print("Updated: {} (setuid, owned by {})".format(target, username))
|
|
280
|
+
|
|
281
|
+
# Clean up the temporary working directory
|
|
282
|
+
shutil.rmtree(update_tmp)
|
|
283
|
+
print("Removed: {}".format(update_tmp))
|
|
161
284
|
|
|
162
285
|
|
|
163
286
|
if __name__ == '__main__': main()
|
rda_python_setuid/install.usg
CHANGED
|
@@ -2,16 +2,17 @@
|
|
|
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
|
+
| -u|--update )
|
|
9
10
|
|
|
10
11
|
Run pywrapper-install with no arguments to display this user guide. Exactly
|
|
11
|
-
one of -c/--compile, -p/--pgstart,
|
|
12
|
-
action.
|
|
12
|
+
one of -c/--compile, -p/--pgstart, -l/--link, or -u/--update must be given
|
|
13
|
+
to perform an action.
|
|
13
14
|
|
|
14
|
-
- Option -
|
|
15
|
+
- Option -n or --username USERNAME
|
|
15
16
|
The user name to own the setuid binary. For Mode 1 (CommonUser program),
|
|
16
17
|
this is the common user (e.g. gdexdata). For Mode 2 (pgstart), this is
|
|
17
18
|
the specialist user (e.g. zji). Defaults to 'gdexdata' unless -p/--pgstart
|
|
@@ -31,19 +32,34 @@
|
|
|
31
32
|
|
|
32
33
|
- Option -p or --pgstart
|
|
33
34
|
Mode 2: copy the compiled pywrapper binary to pgstart_USER (owned by USER,
|
|
34
|
-
chmod 4750). Allows USER
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
chmod 4750). Allows USER (any login user in the same group as the
|
|
36
|
+
common user PGLOG['COMMONUSER']) to run arbitrary commands as themselves
|
|
37
|
+
via the setuid wrapper. Two paths for setting up pgstart_USER:
|
|
38
|
+
(a) if PGLOG['ADMINUSER'] (default zji) can 'sudo -u USER', the admin
|
|
39
|
+
runs 'pywrapper-install -p -n USER' on behalf of USER; or
|
|
40
|
+
(b) USER runs the same command themselves (no sudo required).
|
|
41
|
+
Cannot be combined with -c/--compile or -l/--link.
|
|
42
|
+
|
|
43
|
+
- Option -l or --link PROGRAM|all
|
|
44
|
+
Mode 1: create a symlink PROGRAM -> pywrapper in the bin/ directory so
|
|
45
|
+
that running PROGRAM invokes the setuid wrapper, which then execs
|
|
46
|
+
setuid_PROGRAM as CommonUser. setuid_PROGRAM keeps its default
|
|
47
|
+
ownership and permissions. Cannot be combined with -c/--compile or
|
|
48
|
+
-p/--pgstart.
|
|
49
|
+
Use 'all' instead of a program name to scan bin/ for every setuid_*
|
|
50
|
+
entry and add any missing PROGRAM -> pywrapper symlinks in one pass.
|
|
51
|
+
|
|
52
|
+
- Option -u or --update
|
|
53
|
+
Update an existing installation. Discovers all pgstart_* and pywrapper
|
|
54
|
+
binaries already present in bin/, saves them to a temporary update_tmp/
|
|
55
|
+
subdirectory, recompiles pywrapper using update_tmp/pgstart_COMMONUSER, and
|
|
56
|
+
reinstalls every pgstart_* binary from update_tmp. Use -n/--username to
|
|
57
|
+
specify the gdex common user (default: gdexdata).
|
|
42
58
|
|
|
43
59
|
- Option -s or --simple (use with -l/--link)
|
|
44
60
|
Simple install: create a symlink PROGRAM -> setuid_PROGRAM directly,
|
|
45
61
|
skipping the setuid mechanism entirely. The program runs as the current
|
|
46
|
-
user with no privilege change. -
|
|
62
|
+
user with no privilege change. -n/--username is not required with this option.
|
|
47
63
|
Useful for users who do not need or cannot set up the setuid wrapper.
|
|
48
64
|
|
|
49
65
|
Convention for wrapped programs:
|
|
@@ -66,8 +82,8 @@
|
|
|
66
82
|
"setuid_dsarch" = "rda_python_dsarch.dsarch:main"
|
|
67
83
|
|
|
68
84
|
pip install then places setuid_dsarch in the bin/ directory automatically.
|
|
69
|
-
Running pywrapper-install --link
|
|
70
|
-
pywrapper (running as CommonUser via its setuid bit)
|
|
85
|
+
Running pywrapper-install --link creates the symlink dsarch -> pywrapper;
|
|
86
|
+
pywrapper (running as CommonUser via its setuid bit) execs setuid_dsarch.
|
|
71
87
|
|
|
72
88
|
Environment Setup:
|
|
73
89
|
|
|
@@ -110,10 +126,14 @@
|
|
|
110
126
|
pywrapper-install -c
|
|
111
127
|
|
|
112
128
|
3. Wire up each program as a setuid entry:
|
|
113
|
-
pywrapper-install -l dsarch
|
|
129
|
+
pywrapper-install -l dsarch # one program
|
|
130
|
+
pywrapper-install -l all # or all setuid_* entries at once
|
|
114
131
|
|
|
115
|
-
4. Optionally,
|
|
116
|
-
|
|
132
|
+
4. Optionally, install a pgstart_<loginname> binary so <loginname> (any
|
|
133
|
+
user in the same group as PGLOG['COMMONUSER']) can run commands as
|
|
134
|
+
themselves. Either PGLOG['ADMINUSER'] (default zji, if it has
|
|
135
|
+
'sudo -u <loginname>'), or <loginname> directly, runs:
|
|
136
|
+
pywrapper-install -p -n <loginname>
|
|
117
137
|
|
|
118
138
|
Option B - Simple install (no sudo required, runs as current user):
|
|
119
139
|
|
|
@@ -121,7 +141,8 @@
|
|
|
121
141
|
pip install rda_python_dsarch
|
|
122
142
|
|
|
123
143
|
2. Create a direct symlink to the connector script:
|
|
124
|
-
pywrapper-install -l dsarch -s
|
|
144
|
+
pywrapper-install -l dsarch -s # one program
|
|
145
|
+
pywrapper-install -l all -s # or all setuid_* entries at once
|
|
125
146
|
|
|
126
147
|
Examples:
|
|
127
148
|
|
|
@@ -134,8 +155,18 @@
|
|
|
134
155
|
3. Wire up dsarch to run as gdexdata via pywrapper:
|
|
135
156
|
pywrapper-install -l dsarch
|
|
136
157
|
|
|
137
|
-
4.
|
|
138
|
-
pywrapper-install -
|
|
158
|
+
4. Wire up all setuid_* entries at once:
|
|
159
|
+
pywrapper-install -l all
|
|
139
160
|
|
|
140
|
-
5.
|
|
161
|
+
5. Install pgstart_zji so login user zji can run commands as themselves
|
|
162
|
+
via pgstart (run by ADMINUSER with 'sudo -u zji' available, or by zji):
|
|
163
|
+
pywrapper-install -p -n zji
|
|
164
|
+
|
|
165
|
+
6. Update an existing installation (recompile and reinstall all setuid binaries):
|
|
166
|
+
pywrapper-install -u
|
|
167
|
+
|
|
168
|
+
7. Simple install of dsarch for a user who does not need setuid:
|
|
141
169
|
pywrapper-install -l dsarch -s
|
|
170
|
+
|
|
171
|
+
8. Simple install of all setuid_* entries at once:
|
|
172
|
+
pywrapper-install -l all -s
|
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,227 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rda_python_setuid
|
|
3
|
+
Version: 3.0.0
|
|
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_<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
|
+
|
|
35
|
+
Two Python entry points are packaged alongside the C wrapper:
|
|
36
|
+
|
|
37
|
+
- **`pywrapper.py`** — the default fallback target executed when `pywrapper.c`
|
|
38
|
+
cannot resolve a matching `setuid_<program>` entry point. Acquires the
|
|
39
|
+
effective UID via `PgLOG.set_suid()`, prints the caller's real and effective
|
|
40
|
+
user names, and shows the `pyproject.toml` snippet plus the
|
|
41
|
+
`pywrapper-install -l <program>` command needed to wrap a new script.
|
|
42
|
+
Diagnostic flags `-env`, `-inc`, and `-plg` dump the environment variables,
|
|
43
|
+
`sys.path`, and `PGLOG` dictionary respectively — handy for verifying the
|
|
44
|
+
setuid environment before wiring up a real program.
|
|
45
|
+
|
|
46
|
+
- **`pgstart.py`** — the Mode 2 launcher invoked through a `pgstart_<loginname>`
|
|
47
|
+
copy of `pywrapper`. Reads the real/effective UIDs from `PGLOG`, then
|
|
48
|
+
permits execution only if the real user is in
|
|
49
|
+
`[PGLOG['ADMINUSER'], euser, PGLOG['COMMONUSER']]`
|
|
50
|
+
(i.e. the admin specialist `PGLOG['ADMINUSER']` — default `zji` — the
|
|
51
|
+
effective user themselves, or the shared common user); unauthorized callers receive
|
|
52
|
+
an informational message and exit. After authorization it parses leading
|
|
53
|
+
flag tokens — `-bg` (background via `subprocess.Popen`), `-fg` (explicit
|
|
54
|
+
foreground, default), `-cwd <dir>` (chdir before exec), and the same
|
|
55
|
+
`-env`/`-inc`/`-plg` diagnostics as `pywrapper.py` — and then runs the
|
|
56
|
+
remaining arguments as a command (`subprocess.run`/`Popen`) under the
|
|
57
|
+
effective UID, logging a host/program/timestamp/user line to `pgstart.log`.
|
|
58
|
+
|
|
59
|
+
## Dependency requirement
|
|
60
|
+
|
|
61
|
+
Any Python package whose programs are to be run via the setuid mechanism must declare
|
|
62
|
+
`rda_python_setuid` as a dependency in its `pyproject.toml`:
|
|
63
|
+
|
|
64
|
+
```toml
|
|
65
|
+
[project]
|
|
66
|
+
dependencies = [
|
|
67
|
+
"rda_python_setuid",
|
|
68
|
+
...
|
|
69
|
+
]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
It must also register each wrapped program's connector entry point with a `setuid_`
|
|
73
|
+
prefix:
|
|
74
|
+
|
|
75
|
+
```toml
|
|
76
|
+
[project.scripts]
|
|
77
|
+
"setuid_dsarch" = "rda_python_dsarch.dsarch:main"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`pip install` then places `setuid_dsarch` in the environment's `bin/` directory
|
|
81
|
+
automatically. `pywrapper-install -l/--link` creates the symlink
|
|
82
|
+
`dsarch -> pywrapper`; running `dsarch` goes through the setuid wrapper, which
|
|
83
|
+
execs `setuid_dsarch` as CommonUser.
|
|
84
|
+
|
|
85
|
+
The `main()` of each wrapped program (e.g. `rda_python_dsarch/dsarch.py`) must
|
|
86
|
+
also call `show_setup_guide()` at the top of `main()`, passing an instance of
|
|
87
|
+
the program's class along with the package name and list of setuid program
|
|
88
|
+
names:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
def main():
|
|
92
|
+
from rda_python_setuid.setup_guide import show_setup_guide
|
|
93
|
+
object = DsArch()
|
|
94
|
+
show_setup_guide(object, 'rda_python_dsarch', ['dsarch'])
|
|
95
|
+
...
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
When `setuid_dsarch` is invoked directly (before pywrapper symlinks are set
|
|
99
|
+
up, so euid ≠ CommonUser), `show_setup_guide()` prints the shared setuid setup
|
|
100
|
+
guide and exits. When invoked via the `dsarch -> pywrapper` symlink (euid =
|
|
101
|
+
CommonUser), `get_command()` strips the `setuid_` prefix, the check inside
|
|
102
|
+
`show_setup_guide()` fails, and the program runs normally.
|
|
103
|
+
|
|
104
|
+
## Environment setup
|
|
105
|
+
|
|
106
|
+
Create a Python environment first; package installs in the next section run
|
|
107
|
+
inside whichever environment you activate here.
|
|
108
|
+
|
|
109
|
+
### Option A — Python venv (DECS machines)
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
python3 -m venv $ENVHOME # e.g. /glade/u/home/gdexdata/gdexmsenv
|
|
113
|
+
source $ENVHOME/bin/activate
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Option B — Conda (DAV/Casper)
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
conda create --prefix $ENVHOME python=3.12 # e.g. /glade/work/gdexdata/conda-envs/pg-gdex
|
|
120
|
+
conda activate $ENVHOME
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Installing rda-python-setuid
|
|
124
|
+
|
|
125
|
+
Pick whichever install mode fits your workflow. All four pull in the
|
|
126
|
+
transitive dependency (`rda_python_common`) automatically. Once installed,
|
|
127
|
+
the `pywrapper-install` CLI is available for the setuid wiring steps below.
|
|
128
|
+
|
|
129
|
+
For local development, clone this repo alongside your project and install it
|
|
130
|
+
in editable mode so that changes are picked up without re-installing:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
git clone https://github.com/NCAR/rda-python-setuid.git
|
|
134
|
+
cd rda-python-setuid
|
|
135
|
+
pip install -e .
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
To test a specific branch (e.g. an in-progress feature or fix branch), pass
|
|
139
|
+
`-b/--branch` to `git clone`:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
git clone -b <branch-name> https://github.com/NCAR/rda-python-setuid.git
|
|
143
|
+
cd rda-python-setuid
|
|
144
|
+
pip install -e .
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
For a regular (non-editable) install from a checkout:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
pip install /path/to/rda-python-setuid
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
For a production install on a system that uses the published distribution:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
pip install rda_python_setuid
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Setuid wrapper setup
|
|
160
|
+
|
|
161
|
+
With `rda_python_setuid` installed in the active environment, run
|
|
162
|
+
`pywrapper-install` with no arguments to display the full user guide:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
pywrapper-install
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Full setuid setup (requires sudo access to CommonUser)
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# 1. Install the target package (pulls in rda_python_setuid automatically):
|
|
172
|
+
pip install rda_python_dsarch
|
|
173
|
+
|
|
174
|
+
# 2. Compile pywrapper C binary (once per environment):
|
|
175
|
+
pywrapper-install -c|--compile
|
|
176
|
+
|
|
177
|
+
# 3. Wire up each program as a setuid entry (specify name or use 'all'):
|
|
178
|
+
pywrapper-install -l|--link dsarch
|
|
179
|
+
pywrapper-install -l|--link all # auto-link every setuid_* entry not yet linked
|
|
180
|
+
|
|
181
|
+
# 4. Optionally, install a pgstart_<loginname> binary so <loginname> (any user
|
|
182
|
+
# in the same group as PGLOG['COMMONUSER']) can run commands as themselves
|
|
183
|
+
# via the setuid wrapper. Same command in both cases — only the invoker
|
|
184
|
+
# differs:
|
|
185
|
+
#
|
|
186
|
+
# 4a. If PGLOG['ADMINUSER'] (default zji) can `sudo -u <loginname>`, the
|
|
187
|
+
# admin sets it up on the user's behalf:
|
|
188
|
+
pywrapper-install -p|--pgstart -n|--username <loginname>
|
|
189
|
+
#
|
|
190
|
+
# 4b. Otherwise <loginname> runs the same command themselves (no sudo
|
|
191
|
+
# from ADMINUSER required, since they already are <loginname>):
|
|
192
|
+
pywrapper-install -p|--pgstart -n|--username <loginname>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Update an existing installation (no sudo required)
|
|
196
|
+
|
|
197
|
+
When the package is upgraded and a new `pywrapper.c` is bundled, use `-u/--update`
|
|
198
|
+
to recompile and reinstall all setuid binaries without needing `sudo`. The existing
|
|
199
|
+
`pgstart_*` binaries in `bin/` are used to perform the privileged operations:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
pywrapper-install -u|--update [-n|--username gdexdata] [-e|--envhome $ENVHOME]
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Simple install (no sudo required, runs as current user)
|
|
206
|
+
|
|
207
|
+
Users who do not need the setuid mechanism can skip steps 2–4 and create a
|
|
208
|
+
direct symlink from `dsarch` to `setuid_dsarch`:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
pip install rda_python_dsarch
|
|
212
|
+
pywrapper-install -l|--link dsarch -s|--simple
|
|
213
|
+
pywrapper-install -l|--link all -s|--simple # or link all setuid_* entries at once
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Runtime flow
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
user runs: dsarch [args]
|
|
220
|
+
| (symlink -> pywrapper, setuid bit -> EUID=gdexdata)
|
|
221
|
+
pywrapper.c: execv(bin/setuid_dsarch, args)
|
|
222
|
+
setuid_dsarch: calls dsarch:main() as gdexdata
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Github
|
|
226
|
+
|
|
227
|
+
<https://github.com/NCAR/rda-python-setuid>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
rda_python_setuid/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
2
|
+
rda_python_setuid/install.py,sha256=IguehVEMgY08-mNZcDPL4sMX5_XF5sb71qwkzE-wTqU,12422
|
|
3
|
+
rda_python_setuid/install.usg,sha256=CEeRuUO4_3dzwlIk0uEbDl-6HtYNGm16pltBvNfJqkw,7397
|
|
4
|
+
rda_python_setuid/pgstart.py,sha256=tYCOTJedqE5Lc6hUD2tyQparsOK9VGJ2alHHLNWqF20,3968
|
|
5
|
+
rda_python_setuid/pywrapper.c,sha256=Dp9EOFU9IShduQ4WMeFRiiIJLQvgZHsiQlc89vJgtII,4190
|
|
6
|
+
rda_python_setuid/pywrapper.py,sha256=7RHpWeyHGLP_7Vx3KJ_yy-3qYObYg_ar6meuworA0MA,3023
|
|
7
|
+
rda_python_setuid/setuid_setup.usg,sha256=nJaS08GtlCQmXiy-udp6cxCFgZZJuBaZ36DicUn5Q7I,2915
|
|
8
|
+
rda_python_setuid/setup_guide.py,sha256=thdTlYJwhID63G7G9NWte0qeXwH6ytQPLFbjesWodNQ,2158
|
|
9
|
+
rda_python_setuid-3.0.0.dist-info/licenses/LICENSE,sha256=1dck4EAQwv8QweDWCXDx-4Or0S8YwiCstaso_H57Pno,1097
|
|
10
|
+
rda_python_setuid-3.0.0.dist-info/METADATA,sha256=JTvNrLDE7D3GZV1-Rr2ElyksAB8Bv0FmSVgsazKwnR0,8184
|
|
11
|
+
rda_python_setuid-3.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
12
|
+
rda_python_setuid-3.0.0.dist-info/entry_points.txt,sha256=mWZUCa2KYzGsYVa33M7W03CY9SMsJYsywlIDF-4iMaQ,165
|
|
13
|
+
rda_python_setuid-3.0.0.dist-info/top_level.txt,sha256=ONMhKLagyTBktuz5dTyipSnRC0YhomTMs8eFRjM9kHQ,18
|
|
14
|
+
rda_python_setuid-3.0.0.dist-info/RECORD,,
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: rda_python_setuid
|
|
3
|
-
Version: 1.0.8
|
|
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` locks it down to `chmod 700` so users
|
|
78
|
-
cannot bypass the setuid wrapper by running it directly.
|
|
79
|
-
|
|
80
|
-
## Environment setup
|
|
81
|
-
|
|
82
|
-
### Option A — Python venv (DECS machines)
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
python3 -m venv $ENVHOME # e.g. /glade/u/home/gdexdata/gdexmsenv
|
|
86
|
-
source $ENVHOME/bin/activate
|
|
87
|
-
pip install rda_python_setuid rda_python_dsarch ...
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### Option B — Conda (DAV/Casper)
|
|
91
|
-
|
|
92
|
-
```bash
|
|
93
|
-
conda create -n pg-gdex python=3.10
|
|
94
|
-
conda activate pg-gdex
|
|
95
|
-
pip install rda_python_setuid rda_python_dsarch ...
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
The conda environment is typically at `/glade/work/gdexdata/conda-envs/pg-gdex`.
|
|
99
|
-
|
|
100
|
-
## Installation
|
|
101
|
-
|
|
102
|
-
After setting up the environment and installing packages, run `pywrapper-install`
|
|
103
|
-
with no arguments to display the full user guide:
|
|
104
|
-
|
|
105
|
-
```bash
|
|
106
|
-
pywrapper-install
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Full setuid setup (requires sudo access to CommonUser)
|
|
110
|
-
|
|
111
|
-
```bash
|
|
112
|
-
# 1. Install the target package (pulls in rda_python_setuid automatically):
|
|
113
|
-
pip install rda_python_dsarch
|
|
114
|
-
|
|
115
|
-
# 2. Compile pywrapper C binary (once per environment):
|
|
116
|
-
pywrapper-install -c|--compile
|
|
117
|
-
|
|
118
|
-
# 3. Wire up each program as a setuid entry:
|
|
119
|
-
pywrapper-install -l|--link dsarch
|
|
120
|
-
|
|
121
|
-
# 4. Optionally, allow a specialist to run commands as themselves:
|
|
122
|
-
pywrapper-install -p|--pgstart -u|--user zji
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### Simple install (no sudo required, runs as current user)
|
|
126
|
-
|
|
127
|
-
Users who do not need the setuid mechanism can skip steps 2–4 and create a
|
|
128
|
-
direct symlink from `dsarch` to `setuid_dsarch`:
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
pip install rda_python_dsarch
|
|
132
|
-
pywrapper-install -l|--link dsarch -s|--simple
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
## Runtime flow
|
|
136
|
-
|
|
137
|
-
```
|
|
138
|
-
user runs: dsarch [args]
|
|
139
|
-
| (symlink -> pywrapper, setuid bit -> EUID=gdexdata)
|
|
140
|
-
pywrapper.c: execv(bin/setuid_dsarch, args)
|
|
141
|
-
| (chmod 700, only gdexdata can exec directly)
|
|
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=-YoqQ9OTguOwVaWGOkVEQgzZMxfpHIyG4MEM_oRA8oE,6379
|
|
3
|
-
rda_python_setuid/install.usg,sha256=V2sbJo5hTB4hWKGZClAHBojm7wg1B1cQRZN7NdvTEHo,5543
|
|
4
|
-
rda_python_setuid/pgstart.py,sha256=dzYzuc2ce60RNx7FBxVhUgHYwlVnbiZcCrxVQ6MvZZ4,3909
|
|
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.8.dist-info/licenses/LICENSE,sha256=1dck4EAQwv8QweDWCXDx-4Or0S8YwiCstaso_H57Pno,1097
|
|
8
|
-
rda_python_setuid-1.0.8.dist-info/METADATA,sha256=sxD3iWI8gQ0iHRxYJdN853asC8dlvKP1yMae1aYOJJQ,5048
|
|
9
|
-
rda_python_setuid-1.0.8.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
10
|
-
rda_python_setuid-1.0.8.dist-info/entry_points.txt,sha256=mWZUCa2KYzGsYVa33M7W03CY9SMsJYsywlIDF-4iMaQ,165
|
|
11
|
-
rda_python_setuid-1.0.8.dist-info/top_level.txt,sha256=ONMhKLagyTBktuz5dTyipSnRC0YhomTMs8eFRjM9kHQ,18
|
|
12
|
-
rda_python_setuid-1.0.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|