Protecting Python Programs with CTE
CTE uses SHA-256 to generate binary signatures that can be used to allow certain trust levels for a customer application. Capturing the signature of a binary allows CTE to determine if the application's binary has changed in any way, because those changes might mean the security of the binary has been compromised.
While this mechanism works well for executable binaries to verify an application's validity at run time, this mechanism does not work for applications implemented with interpreter languages because CTE can only access the interpreter's signature to validate the file. The program itself is something that is executed by the interpreter and thus CTE cannot hook into this process. Scripting languages like Python are particularly difficult because many Python programs include other Python files, which means there is no single program signature that defines the execution path.
While CTE cannot use signature sets to protect traditional Python programs, tools like PyInstaller allow for the creation of a self-contained Python executable that CTE can sign and protect.
Benefits of Using PyInstaller with CTE
Using PyInstaller allows the creation of self-contained Python binary packages. These packages contain the Python interpreter, the Python program, and all required modules and libraries. Because each package includes all of the files it needs, it is immune to changes to the Python modules or libraries on the system. This not only allows CTE to sign the binary but it also adds extra protection against an attacker trying to gain access through modifications of the underlying libraries.
Another benefit is that it provides security administrators with a stable process that reasonably immune to system updates done by a system administrator. Once the application binary has been created and signed, the behavior of the program will not change because of updates made through the OS packaging system or Python's PIP package manager.
Getting PyInstaller
PyInstaller is an OpenSource project. The instructions for installing PyInstaller, as well as the documentation, are available on the project page.
Example Usage
The exact usage of PyInstaller arguments varies based on the details of the Python program being packaged into an executable. This example shows what is needed to generate a self-contained AWS CLI binary with PyInstaller. It uses the --onefile
flag to generate a single, self-contained binary. It assumes that the pip3 Python package manager is already installed on the system.
Installing PyInstaller
The following command installs PyInstaller in ${HOME}/.local/bin/PyInstaller
:
pip3 install --user PyInstaller
Installing the Amazon AWS Command Line Utility
The following command installs the AWS utility in ${HOME}/.local/bin/aws
:
pip3 install --user awscli
Creating a Runtime Hook
The following command creates a runtime hook that changes the Python botocore root
directory within the packaged binary. This is necessary to make sure that we use the botocore
packaged by PyInstaller. The hook is save to a file named change-botocore-root.py
. For more information on runtime hooks, see the PyInstaller documentation.
cat <<EOF > ${HOME}/change-botocore-root.py
import sys
import botocore
if getattr(sys, 'frozen', False):
botocore.BOTOCORE_ROOT = sys._MEIPASS
EOF
In order to get the AWS CLI utility working with PyInstaller, we need to specify some hidden imports as well as add some folders from the awscli
and botocore
packages into the executable. For more information about hidden imports and adding files to the executable, see the PyInstaller documentation.
${HOME}/.local/bin/PyInstaller --onefile \
--hiddenimport=awscli.handlers \
--hiddenimport=pipes \
--add-data=${HOME}/.local/lib/python3.6/site-packages/awscli/data:data \
--add-data=${HOME}/.local/lib/python3.6/site-packages/botocore/data:data \
--runtime-hook=${HOME}/change-botocore-root.py \
${HOME}/.local/bin/aws
The resulting binary will be saved in the current working directory as dist/aws
.