#!/opt/cloudlinux/venv/bin/python3 -bb import stat import subprocess, os import pathlib HOOKS_DIR = '/opt/cloudlinux/rhn_hooks/post.d' JWT_TOKEN = '/etc/sysconfig/rhn/jwt.token' def _update_edition_user_file(): """ Save information about current edition in specific location available for users """ if not os.path.exists('/usr/bin/cldetect') or not os.path.exists('/opt/cloudlinux/'): return p = subprocess.Popen(['/usr/bin/cldetect', '--detect-edition'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() if p.returncode == 0: with open('/opt/cloudlinux/cl_edition.tmp', 'wb') as f: f.write(stdout) os.rename('/opt/cloudlinux/cl_edition.tmp', '/opt/cloudlinux/cl_edition') os.chmod('/opt/cloudlinux/cl_edition', 0o644) def _is_safe_to_execute(path): """ Returns True only if the given path is owned by root, is not a symlink, and is not writable by group or others. Used as a single gate immediately before invoking root-privileged code on the path, to narrow the TOCTOU window between validation and use. """ try: st = os.lstat(path) except OSError: return False if stat.S_ISLNK(st.st_mode): return False if st.st_uid != 0: return False if st.st_mode & 0o022: return False return True def _trigger_universal_hooks(): """ Discovers executable files in directory and triggers them all together Executable files are brought by different rpm packages, depending on which one requires the action """ if not os.path.exists(HOOKS_DIR): return if not _is_safe_to_execute(HOOKS_DIR): return hooks = [os.path.join(HOOKS_DIR, f) for f in os.listdir(HOOKS_DIR) if os.path.isfile(os.path.join(HOOKS_DIR, f))] for hook in hooks: if not _is_safe_to_execute(hook): continue process = subprocess.Popen([hook], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = process.communicate() def _write_token_var(token, varpath): """ Atomically write the token into a yum vars file. """ varpath = pathlib.Path(varpath) tmppath = varpath.with_suffix('.tmp') try: os.unlink(str(tmppath)) except OSError: pass fd = os.open(str(tmppath), os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW | os.O_CLOEXEC, 0o600) try: os.write(fd, token.encode()) finally: os.close(fd) os.replace(str(tmppath), str(varpath)) def _create_repo_auth_vars(): """ Provide the els repos authentication token as yum repo variables. CL7 uses yum, which reads repo variables from /etc/yum/vars/ and silently skips symlinks there — so unlike the CL8+ hook (which symlinks the token under /etc/dnf/vars/), the token value is copied into regular files. This hook runs on every token update, so the copies stay current. """ token = pathlib.Path(JWT_TOKEN).read_text() pathlib.Path('/etc/yum/vars').mkdir(parents=True, exist_ok=True) for name in ('phpelstoken', 'altpythonelstoken', 'altrubyelstoken', 'altnodejselstoken'): _write_token_var(token, '/etc/yum/vars/' + name) def main(): """ You add your other actions here, but don't forget to handle errors. """ _update_edition_user_file() _create_repo_auth_vars() _trigger_universal_hooks() if __name__ == '__main__': main()