rda-python-setuid 1.0.2__py3-none-any.whl → 1.0.4__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.
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ ##################################################################################
4
+ #
5
+ # Title: pywrapper-install
6
+ # Author: Zaihua Ji, zji@ucar.edu
7
+ # Date: 2025-05-11
8
+ # Purpose: Install helper for the pywrapper setuid C binary.
9
+ # Replaces the manual gcc/chmod/ln steps with a single command.
10
+ #
11
+ # Github: https://github.com/NCAR/rda-python-setuid.git
12
+ #
13
+ # Usage:
14
+ # # 1. Compile and install pywrapper (run once per environment):
15
+ # pywrapper-install --user gdexdata [--envhome $ENVHOME/bin/]
16
+ #
17
+ # # 2. Create pgstart_USER entry so USER can run commands as themselves:
18
+ # pywrapper-install --pgstart --user zji [--envhome $ENVHOME/bin/]
19
+ #
20
+ # # 3. Create a symlink so a program runs as CommonUser via pywrapper (setuid):
21
+ # pywrapper-install --link myprog --user gdexdata [--envhome $ENVHOME/bin/]
22
+ #
23
+ # # 4. Simple install: symlink PROGRAM -> setuid_PROGRAM (no setuid, runs as current user):
24
+ # pywrapper-install --link myprog --simple [--envhome $ENVHOME/bin/]
25
+ #
26
+ # Convention for wrapped programs:
27
+ # The target package must register its connector entry point with a setuid_ prefix:
28
+ # [project.scripts]
29
+ # "setuid_dsarch" = "rda_python_dsarch.dsarch:main"
30
+ # pip install places setuid_dsarch in the bin dir automatically.
31
+ # pywrapper-install --link dsarch will chown setuid_dsarch to CommonUser and
32
+ # chmod 700, so users cannot run it directly and must go through the setuid wrapper.
33
+ # pywrapper-install --link dsarch --simple creates dsarch -> setuid_dsarch directly,
34
+ # skipping setuid; the program runs as the current user.
35
+ #
36
+ ##################################################################################
37
+
38
+ import argparse
39
+ import os
40
+ import shutil
41
+ import subprocess
42
+ import sys
43
+
44
+
45
+ def get_bindir():
46
+ """Return the bin directory of the active Python environment."""
47
+ return os.path.dirname(os.path.abspath(sys.executable))
48
+
49
+
50
+ def get_c_source():
51
+ """Return path to pywrapper.c bundled with this package."""
52
+ return os.path.join(os.path.dirname(__file__), 'pywrapper.c')
53
+
54
+
55
+ def run(cmd):
56
+ print(" $", " ".join(cmd))
57
+ subprocess.run(cmd, check=True)
58
+
59
+
60
+ def show_usage():
61
+ usgfile = os.path.join(os.path.dirname(__file__), 'install.usg')
62
+ os.system("more " + usgfile)
63
+ sys.exit(0)
64
+
65
+
66
+ def main():
67
+
68
+ if len(sys.argv) == 1:
69
+ show_usage()
70
+
71
+ parser = argparse.ArgumentParser(
72
+ description="Compile and install the pywrapper setuid C binary."
73
+ )
74
+ parser.add_argument(
75
+ '--envhome', default=None,
76
+ help="Path to the venv bin/ directory (default: bin/ dir of the current Python executable)"
77
+ )
78
+ parser.add_argument(
79
+ '--user', default=None,
80
+ help="User name to own the setuid binary (default: current login user for --pgstart, gdexdata otherwise)"
81
+ )
82
+ parser.add_argument(
83
+ '--simple', action='store_true',
84
+ help="Simple install: create symlink PROGRAM -> setuid_PROGRAM, skipping setuid (use with --link)"
85
+ )
86
+ group = parser.add_mutually_exclusive_group()
87
+ group.add_argument(
88
+ '--pgstart', action='store_true',
89
+ help="Create pgstart_USER for running commands as USER (Mode 2)"
90
+ )
91
+ group.add_argument(
92
+ '--link', metavar='PROGRAM',
93
+ help="Create symlink PROGRAM -> pywrapper for running a fixed program as CommonUser (Mode 1)"
94
+ )
95
+ args = parser.parse_args()
96
+
97
+ if args.user is None and not args.simple:
98
+ import pwd
99
+ if args.pgstart:
100
+ args.user = pwd.getpwuid(os.getuid()).pw_name
101
+ else:
102
+ args.user = 'gdexdata'
103
+
104
+ bindir = args.envhome or get_bindir()
105
+ pywrapper = os.path.join(bindir, 'pywrapper')
106
+
107
+ if args.link:
108
+ target = os.path.join(bindir, args.link)
109
+ script = os.path.join(bindir, 'setuid_{}'.format(args.link))
110
+ if not os.path.exists(script):
111
+ print("Error: {} not found. Install the package that provides it first.".format(script))
112
+ sys.exit(1)
113
+ if args.simple:
114
+ # Simple install: symlink PROGRAM -> setuid_PROGRAM, no setuid, runs as current user.
115
+ if os.path.lexists(target):
116
+ print("Already exists: {}".format(target))
117
+ else:
118
+ os.symlink(script, target)
119
+ print("Created: {} -> setuid_{}".format(target, args.link))
120
+ else:
121
+ # Mode 1: symlink PROGRAM -> pywrapper, then lock down setuid_PROGRAM
122
+ # so users cannot bypass the setuid wrapper by running it directly.
123
+ if os.path.lexists(target):
124
+ print("Already exists: {}".format(target))
125
+ else:
126
+ os.symlink(pywrapper, target)
127
+ print("Created: {} -> pywrapper".format(target))
128
+ # chown and chmod 700: only CommonUser can execute setuid_PROGRAM directly.
129
+ # pywrapper (running as CommonUser via setuid) can still execv it, but any
130
+ # direct invocation by other users will get "permission denied".
131
+ run(['sudo', '-u', args.user, 'chown', args.user, script])
132
+ run(['sudo', '-u', args.user, 'chmod', '700', script])
133
+ print("Locked: {} (chmod 700, owned by {})".format(script, args.user))
134
+
135
+ elif args.pgstart:
136
+ # Mode 2: copy pywrapper to pgstart_USER with setuid owned by USER
137
+ if not os.path.exists(pywrapper):
138
+ print("Error: {} not found. Run pywrapper-install --user COMMONUSER first.".format(pywrapper))
139
+ sys.exit(1)
140
+ target = os.path.join(bindir, 'pgstart_{}'.format(args.user))
141
+ run(['sudo', '-u', args.user, 'cp', pywrapper, target])
142
+ run(['sudo', '-u', args.user, 'chmod', '4750', target])
143
+ print("Installed: {} (setuid, owned by {})".format(target, args.user))
144
+
145
+ else:
146
+ # Default: compile pywrapper.c and install pywrapper with setuid
147
+ src = get_c_source()
148
+ src_dest = os.path.join(bindir, 'pywrapper.c')
149
+ shutil.copy(src, src_dest)
150
+ print("Copied: {}".format(src_dest))
151
+ run(['sudo', '-u', args.user, 'gcc', '-o', pywrapper, src_dest])
152
+ run(['sudo', '-u', args.user, 'chmod', '4750', pywrapper])
153
+ print("Installed: {} (setuid, owned by {})".format(pywrapper, args.user))
154
+
155
+
156
+ if __name__ == '__main__': main()
@@ -0,0 +1,132 @@
1
+ Compile and install the pywrapper setuid C binary, and set up program entries
2
+ that execute Python scripts as a common or effective user via the setuid mechanism.
3
+ Must be run inside the target Python virtual environment.
4
+
5
+ Usage: pywrapper-install --user USER [--envhome BINDIR] [--pgstart | --link PROGRAM [--simple]]
6
+
7
+ - Option --user USER
8
+ The user name to own the setuid binary. For Mode 1 (CommonUser program),
9
+ this is the common user (e.g. gdexdata). For Mode 2 (pgstart), this is
10
+ the specialist user (e.g. zji). Defaults to 'gdexdata' unless --pgstart
11
+ is given, in which case it defaults to the current login user.
12
+
13
+ - Option --envhome BINDIR
14
+ Path to the venv bin/ directory. Defaults to the bin/ directory of the
15
+ currently active Python executable. Only needed when installing into an
16
+ environment other than the one currently active.
17
+
18
+ - Option --pgstart
19
+ Mode 2: copy the compiled pywrapper binary to pgstart_USER (owned by USER,
20
+ chmod 4750). Allows USER to run arbitrary commands as themselves via the
21
+ setuid wrapper. Cannot be combined with --link.
22
+
23
+ - Option --link PROGRAM
24
+ Mode 1: create a symlink PROGRAM -> pywrapper in the bin/ directory, then
25
+ lock down setuid_PROGRAM (chmod 700, owned by USER) so users cannot bypass
26
+ the setuid wrapper by running setuid_PROGRAM directly. Cannot be combined
27
+ with --pgstart.
28
+
29
+ - Option --simple (use with --link)
30
+ Simple install: create a symlink PROGRAM -> setuid_PROGRAM directly,
31
+ skipping the setuid mechanism entirely. The program runs as the current
32
+ user with no privilege change. --user is not required with this option.
33
+ Useful for users who do not need or cannot set up the setuid wrapper.
34
+
35
+ When run without --pgstart or --link, the default action is to compile pywrapper.c
36
+ (bundled with this package) and install it as bin/pywrapper, owned by USER with
37
+ chmod 4750 (setuid). This step must be done once per environment before using
38
+ --pgstart or --link.
39
+
40
+ Convention for wrapped programs:
41
+ Any Python package whose program is to be run via pywrapper must:
42
+
43
+ 1. Declare rda_python_setuid as a dependency in pyproject.toml:
44
+
45
+ [project]
46
+ dependencies = [
47
+ "rda_python_setuid",
48
+ ...
49
+ ]
50
+
51
+ This ensures pywrapper-install is available in the environment after
52
+ pip install.
53
+
54
+ 2. Register its connector entry point with a setuid_ prefix in pyproject.toml:
55
+
56
+ [project.scripts]
57
+ "setuid_dsarch" = "rda_python_dsarch.dsarch:main"
58
+
59
+ pip install then places setuid_dsarch in the bin/ directory automatically.
60
+ Running pywrapper-install --link will lock it down (chmod 700) so that only
61
+ pywrapper (running as CommonUser via its setuid bit) can execv it.
62
+
63
+ Environment Setup:
64
+
65
+ Option A - Python venv (recommended for DECS machines):
66
+
67
+ 1. Create and activate the virtual environment:
68
+ python3 -m venv $ENVHOME
69
+ source $ENVHOME/bin/activate
70
+
71
+ 2. Install the required packages:
72
+ pip install rda_python_setuid rda_python_dsarch ...
73
+
74
+ 3. Deactivate when done:
75
+ deactivate
76
+
77
+ $ENVHOME is typically /glade/u/home/gdexdata/gdexmsenv on DECS machines.
78
+
79
+ Option B - Conda (recommended for DAV/Casper):
80
+
81
+ 1. Create and activate the conda environment:
82
+ conda create -n pg-gdex python=3.10
83
+ conda activate pg-gdex
84
+
85
+ 2. Install the required packages:
86
+ pip install rda_python_setuid rda_python_dsarch ...
87
+
88
+ The conda environment is typically located at:
89
+ /glade/work/gdexdata/conda-envs/pg-gdex
90
+
91
+ Use 'conda activate pg-gdex' to activate it before running pywrapper-install.
92
+
93
+ Setup sequence for a new environment:
94
+
95
+ Option A - Full setuid setup (requires sudo access to CommonUser):
96
+
97
+ 1. Install the target package and its dependencies:
98
+ pip install rda_python_dsarch
99
+
100
+ 2. Compile pywrapper (once per environment):
101
+ pywrapper-install --user gdexdata
102
+
103
+ 3. Wire up each program as a setuid entry:
104
+ pywrapper-install --link dsarch --user gdexdata
105
+
106
+ 4. Optionally, allow a specialist to run commands as themselves:
107
+ pywrapper-install --pgstart --user zji
108
+
109
+ Option B - Simple install (no sudo required, runs as current user):
110
+
111
+ 1. Install the target package:
112
+ pip install rda_python_dsarch
113
+
114
+ 2. Create a direct symlink to the connector script:
115
+ pywrapper-install --link dsarch --simple
116
+
117
+ Examples:
118
+
119
+ 1. Compile and install pywrapper for common user gdexdata:
120
+ pywrapper-install --user gdexdata
121
+
122
+ 2. Same, but targeting a specific environment:
123
+ pywrapper-install --user gdexdata --envhome /glade/u/home/gdexdata/gdexmsenv/bin/
124
+
125
+ 3. Wire up dsarch to run as gdexdata via pywrapper:
126
+ pywrapper-install --link dsarch --user gdexdata
127
+
128
+ 4. Allow specialist zji to run commands as themselves via pgstart:
129
+ pywrapper-install --pgstart --user zji
130
+
131
+ 5. Simple install of dsarch for a user who does not need setuid:
132
+ pywrapper-install --link dsarch --simple
@@ -18,6 +18,7 @@ import os
18
18
  import sys
