Handling Python dependencies

The Python dependencies are not a part of the basic Python program and are instead loaded during runtime. Therefore, the application measurement, when loaded, does not cover these dependencies.

Anjuna provides the user a way to identify these dependencies.

A Python application can have two types of dependencies - static dependencies and dependencies that are computed during execution.

A static dependency is one that appears in the code as follows:

import mariadb

Here the code says that it depends on the MariaDB module.

A dependency that is computed during execution is one that is realized during runtime. The following example is from the file onnx/symbolic_registry.py from the PyTorch module:

for opset_version in _onnx_stable_opsets + [_onnx_main_opset]:
    module =
importlib.import_module("torch.onnx.symbolic_opset{}".format(opset_version))
    _symbolic_versions[opset_version] = module

You want to identify these two types of dependencies and then store their data in the manifest file of the enclave.

Identifying Python static dependencies

The static dependencies can be identified by the Anjuna manifest compiler. The compiler will add all of the static dependencies and their measurements to the manifest file.

This is done by including the tag python_config in the manifest template.

For the basic analyzer, include an empty python_config tag in the template, i.e., add the following line to the template:

python_config: {}

This allows anjuna-compile-manifest to automatically invoke the Python analyzer to include the Python dependencies in the application manifest from the manifest template.

If you change your code or change your dependencies, you should re-run the anjuna-compile-manifest (or remove the python*.manifest.sgx file and re-run anjuna-sgxrun).

Identifying Python dependencies that are computed during execution

The dependencies that are computed during execution can be identified when running the application in the enclave. For any dependency that is not a part of the manifest, or its measurements are different, a proper message will be printed to stderr.

Dependencies that are not a part of the manifest

When in non-strict mode, any dependency that is missing from the manifest file will be printed to stderr in the following format:

Module: <module_name> not in config

In the non-strict mode, the module will be allowed to be imported.

When in strict mode, the module will not be allowed to be imported for any dependency that is missing from the manifest file and as a result triggers a ModuleNotFoundError by the Python engine.

To solve the problem of the missing dependency, you will need to add the modules to the manifest template file. For example, if the modules torch.onnx.symbolic_opset7 and torch.onnx.symbolic_opset8 are computed during execution, you should add a python_modules tag into your manifest template, under the python_config tag with the field name and the value <module_name> for each missing dependency.

In the above example, you will have the following tag in the manifest template:

python_config:
  python_modules:
    - name: torch.onnx.symbolic_opset7
    - name: torch.onnx.symbolic_opset8

Dependencies with a different measurement

When in non-strict mode, any dependency whose measurement differs from the one identified by the analyzer will be printed to stderr in the following format:

Module: <module_name> has improper hash value
Expected: <expected_module_measurement>
Found: <actual_module_measurement>

In the non-strict mode, the module will be allowed to be imported.

When in strict mode, any dependency that has a different hash prevents the module from getting imported and as a result triggers a ModuleNotFoundError by the Python engine.

To solve the problem of a measurement that differs, determine if the module is indeed the correct module. If you trust the module with the new measurement, delete the manifest file and recompile it from the template (anjuna-compile-manifest or anjuna-sgxrun).