Skip to main content

RDA Python package, including a C code wrapper, to execute commandline applications via setuid for effective and common user names.

Overview

rda_python_setuid provides a C binary (pywrapper) that acquires a setuid effective user, then execvs a Python entry point script. This allows Python programs to run as a designated common user (e.g. gdexdata) without requiring sudo access.

Three modes are supported:

  • Mode 1 (CommonUser program): a symlink dsarch -> pywrapper runs setuid_dsarch as the common user.
  • Mode 2 (pgstart specialist): a copy pgstart_<loginname> (e.g. pgstart_zji) runs any command as <loginname> via pgstart.py. <loginname> can be any user that belongs to the same group as PGLOG['COMMONUSER']. Execution is restricted to authorized callers (see pgstart.py below).
  • Mode 3 (cmwrapper, callers outside the group): a dedicated binary compiled from cmwrapper.c and installed 4755 runs ONE fixed program as the common user for any user on the machine, including users outside the common user's group (see "cmwrapper" below). With -s|--simple the same binary is installed 755 with no setuid, which publishes ONE program of this environment to users who have no environment of their own.

Two Python entry points are packaged alongside the C wrapper:

  • pywrapper.py — the default fallback target executed when pywrapper.c cannot resolve a matching setuid_<program> entry point. Acquires the effective UID via PgLOG.set_suid(), prints the caller's real and effective user names, and shows the pyproject.toml snippet plus the pywrapper-install -l <program> command needed to wrap a new script. Diagnostic flags -env, -inc, and -plg dump the environment variables, sys.path, and PGLOG dictionary respectively — handy for verifying the setuid environment before wiring up a real program.

  • pgstart.py — the Mode 2 launcher invoked through a pgstart_<loginname> copy of pywrapper. Reads the real/effective UIDs from PGLOG, then permits execution only if the real user is in [PGLOG['ADMINUSER'], euser, PGLOG['COMMONUSER']] (i.e. the admin specialist PGLOG['ADMINUSER'] — default zji — the effective user themselves, or the shared common user); unauthorized callers receive an informational message and exit. After authorization it parses leading flag tokens — -bg (background via subprocess.Popen), -fg (explicit foreground, default), -cwd <dir> (chdir before exec), and the same -env/-inc/-plg diagnostics as pywrapper.py — and then runs the remaining arguments as a command (subprocess.run/Popen) under the effective UID, logging a host/program/timestamp/user line to pgstart.log.

Dependency requirement

Any Python package whose programs are to be run via the setuid mechanism must declare rda_python_setuid as a dependency in its pyproject.toml:

[project]
dependencies = [
  "rda_python_setuid",
  ...
]

It must also register each wrapped program's connector entry point with a setuid_ prefix:

[project.scripts]
"setuid_dsarch" = "rda_python_dsarch.dsarch:main"

pip install then places setuid_dsarch in the environment's bin/ directory automatically. pywrapper-install -l/--link creates the symlink dsarch -> pywrapper; running dsarch goes through the setuid wrapper, which execs setuid_dsarch as CommonUser.

The main() of each wrapped program (e.g. rda_python_dsarch/dsarch.py) must also call show_setup_guide() at the top of main(), passing an instance of the program's class along with the package name and list of setuid program names:

def main():
   from rda_python_setuid.setup_guide import show_setup_guide
   object = DsArch()
   show_setup_guide(object, 'rda_python_dsarch', ['dsarch'])
   ...

When setuid_dsarch is invoked directly (before pywrapper symlinks are set up, so euid ≠ CommonUser), show_setup_guide() prints the shared setuid setup guide and exits. When invoked via the dsarch -> pywrapper symlink (euid = CommonUser), get_command() strips the setuid_ prefix, the check inside show_setup_guide() fails, and the program runs normally.

Environment setup

Create a Python environment first; package installs in the next section run inside whichever environment you activate here.

Option A — Python venv (DECS machines)

python3 -m venv $ENVHOME          # e.g. /glade/u/home/gdexdata/gdexmsenv
source $ENVHOME/bin/activate