19
19
  import re
20
20
  import pwd
21
+ import subprocess
21
22
  from rda_python_common import PgLOG
22
23
 
23
24
  #
@@ -25,15 +26,19 @@ from rda_python_common import PgLOG
25
26
  #
26
27
  def main():
27
28
 
28
- permit = 1
29
+ permit = False
29
30
  PgLOG.PGLOG['LOGFILE'] = "pgstart.log"
30
31
  aname = PgLOG.get_command()
31
32
  bckgrd = ""
32
33
  workdir = None
33
34
  argv = sys.argv[1:]
34
35
 
35
- PgLOG.set_suid(PgLOG.PGLOG['EUID'])
36
- if PgLOG.PGLOG['CURUID'] != PgLOG.PGLOG['RDAUSER'] and PgLOG.PGLOG['CURUID'] != "zji": permit = 0
36
+ ruid = PgLOG.PGLOG['RUID']
37
+ euid = PgLOG.PGLOG['EUID']
38
+ ruser = pwd.getpwuid(ruid).pw_name
39
+ euser = pwd.getpwuid(euid).pw_name
40
+ if ruser == euser or ruser == PgLOG.PGLOG['GDEXUSER']: permit = True
41
+ PgLOG.set_suid(euid)
37
42
 
38
43
  while argv:
