aboutsummaryrefslogtreecommitdiffstats
path: root/poetry_restrict_plugin/plugin.py
diff options
context:
space:
mode:
Diffstat (limited to 'poetry_restrict_plugin/plugin.py')
-rw-r--r--poetry_restrict_plugin/plugin.py86
1 files changed, 86 insertions, 0 deletions
diff --git a/poetry_restrict_plugin/plugin.py b/poetry_restrict_plugin/plugin.py
new file mode 100644
index 0000000..b75c669
--- /dev/null
+++ b/poetry_restrict_plugin/plugin.py
@@ -0,0 +1,86 @@
+import os.path
+import pathlib
+import sys
+import traceback
+
+import poetry as poetry_package
+from cleo.io.io import IO
+from landlock import FSAccess, Ruleset
+from poetry.plugins.plugin import Plugin
+from poetry.poetry import Poetry
+
+
+def existing_paths(paths):
+ for path in paths:
+ if os.path.exists(path):
+ yield path
+
+
+class RestrictPlugin(Plugin):
+ def landlock(self, poetry: Poetry):
+ poetry_libs_path = pathlib.Path(poetry_package.__path__._path[0]).parent
+
+ ruleset = Ruleset()
+
+ # Rules for Poetry's virtual environment management
+ # Storing the virtual environment
+ ruleset.allow(poetry.config.virtualenvs_path, rules=FSAccess.all())
+ # Cached dependencies
+ ruleset.allow(poetry.config.artifacts_cache_directory, rules=FSAccess.all())
+ ruleset.allow(poetry.config.repository_cache_directory, rules=FSAccess.all())
+ # Temporary storage
+ ruleset.allow("/tmp", rules=FSAccess.all() & ~FSAccess.EXECUTE)
+ # Poetry may also want to late-import some of its dependencies, or built-in modules
+ ruleset.allow(*existing_paths(sys.path), rules=FSAccess.READ_FILE | FSAccess.READ_DIR)
+
+ # Finally, the Python executable may need to import some of its shared libraries
+ ruleset.allow(
+ *existing_paths(("/lib", "/lib64")),
+ rules=FSAccess.READ_FILE | FSAccess.READ_DIR | FSAccess.EXECUTE,
+ )
+ # and in poetry shell, we might want to run some system executables, too
+ ruleset.allow("/usr/bin", rules=FSAccess.READ_FILE | FSAccess.READ_DIR | FSAccess.EXECUTE)
+
+
+ # We allow read access here, later we might want to restrict the pid namespace though
+ ruleset.allow("/proc", rules=FSAccess.READ_FILE | FSAccess.READ_DIR)
+ # needed for /dev/tty and /dev/pty devices, see /usr/lib/python3.11/pty.py
+ ruleset.allow("/dev", rules=FSAccess.READ_FILE | FSAccess.READ_DIR | FSAccess.WRITE_FILE)
+
+ # Python's `zoneinfo` module
+ ruleset.allow("/usr/share/zoneinfo/", rules=FSAccess.READ_FILE | FSAccess.READ_DIR)
+
+ ruleset.allow(
+ # We need to know which DNS resolver to use, and any custom hosts
+ *existing_paths(("/etc/resolv.conf", "/etc/hosts")),
+ # pip reads this file in _vendor/distro/distro.py
+ *existing_paths(("/etc/debian_version")),
+ # I'm not opposed to including things like this because I don't want to annoy people
+ # when their tooling doesn't work. But we have to be conservative. I think shells
+ # are fine, but if there was some further tooling (e.g. shell tools run at startup)
+ # I don't think those should be included.
+ *existing_paths(("/etc/bash.bashrc", os.path.expanduser("~/.bashrc"))),
+ rules=FSAccess.READ_FILE,
+ )
+ ruleset.allow("/etc/ssl/certs", "/usr/local/share/ca-certificates", rules=FSAccess.READ_FILE | FSAccess.READ_DIR)
+
+ pre_commit_cache = os.path.expanduser("~/.cache/pre-commit")
+ if os.path.exists(pre_commit_cache):
+ ruleset.allow(pre_commit_cache)
+
+ # Allow manipulation of files in our projects, e.g. for linters.
+ # We might need to check this more thoroughly. For instance, configuring custom
+ # filter programs in gitattributes might allow a sandbox escape.
+ ruleset.allow(".")
+
+ ruleset.apply()
+
+ def activate(self, poetry: Poetry, io: IO):
+ try:
+ self.landlock(poetry)
+ io.write_line("<info>poetry-restrict-plugin</info>: Landlock engaged.")
+ except Exception as err:
+ io.write_line("<error>Fatal error trying to enforce Landlock rules:</error>")
+ traceback.print_exception(err)
+ io.write_line("<error>This is an issue of the Poetry restrict plugin, not of Poetry itself.</error>")
+ raise