Option B — Conda (DAV/Casper)

conda create --prefix $ENVHOME python=3.12   # e.g. /glade/work/gdexdata/conda-envs/pg-gdex
conda activate $ENVHOME

Installing rda-python-setuid

Pick whichever install mode fits your workflow. All four pull in the transitive dependency (rda_python_common) automatically. Once installed, the pywrapper-install CLI is available for the setuid wiring steps below.

For local development, clone this repo alongside your project and install it in editable mode so that changes are picked up without re-installing:

git clone https://github.com/NCAR/rda-python-setuid.git
cd rda-python-setuid
pip install -e .

To test a specific branch (e.g. an in-progress feature or fix branch), pass -b/--branch to git clone:

git clone -b <branch-name> https://github.com/NCAR/rda-python-setuid.git
cd rda-python-setuid
pip install -e .

For a regular (non-editable) install from a checkout:

pip install /path/to/rda-python-setuid

For a production install on a system that uses the published distribution:

pip install rda_python_setuid

Setuid wrapper setup

With rda_python_setuid installed in the active environment, run pywrapper-install with no arguments to display the full user guide:

pywrapper-install

Full setuid setup (requires sudo access to CommonUser)

# 1. Install the target package (pulls in rda_python_setuid automatically):
pip install rda_python_dsarch

# 2. Compile pywrapper C binary (once per environment):
pywrapper-install -c|--compile

# 3. Wire up each program as a setuid entry (specify name or use 'all'):
pywrapper-install -l|--link dsarch
pywrapper-install -l|--link all           # auto-link every setuid_* entry not yet linked

# 4. Optionally, install a pgstart_<loginname> binary so <loginname> (any user
#    in the same group as PGLOG['COMMONUSER']) can run commands as themselves
#    via the setuid wrapper.  Same command in both cases — only the invoker
#    differs:
#
#    4a. If PGLOG['ADMINUSER'] (default zji) can `sudo -u <loginname>`, the
#        admin sets it up on the user's behalf:
pywrapper-install -p|--pgstart -n|--username <loginname>
#
#    4b. Otherwise <loginname> runs the same command themselves (no sudo
#        from ADMINUSER required, since they already are <loginname>):
pywrapper-install -p|--pgstart -n|--username <loginname>

Update an existing installation (no sudo required)

When the package is upgraded and a new pywrapper.c is bundled, use -u/--update to recompile and reinstall all setuid binaries without needing sudo. The existing pgstart_* binaries in bin/ are used to perform the privileged operations:

pywrapper-install -u|--update [-n|--username gdexdata] [-e|--envhome $ENVHOME]

Simple install (no sudo required, runs as current user)

Users who do not need the setuid mechanism can skip steps 2–4 and create a direct symlink from dsarch to setuid_dsarch:

pip install rda_python_dsarch
pywrapper-install -l|--link dsarch -s|--simple
pywrapper-install -l|--link all -s|--simple   # or link all setuid_* entries at once

cmwrapper (Mode 3, for callers outside the CommonUser group)

pywrapper is installed 4750, so only members of the common user's group can execute it. That group restriction is what makes it safe for pywrapper to pick the program to run from basename(argv[0]): anyone who can execute it can already reach every setuid_* entry in bin/ just by naming the symlink differently.

cmwrapper is for the opposite case — letting users who are not in the common user's group run one specific program as the common user. It is installed 4755, i.e. executable by everyone, so nothing about what it runs may come from the caller:

  • the absolute path of the script to exec, and the program name, are baked into the binary at compile time, so it cannot be symlinked under another name to reach a different program;
  • the environment is replaced with a fixed whitelist (HOME, USER, LOGNAME, TERM, LANG, TZ, a fixed PATH and PYTHONNOUSERSITE=1), so PYTHONPATH, PYTHONHOME, LD_PRELOAD, LD_LIBRARY_PATH and the PGLOG path variables (DSDHOME, DSSHOME, LOGPATH, COMMONUSER, ...) cannot be used to run arbitrary code, or redirect where files are written, as the common user.