39
44
  ms = re.match(r'^-(\w+)$', argv[0])
@@ -48,27 +53,26 @@ def main():
48
53
  display_message(opt)
49
54
 
50
55
  if not (permit and argv):
51
- ruid = PgLOG.PGLOG['RUID']
52
- euid = PgLOG.PGLOG['EUID']
53
- ruser = pwd.getpwuid(ruid).pw_name
54
- euser = pwd.getpwuid(euid).pw_name
55
56
  print("********************************************************************")
56
57
  print("* Your Login Name is {}({}) & Effective User Name is {}({}).".format(ruser, ruid, euser, euid))
57
58
  print("* Pass a command or options -(plg|env|inc) to run '{}'.".format(aname))
58
59
  if not permit:
59
- print("* You must be '{}' to execute a command as user '{}'.".format(PgLOG.PGLOG['RDAUSER'], euser))
60
+ print("* You must be '{}' or '{}' to execute a command as user '{}'.".format(euser, PgLOG.PGLOG['GDEXUSER'], euser))
60
61
  print("********************************************************************")
61
62
  sys.exit(0)
62
63
 
63
64
  cmd = PgLOG.argv_to_string(argv)
64
-
65
+
65
66
  msg = "{}-{}{}-{}".format(PgLOG.PGLOG['HOSTNAME'], aname, PgLOG.current_datetime(), PgLOG.PGLOG['CURUID'])
