From f55063c06c43050dbb39a76b67b9e5f05b7f7325 Mon Sep 17 00:00:00 2001 From: Georg Sieber Date: Fri, 14 Jun 2024 13:54:03 +0200 Subject: [PATCH] static build for runner deb package - since LAPS runner is a critical system component, I decided to build static binaries as with the OCO agent to make sure the program still runs even if the python runtime changed on the system - this is done in contrast to the LAPS GUI/CLI client, where I want to keep the during installation dynamically created venv --- .github/workflows/build_packages.yml | 8 ++ installer/deb/build.sh | 10 +- .../deb/laps4linux-client/DEBIAN/control | 1 - .../deb/laps4linux-runner/DEBIAN/control | 3 +- .../deb/laps4linux-runner/DEBIAN/postinst | 7 -- laps-client/laps-client.linux.spec | 107 ++++++++++++++++++ laps-runner/laps-runner.linux.spec | 74 ++++++++++++ laps-runner/laps_runner/laps_runner.py | 2 +- 8 files changed, 195 insertions(+), 17 deletions(-) create mode 100644 laps-client/laps-client.linux.spec create mode 100644 laps-runner/laps-runner.linux.spec diff --git a/.github/workflows/build_packages.yml b/.github/workflows/build_packages.yml index 86f6be1..010a222 100644 --- a/.github/workflows/build_packages.yml +++ b/.github/workflows/build_packages.yml @@ -30,6 +30,14 @@ jobs: name: Get version name for Github release title run: cd laps-client && echo "version=$(python3 -c 'import laps_client; print(laps_client.__version__)')" >> $GITHUB_OUTPUT + - name: Create venv, install Python packages, compile binaries - Runner + run: | + cd laps-runner + python -m venv venv + venv/bin/pip3 install pyinstaller . + venv/bin/pyinstaller laps-runner.linux.spec + cd .. + - name: Execute deb build run: cd installer/deb/ && ./build.sh diff --git a/installer/deb/build.sh b/installer/deb/build.sh index f8bd3a1..a1f8318 100755 --- a/installer/deb/build.sh +++ b/installer/deb/build.sh @@ -45,13 +45,11 @@ BUILDDIR=laps4linux-runner if [ -d "$BUILDDIR/usr" ]; then sudo rm -r $BUILDDIR/usr fi +mkdir -p $BUILDDIR/usr/share # copy files in place -sudo install -D -m 644 ../../README.md -t $BUILDDIR/$INSTALLDIR -sudo install -D -m 644 ../../assets/laps-runner.cron $BUILDDIR/etc/cron.hourly/laps-runner -sudo install -D -m 644 ../../laps-runner/laps_runner/*.py -t $BUILDDIR/$INSTALLDIR/laps_runner -sudo install -D -m 644 ../../laps-runner/requirements.txt -t $BUILDDIR/$INSTALLDIR -sudo install -D -m 644 ../../laps-runner/setup.py -t $BUILDDIR/$INSTALLDIR +cp -r ../../laps-runner/dist/laps-runner $BUILDDIR/$INSTALLDIR + # test if we have our own laps-runner config if [ -f ../../laps-runner/laps-runner.json ]; then sudo install -D -m 644 ../../laps-runner/laps-runner.json $BUILDDIR/etc/laps-runner.json @@ -65,7 +63,7 @@ sudo chown -R root:root $BUILDDIR # make binaries available in PATH sudo mkdir -p $BUILDDIR/usr/sbin -sudo ln -sf $INSTALLDIR/venv/bin/laps-runner $BUILDDIR/usr/sbin/laps-runner +sudo ln -sf $INSTALLDIR/laps-runner $BUILDDIR/usr/sbin/laps-runner # build debs diff --git a/installer/deb/laps4linux-client/DEBIAN/control b/installer/deb/laps4linux-client/DEBIAN/control index 57eaaab..bed655d 100644 --- a/installer/deb/laps4linux-client/DEBIAN/control +++ b/installer/deb/laps4linux-client/DEBIAN/control @@ -8,4 +8,3 @@ Conflicts: laps4linux, laps4linux-gui, laps4linux-cli Maintainer: Georg Sieber Description: Linux implementation of the Local Administrator Password Solution (LAPS) GUI from Microsoft. This package contains the management client GUI und CLI for viewing the admin passwords and setting new expiration dates. - diff --git a/installer/deb/laps4linux-runner/DEBIAN/control b/installer/deb/laps4linux-runner/DEBIAN/control index 18a7fe5..418736f 100644 --- a/installer/deb/laps4linux-runner/DEBIAN/control +++ b/installer/deb/laps4linux-runner/DEBIAN/control @@ -3,9 +3,8 @@ Version: 1.8.0 Section: base Priority: optional Architecture: all -Depends: python3, python3-venv, python3-pip, python3-setuptools, python3-gssapi, python3-dnspython, krb5-user, libkrb5-dev +Depends: python3, krb5-user, libkrb5-dev Conflicts: laps4linux, laps4linux-gui, laps4linux-cli Maintainer: Georg Sieber Description: Linux implementation of the Local Administrator Password Solution (LAPS) from Microsoft. This package contains the LAPS runner for automatically changing the local admin password. In order to use the runner, the machine needs to be joined into your domain using Samba 'net ads join', PBIS 'domainjoin-cli join' or 'adcli join'. Please adjust the config file /etc/laps-runner.ini and check if LDAP access is working by executing 'laps-runner -f'. - diff --git a/installer/deb/laps4linux-runner/DEBIAN/postinst b/installer/deb/laps4linux-runner/DEBIAN/postinst index 9c3017b..6c6dab3 100755 --- a/installer/deb/laps4linux-runner/DEBIAN/postinst +++ b/installer/deb/laps4linux-runner/DEBIAN/postinst @@ -5,10 +5,3 @@ set -e # source debconf library. #. /usr/share/debconf/confmodule - -DIR=/usr/share/laps4linux-runner - -# create system-wide venv and install python libraries via pip -python3 -m venv $DIR/venv -$DIR/venv/bin/pip3 install --upgrade $DIR -$DIR/venv/bin/pip3 uninstall -y pip diff --git a/laps-client/laps-client.linux.spec b/laps-client/laps-client.linux.spec new file mode 100644 index 0000000..4ce7001 --- /dev/null +++ b/laps-client/laps-client.linux.spec @@ -0,0 +1,107 @@ +# -*- mode: python ; coding: utf-8 -*- + +block_cipher = None + +def Entrypoint(dist, group, name, **kwargs): + import pkg_resources + + # get toplevel packages of distribution from metadata + def get_toplevel(dist): + distribution = pkg_resources.get_distribution(dist) + if distribution.has_metadata('top_level.txt'): + return list(distribution.get_metadata('top_level.txt').split()) + else: + return [] + + kwargs.setdefault('hiddenimports', []) + packages = [] + for distribution in kwargs['hiddenimports']: + packages += get_toplevel(distribution) + + kwargs.setdefault('pathex', []) + # get the entry point + ep = pkg_resources.get_entry_info(dist, group, name) + # insert path of the egg at the verify front of the search path + kwargs['pathex'] = [ep.dist.location] + kwargs['pathex'] + # script name must not be a valid module name to avoid name clashes on import + script_path = os.path.join(workpath, name + '-script.py') + print("creating script for entry point", dist, group, name) + with open(script_path, 'w') as fh: + print("import", ep.module_name, file=fh) + print("%s.%s()" % (ep.module_name, '.'.join(ep.attrs)), file=fh) + for package in packages: + print("import", package, file=fh) + + return Analysis( + [script_path] + kwargs.get('scripts', []), + **kwargs + ) + +gui_a = Entrypoint('laps4linux_client', 'gui_scripts', 'laps-gui', + pathex=['.'], + binaries=[], + datas=[ ('../assets/laps.png', '.') ], + hiddenimports=[], + hookspath=[], + runtime_hooks=[], + excludes=[], + cipher=block_cipher, + noarchive=False, + optimize=0, +) +cli_a = Entrypoint('laps4linux_client', 'console_scripts', 'laps-cli', + pathex=['.'], + binaries=[], + datas=[ ('../assets/laps.png', '.') ], + hiddenimports=[], + hookspath=[], + runtime_hooks=[], + excludes=[], + cipher=block_cipher, + noarchive=False, + optimize=0, +) +MERGE( (gui_a, 'laps-gui', 'laps-gui'), (cli_a, 'laps-cli', 'laps-cli') ) + +gui_pyz = PYZ(gui_a.pure, gui_a.zipped_data, cipher=block_cipher) +gui_exe = EXE(gui_pyz, gui_a.scripts, [], + exclude_binaries=True, + name='laps-gui', + contents_directory='.', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) + +cli_pyz = PYZ(cli_a.pure, cli_a.zipped_data, cipher=block_cipher) +cli_exe = EXE(cli_pyz, cli_a.scripts, [], + exclude_binaries=True, + name='laps-cli', + contents_directory='.', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) + +coll = COLLECT( + gui_exe, gui_a.binaries, gui_a.zipfiles, gui_a.datas, + cli_exe, cli_a.binaries, cli_a.zipfiles, cli_a.datas, + strip=False, + upx=True, + upx_exclude=[], + name='laps-client' +) diff --git a/laps-runner/laps-runner.linux.spec b/laps-runner/laps-runner.linux.spec new file mode 100644 index 0000000..0730941 --- /dev/null +++ b/laps-runner/laps-runner.linux.spec @@ -0,0 +1,74 @@ +# -*- mode: python ; coding: utf-8 -*- + +block_cipher = None + +def Entrypoint(dist, group, name, **kwargs): + import pkg_resources + + # get toplevel packages of distribution from metadata + def get_toplevel(dist): + distribution = pkg_resources.get_distribution(dist) + if distribution.has_metadata('top_level.txt'): + return list(distribution.get_metadata('top_level.txt').split()) + else: + return [] + + kwargs.setdefault('hiddenimports', []) + packages = [] + for distribution in kwargs['hiddenimports']: + packages += get_toplevel(distribution) + + kwargs.setdefault('pathex', []) + # get the entry point + ep = pkg_resources.get_entry_info(dist, group, name) + # insert path of the egg at the verify front of the search path + kwargs['pathex'] = [ep.dist.location] + kwargs['pathex'] + # script name must not be a valid module name to avoid name clashes on import + script_path = os.path.join(workpath, name + '-script.py') + print("creating script for entry point", dist, group, name) + with open(script_path, 'w') as fh: + print("import", ep.module_name, file=fh) + print("%s.%s()" % (ep.module_name, '.'.join(ep.attrs)), file=fh) + for package in packages: + print("import", package, file=fh) + + return Analysis( + [script_path] + kwargs.get('scripts', []), + **kwargs + ) + +a = Entrypoint('laps4linux_runner', 'console_scripts', 'laps-runner', + pathex=['.'], + binaries=[], + datas=[], + hiddenimports=[], + hookspath=[], + runtime_hooks=[], + excludes=[], + cipher=block_cipher, + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE(pyz, a.scripts, [], + exclude_binaries=True, + name='laps-runner', + contents_directory='.', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) +coll = COLLECT(exe, a.binaries, a.datas, + strip=False, + upx=True, + upx_exclude=[], + name='laps-runner', +) diff --git a/laps-runner/laps_runner/laps_runner.py b/laps-runner/laps_runner/laps_runner.py index 5110632..ae91f7b 100755 --- a/laps-runner/laps_runner/laps_runner.py +++ b/laps-runner/laps_runner/laps_runner.py @@ -330,7 +330,7 @@ def main(): except Exception as e: print(traceback.format_exc()) runner.logger.critical(__title__+': Error while executing workflow: '+str(e)) - exit(1) + sys.exit(1) return