One binary is compiled per wrapped program:

# Install bin/gdexdrop as a 4755 binary that execs bin/setuid_gdexdrop:
pywrapper-install -m|--cmlink gdexdrop

# Same, but installed into a common area already on everyone's PATH:
pywrapper-install -m|--cmlink gdexdrop -d|--destdir /glade/u/home/gdexdata/bin

# Point it at a script somewhere other than bin/setuid_gdexdrop:
pywrapper-install -m|--cmlink gdexdrop -t|--target /path/to/setuid_gdexdrop

Only wrap a program that does its own authorization and confines where it writes, such as gdexdrop, which checks its caller against an access list and copies only into the requested dataset directory. Never wrap a general purpose program such as gdexcp: at 4755 that would let any user on the machine read or overwrite any file of the common user.

cmwrapper with no setuid, to publish a program of this environment

Add -s|--simple to compile the binary 755 with no setuid bit. Nothing runs as another user; the binary exists only so that users who do not have this environment, and cannot activate a venv or a conda environment, can still run one of its programs by name — gdexls, for example:

# Install /glade/u/apps/contrib/gdexls, a 755 binary that execs bin/gdexls:
pywrapper-install -m|--cmlink gdexls -s|--simple -d|--destdir /glade/u/apps/contrib

Since there is no privilege change, sanitizing the environment would buy no security and would only break the caller's own settings, so the caller's environment is passed through except for PYTHONPATH, PYTHONHOME and PYTHONSTARTUP, which would send the program to another environment's modules. The default -t|--target is bin/PROGRAM rather than bin/setuid_PROGRAM, and no sudo access is required.

Runtime flow

user runs:  dsarch [args]
              |  (symlink -> pywrapper, setuid bit -> EUID=gdexdata)
pywrapper.c:  execv(bin/setuid_dsarch, args)
setuid_dsarch: calls dsarch:main() as gdexdata

user runs:  gdexdrop [args]
              |  (dedicated 4755 binary, setuid bit -> EUID=gdexdata)
cmwrapper.c:  execve(bin/setuid_gdexdrop, args, sanitized env)
setuid_gdexdrop: calls gdexdrop:main() as gdexdata

user runs:  gdexls [args]
              |  (dedicated 755 binary, no setuid, -DCMSIMPLE)
cmwrapper.c:  execv(bin/gdexls, args)
gdexls:       calls gdexls:main() as the calling user

Github

https://github.com/NCAR/rda-python-setuid

Metadata

Release files for rda-python-setuid 3.0.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rda-python-setuid 3.0.3
File Size Uploaded
rda_python_setuid-3.0.3.tar.gz 22.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for rda-python-setuid 3.0.3
File Interpreter ABI Platform
rda_python_setuid-3.0.3-py3-none-any.whl Python 3 none any Details

Total release size: 47.3 kB

Release files / rda_python_setuid-3.0.3.tar.gz

Download URL rda_python_setuid-3.0.3.tar.gz
Size 22.9 kB
Tags Source
SHA-256 checksum
How to use checksums
205e093afdd570d2cec93eef8972046df812dd0277bab716d210743a9d4a9da5
BLAKE2b-256 checksum
How to use checksums
5827bac2d7c2e5980c27f62eb5cc328a78105f24e156a93abd7c5660d0c19fea
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.

Transparency log

Release files / rda_python_setuid-3.0.3-py3-none-any.whl

Download URL rda_python_setuid-3.0.3-py3-none-any.whl
Size 24.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b61a3a0d0ae2e3d2b92458c6d4ee5396e5569aff35e62adb7c25ab01f956a725
BLAKE2b-256 checksum
How to use checksums
61e33b9faea26ef4b1938fd24be8ea09ccf7c257bdaa10ce94975e0df011069a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

3.0.3 This release

2 release files

3.0.2

2 release files

3.0.1

2 release files

3.0.0

2 release files

1.0.9

2 release files

1.0.8

2 release files

1.0.7

2 release files

1.0.6

2 release files

1.0.5

2 release files

1.0.4

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page