66
67
  if workdir:
67
68
  msg += "-" + workdir
68
69
  os.chdir(workdir)
69
70
 
70
71
  PgLOG.pglog("{}: {}".format(msg, cmd), PgLOG.MSGLOG)
71
- os.system(cmd + bckgrd)
72
+ if bckgrd:
73
+ subprocess.Popen(argv)
74
+ else:
75
+ subprocess.run(argv)
72
76
  sys.exit(0)
73
77
 
74
78
  #
@@ -8,27 +8,35 @@
8
8
  * Instruction:
9
9
  * python -m venv $ENVHOME
10
10
  * python -m pip install rda_python_setuid
11
- * cd $ENVHOME/bin/
12
- * cp ../lib/python3.N/site-packages/rda_python_setuid/pywrapper.c ./
13
- * sudo -u CommonUser gcc -o pywrapper $ENVHOME/bin/pywrapper.c
14
- * sudo -u CommonUser chmod 4750 pywrapper
15
11
  *
16
- * For an existing python program, $ENVHOME/bin/CommonProgram.py, to execute it
17
- * as the common user:
18
- * sudo -u CommonUser ln -s pywrapper CommonProgram
12
+ * # Compile pywrapper and set setuid bit (owned by CommonUser):
13
+ * pywrapper-install --user CommonUser [--envhome $ENVHOME/bin]
14
+ *
15
+ * # For an existing python program, $ENVHOME/bin/CommonProgram.py, to execute it
16
+ * # as the common user (Mode 1 - symlink):
17
+ * pywrapper-install --link CommonProgram [--envhome $ENVHOME/bin]
19
18
  * CommonProgram [options]
20
19
  *
21
- * For an existing python program, $ENVHOME/bin/EffectProgram.py, to execute it
22
- * as the effective user:
23
- * sudo -u EffectUser cp pywrapper pgstart_EffectUser
24
- * sudo -u EffectUser chmod 4750 pgstart_EffectUser
20
+ * # For a specialist to run commands as themselves via pgstart (Mode 2):
21
+ * pywrapper-install --pgstart --user EffectUser [--envhome $ENVHOME/bin]
25
22
  * pgstart_EffectUser EffectProgram [options]
26
23
  *
27
24
  * N: python 3 release number, it is 10 for Python 3.10.12
28
- * CommonUser: a common user login name, such as rdadata, for RDAMS configuration
25
+ * CommonUser: a common user login name, such as gdexdata, for GDEXMS configuration
29
26
  * EffectUser: any user login name in the same group of the common user
30
- * $ENVHOME: /glade/u/home/rdadata/rdamsenv (venv) on DECS machines, and
31
- * /glade/work/rdadata/conda-envs/pg-rda (conda) on DAV
27
+ * $ENVHOME: /glade/u/home/gdexdata/rdamsenv (venv) on DECS machines, and
28
+ * /glade/work/gdexdata/conda-envs/pg-rda (conda) on DAV
29
+ *
30
+ * Convention:
31
+ * Any Python package whose program is to be run via pywrapper must register
32
+ * its connector entry point with a setuid_ prefix in pyproject.toml, e.g.:
33
+ * [project.scripts]
34
+ * "setuid_dsarch" = "rda_python_dsarch.dsarch:main"
35
+ * pip install places setuid_dsarch in $ENVHOME/bin/. pywrapper locates it by
36
+ * prepending setuid_ to the invoked program name. No manual script creation needed.
37
+ * pywrapper-install --link will chown setuid_dsarch to CommonUser and chmod 700,
38
+ * preventing direct execution by other users while still allowing pywrapper
39
+ * (which runs with EUID=CommonUser via the setuid bit) to execv it.
32
40
  *
33
41
  \***************************************************************************************/
34
42
 
@@ -37,6 +45,8 @@
37
45
  #include <unistd.h>
38
46
  #include <stdio.h>
39
47
  #include <string.h>
48
+ #include <stdlib.h>
49
+ #include <limits.h>
40
50
  #include <libgen.h>
41
51
 
42
52
  int is_executable(const char *filename) {
@@ -52,29 +62,54 @@ int is_executable(const char *filename) {
52
62
  /* main program */
53
63
  int main(int argc, char *argv[]) {
54
64
  char *name;
55
- char cname[80], prog[255];
56
- char file[] = __FILE__;
65
+ char cname[80], prog[PATH_MAX];
66
+ char exepath[PATH_MAX];
67
+ char *fpath;
68
+ ssize_t exelen;
57
69
  char pgstart[] = "pgstart";
58
70
  char **apntr = argv;
59
71
 
72
+ exelen = readlink("/proc/self/exe", exepath, sizeof(exepath) - 1);
73
+ if(exelen == -1) {
74
+ perror("readlink /proc/self/exe");
75
+ exit(1);
76
+ }
77
+ exepath[exelen] = '\0';
78
+ fpath = dirname(exepath);
79
+
60
80
  name = strrchr(argv[0], '/');
61
- strcpy(cname, (name == NULL ? argv[0] : ++name));
81
+ strncpy(cname, (name == NULL ? argv[0] : ++name), sizeof(cname) - 1);
82
+ cname[sizeof(cname) - 1] = '\0';
62
83
 
63
84
  if(strstr(cname, pgstart) == cname) {
64
85
  if(argc == 1 || argv[1][0] == '-') {
65
- strcpy(cname, pgstart);
86
+ strncpy(cname, pgstart, sizeof(cname) - 1);
66
87
  } else {
67
88
  argv += 1;
68
89
  name = strrchr(argv[0], '/');
69
- strcpy(cname, (name == NULL ? argv[0] : ++name));
90
+ strncpy(cname, (name == NULL ? argv[0] : ++name), sizeof(cname) - 1);
91
+ cname[sizeof(cname) - 1] = '\0';
70
92
  }
71
93
  }
72
- sprintf(prog, "%s/%s.py", dirname(file), cname);
73
-
74
- if(is_executable(prog)) {
75
- execv(prog, argv); /* call Python script */
76
- } else{
77
- sprintf(prog, "%s/pgstart.py", dirname(file), cname);
78
- execv(prog, apntr); /* pass the command to pgstart.py */
94
+ if(snprintf(prog, sizeof(prog), "%s/setuid_%s", fpath, cname) >= (int)sizeof(prog)) {
95
+ fprintf(stderr, "pywrapper: path too long\n");
96
+ exit(1);
97
+ }
98
+ if(is_executable(prog) == 0) {
99
+ /* fall back to cname.py for backward compatibility */
100
+ if(snprintf(prog, sizeof(prog), "%s/%s.py", fpath, cname) >= (int)sizeof(prog)) {
101
+ fprintf(stderr, "pywrapper: path too long\n");
102
+ exit(1);
103
+ }
104
+ if(is_executable(prog) == 0) {
105
+ if(snprintf(prog, sizeof(prog), "%s/pgstart.py", fpath) >= (int)sizeof(prog)) {
106
+ fprintf(stderr, "pywrapper: path too long\n");
107
+ exit(1);
108
+ }
109
+ argv = apntr;
110
+ }
79
111
  }
112
+ execv(prog, argv); /* call Python script */
113
+ perror(prog); /* execv only returns on error */
114
+ exit(1);
80
115
  }
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.4
2
+ Name: rda_python_setuid
3
+ Version: 1.0.4
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
+ ## Dependency requirement
34
+
35
+ Any Python package whose programs are to be run via the setuid mechanism must declare
36
+ `rda_python_setuid` as a dependency in its `pyproject.toml`:
37
+
38
+ ```toml
39
+ [project]
40
+ dependencies = [
41
+ "rda_python_setuid",
42
+ ...
43
+ ]
44
+ ```
45
+
46
+ It must also register each wrapped program's connector entry point with a `setuid_`
47
+ prefix:
48
+
49
+ ```toml
50
+ [project.scripts]
51
+ "setuid_dsarch" = "rda_python_dsarch.dsarch:main"
52
+ ```
53
+
54
+ `pip install` then places `setuid_dsarch` in the environment's `bin/` directory
55
+ automatically. `pywrapper-install --link` locks it down to `chmod 700` so users
56
+ cannot bypass the setuid wrapper by running it directly.
57
+
58
+ ## Environment setup
59
+
60
+ ### Option A — Python venv (DECS machines)
61
+
62
+ ```bash
63
+ python3 -m venv $ENVHOME # e.g. /glade/u/home/gdexdata/gdexmsenv
64
+ source $ENVHOME/bin/activate
65
+ pip install rda_python_setuid rda_python_dsarch ...
66
+ ```
67
+
68
+ ### Option B — Conda (DAV/Casper)
69
+
70
+ ```bash
71
+ conda create -n pg-gdex python=3.10
72
+ conda activate pg-gdex
73
+ pip install rda_python_setuid rda_python_dsarch ...
74
+ ```
75
+
76
+ The conda environment is typically at `/glade/work/gdexdata/conda-envs/pg-gdex`.
77
+
78
+ ## Installation
79
+
80
+ After setting up the environment and installing packages, run `pywrapper-install`
81
+ with no arguments to display the full user guide:
82
+
83
+ ```bash
84
+ pywrapper-install
85
+ ```
86
+
87
+ ### Full setuid setup (requires sudo access to CommonUser)
88
+
89
+ ```bash
90
+ # 1. Install the target package (pulls in rda_python_setuid automatically):
91
+ pip install rda_python_dsarch
92
+
93
+ # 2. Compile pywrapper C binary (once per environment):
94
+ pywrapper-install --user gdexdata
95
+
96
+ # 3. Wire up each program as a setuid entry:
97
+ pywrapper-install --link dsarch --user gdexdata
98
+
99
+ # 4. Optionally, allow a specialist to run commands as themselves:
100
+ pywrapper-install --pgstart --user zji
101
+ ```
102
+
103
+ ### Simple install (no sudo required, runs as current user)
104
+
105
+ Users who do not need the setuid mechanism can skip steps 2–4 and create a
106
+ direct symlink from `dsarch` to `setuid_dsarch`:
107
+
108
+ ```bash
109
+ pip install rda_python_dsarch
110
+ pywrapper-install --link dsarch --simple
111
+ ```
112
+
113
+ ## Runtime flow
114
+
115
+ ```
116
+ user runs: dsarch [args]
117
+ | (symlink -> pywrapper, setuid bit -> EUID=gdexdata)
118
+ pywrapper.c: execv(bin/setuid_dsarch, args)
119
+ | (chmod 700, only gdexdata can exec directly)
120
+ setuid_dsarch: calls dsarch:main() as gdexdata
121
+ ```
122
+
123
+ ## Github
124
+
125
+ <https://github.com/NCAR/rda-python-setuid>
@@ -0,0 +1,12 @@
1
+ rda_python_setuid/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
2
+ rda_python_setuid/install.py,sha256=C5ZT-hHdow1iSo5CcNeq3_UMCTl815Ci0sXVk4aO7NU,6081
3
+ rda_python_setuid/install.usg,sha256=O-CY7wE_6ayGzBdpJst0Ckc9x1_gDTKdFLchAUQpT7U,5226
4
+ rda_python_setuid/pgstart.py,sha256=Tm78_7zEF2Ka0oWtRJ0RVzGSSyL6AiPIYBWM8490Dis,2984
5
+ rda_python_setuid/pywrapper.c,sha256=HImbMviD1I7CDWW-GUcIxpQKxILOwc0mHI23jegvB4U,4132
6
+ rda_python_setuid/pywrapper.py,sha256=8QRMr0Sui8OdwkHQyRSNyzXpyQWhwOgeEuTGO30bmN0,2353
7
+ rda_python_setuid-1.0.4.dist-info/licenses/LICENSE,sha256=1dck4EAQwv8QweDWCXDx-4Or0S8YwiCstaso_H57Pno,1097
8
+ rda_python_setuid-1.0.4.dist-info/METADATA,sha256=aVjlqIlPTOZ5bC3hXYwRMnEzNXSEp3c3S8Huf1_8xak,3652
9
+ rda_python_setuid-1.0.4.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
10
+ rda_python_setuid-1.0.4.dist-info/entry_points.txt,sha256=mWZUCa2KYzGsYVa33M7W03CY9SMsJYsywlIDF-4iMaQ,165
11
+ rda_python_setuid-1.0.4.dist-info/top_level.txt,sha256=ONMhKLagyTBktuz5dTyipSnRC0YhomTMs8eFRjM9kHQ,18
12
+ rda_python_setuid-1.0.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.0.1)
2
+ Generator: setuptools (82.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+ pgstart.py = rda_python_setuid.pgstart:main
3
+ pywrapper-install = rda_python_setuid.install:main
4
+ setuid_pywrapper = rda_python_setuid.pywrapper:main
@@ -1,17 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: rda_python_setuid
3
- Version: 1.0.2
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 via setuid for effective and common user names.
@@ -1,10 +0,0 @@
1
- rda_python_setuid/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
2
- rda_python_setuid/pgstart.py,sha256=d7gOLjm9V3gsrV-BM64eMvgf0QULtSk-FTQpzaLEpAA,2953
3
- rda_python_setuid/pywrapper.c,sha256=T6KeW7Xh4aI6Bn4m2hRzOlzkPTtRLZ6F_pyb5qsqKHc,2605
4
- rda_python_setuid/pywrapper.py,sha256=8QRMr0Sui8OdwkHQyRSNyzXpyQWhwOgeEuTGO30bmN0,2353
5
- rda_python_setuid-1.0.2.dist-info/licenses/LICENSE,sha256=1dck4EAQwv8QweDWCXDx-4Or0S8YwiCstaso_H57Pno,1097
6
- rda_python_setuid-1.0.2.dist-info/METADATA,sha256=ePFnyvPP7aXVZt7CjAjmvzaHbkmsVmE7a0yKfKw_GOk,733
7
- rda_python_setuid-1.0.2.dist-info/WHEEL,sha256=L0N565qmK-3nM2eBoMNFszYJ_MTx03_tQ0CQu1bHLYo,91
8
- rda_python_setuid-1.0.2.dist-info/entry_points.txt,sha256=mWjhY3JTnoRv5q-B0KNGSHOd_2QbKfwxH-adiT6ef8U,110
9
- rda_python_setuid-1.0.2.dist-info/top_level.txt,sha256=ONMhKLagyTBktuz5dTyipSnRC0YhomTMs8eFRjM9kHQ,18
10
- rda_python_setuid-1.0.2.dist-info/RECORD,,
@@ -1,3 +0,0 @@
1
- [console_scripts]
2
- pgstart.py = rda_python_setuid.pgstart:main
3
- pywrapper.py = rda_python_setuid.pywrapper:main