ToolJet/docker/nsjail/python-execution.cfg
Adish M 05efcd622f
feat(nsjail): add Python execution configurations (#14816)
* feat(nsjail): add Python execution configurations

* enhance Python security mode detection and configuration

* consolidate Python execution configurations and remove USERNS mode
2026-01-16 13:06:06 +05:30

249 lines
9.1 KiB
INI

# ============================================================================
# nsjail Configuration: FULL Mode (CAP_SYS_ADMIN)
# ============================================================================
#
# This configuration provides maximum isolation for Python code execution:
# - 7 Linux namespaces (mount, PID, network, IPC, UTS, user, cgroup)
# - cgroups v2 resource limits (if kernel supports)
# - Seccomp syscall filtering
# - Complete filesystem isolation
# - Network isolation (no loopback)
#
# Requirements:
# - CAP_SYS_ADMIN capability
# - Kernel 4.6+ for all features
# - cgroups v2 (kernel 5.2+) for hard memory limits
#
# ============================================================================
name: "tooljet-python-full"
description: "ToolJet Python Execution - FULL Mode (Maximum Isolation)"
mode: ONCE
# ============================================================================
# NAMESPACE ISOLATION - All 7 namespaces enabled
# ============================================================================
clone_newuser: true # User namespace - UID/GID mapping
clone_newns: true # Mount namespace - Isolated filesystem
clone_newpid: true # PID namespace - Process tree isolation
clone_newipc: true # IPC namespace - No shared memory with host
clone_newuts: true # UTS namespace - Hostname isolation
clone_newnet: true # Network namespace - No network interfaces
clone_newcgroup: true # Cgroup namespace - Resource isolation
# Map sandbox user to nobody (UID 65534)
uidmap { inside_id: "0" outside_id: "65534" count: 1 }
gidmap { inside_id: "0" outside_id: "65534" count: 1 }
# ============================================================================
# CAPABILITIES AND PRIVILEGES
# ============================================================================
# Drop all capabilities inside sandbox
keep_caps: false
# Enforce no_new_privs (prevent privilege escalation)
disable_no_new_privs: false
# ============================================================================
# TIME AND RESOURCE LIMITS
# ============================================================================
# Wall-clock timeout (10 seconds total execution time)
time_limit: 10
# CPU time limit (5 seconds of actual CPU usage)
rlimit_cpu: 5
# Address space limit (512MB - fallback if cgroups unavailable)
rlimit_as: 536870912 # 512MB in bytes
# File size limit (1MB max file write)
rlimit_fsize: 1048576
# Open file descriptors limit
rlimit_nofile: 64
# Process limit (only 1 process allowed - no forking)
rlimit_nproc: 1
# Core dumps disabled
rlimit_core: 0
# ============================================================================
# CGROUPS V2 - Hard Resource Limits (preferred)
# ============================================================================
# Note: Requires cgroups v2 (kernel 5.2+) and proper cgroup delegation
# If unavailable, falls back to rlimits above
use_cgroupv2: true
# Hard memory limit (128MB - OOM killer enforced)
cgroup_mem_max: 134217728 # 128MB in bytes
# Disable memory swap
cgroup_mem_swap_max: 0
# CPU shares (relative weight for CPU scheduling)
cgroup_cpu_ms_per_sec: 1000 # 100% of 1 CPU core max
# Maximum number of processes in cgroup
cgroup_pids_max: 1
# ============================================================================
# NETWORK ISOLATION
# ============================================================================
# Disable loopback interface (no 127.0.0.1)
# This prevents access to localhost services and cloud metadata APIs
iface_no_lo: true
# ============================================================================
# FILESYSTEM MOUNTS
# ============================================================================
# Working directory inside sandbox
cwd: "/home"
# Hostname for sandbox
hostname: "tooljet-sandbox"
# System libraries (read-only)
mount { src: "/usr" dst: "/usr" is_bind: true rw: false }
mount { src: "/lib" dst: "/lib" is_bind: true rw: false }
mount { src: "/lib64" dst: "/lib64" is_bind: true rw: false mandatory: false }
mount { src: "/usr/lib" dst: "/usr/lib" is_bind: true rw: false }
# ld.so.cache for dynamic linking (read-only)
mount { src: "/etc/ld.so.cache" dst: "/etc/ld.so.cache" is_bind: true rw: false mandatory: false }
# Essential devices
mount { src: "/dev/null" dst: "/dev/null" is_bind: true rw: true }
mount { src: "/dev/zero" dst: "/dev/zero" is_bind: true rw: false }
mount { src: "/dev/urandom" dst: "/dev/urandom" is_bind: true rw: false }
# Writable tmpfs for working directory (50MB limit)
mount { dst: "/home" fstype: "tmpfs" rw: true options: "size=52428800" }
# Writable tmpfs for /tmp (16MB limit)
mount { dst: "/tmp" fstype: "tmpfs" rw: true options: "size=16777216" }
# Create minimal /etc/passwd and /etc/group
mount { src_content: "sandbox:x:0:0:Sandbox User:/home:/bin/false" dst: "/etc/passwd" }
mount { src_content: "sandbox:x:0:" dst: "/etc/group" }
# ============================================================================
# PYTHON RUNTIME MOUNT
# ============================================================================
# This will be dynamically added by the executor:
# mount { src: "/opt/python-runtime" dst: "/python" is_bind: true rw: false }
# ============================================================================
# PACKAGE BUNDLE MOUNT (Dynamic)
# ============================================================================
# This will be dynamically added if bundle exists:
# mount { src: "/tmp/bundles/<bundle-id>" dst: "/packages" is_bind: true rw: false }
# ============================================================================
# USER CODE MOUNT (Dynamic)
# ============================================================================
# This will be dynamically added at execution time:
# mount { src: "/tmp/executions/<exec-id>/code.py" dst: "/home/code.py" is_bind: true rw: false }
# mount { src: "/tmp/executions/<exec-id>/state.json" dst: "/home/state.json" is_bind: true rw: false }
# ============================================================================
# ENVIRONMENT VARIABLES
# ============================================================================
# Clean environment (do NOT inherit from parent)
keep_env: false
# Set minimal safe environment
envar: "PATH=/usr/local/bin:/usr/bin:/bin"
envar: "HOME=/home"
envar: "PYTHONPATH=/packages/site-packages"
envar: "PYTHONDONTWRITEBYTECODE=1"
envar: "PYTHONUNBUFFERED=1"
envar: "LANG=C.UTF-8"
envar: "LC_ALL=C.UTF-8"
# Limit thread usage for numpy/scipy
envar: "OMP_NUM_THREADS=2"
envar: "OPENBLAS_NUM_THREADS=2"
envar: "MKL_NUM_THREADS=2"
# ============================================================================
# SECCOMP SYSCALL FILTER
# ============================================================================
# Block dangerous syscalls that could:
# - Escape sandbox (mount, pivot_root, etc.)
# - Debug other processes (ptrace)
# - Load kernel modules
# - Reboot system
#
# Network syscalls are allowed here because network namespace already
# blocks all network access. Attempting socket() will fail with EACCES.
# ============================================================================
# ptrace: Return EPERM instead of killing process (for testability)
seccomp_string: "ERRNO(1) {"
seccomp_string: " ptrace"
seccomp_string: "}"
# Kill process on these syscalls
seccomp_string: "KILL {"
# Debugging/inspection
seccomp_string: " process_vm_readv,"
seccomp_string: " process_vm_writev,"
# Kernel module loading
seccomp_string: " init_module,"
seccomp_string: " finit_module,"
seccomp_string: " delete_module,"
# System control
seccomp_string: " kexec_load,"
seccomp_string: " kexec_file_load,"
seccomp_string: " reboot,"
seccomp_string: " sethostname,"
seccomp_string: " setdomainname,"
# Namespace manipulation (redundant but explicit)
seccomp_string: " unshare,"
seccomp_string: " setns,"
# Filesystem manipulation
seccomp_string: " mount,"
seccomp_string: " umount,"
seccomp_string: " umount2,"
seccomp_string: " pivot_root,"
seccomp_string: " chroot,"
# Performance monitoring
seccomp_string: " perf_event_open,"
# BPF (could be used for privilege escalation)
seccomp_string: " bpf,"
# User namespace creation (prevent nested sandboxes)
seccomp_string: " clone,"
seccomp_string: " clone3"
seccomp_string: "}"
# Allow all other syscalls
seccomp_string: "DEFAULT ALLOW"
# ============================================================================
# LOGGING
# ============================================================================
# Log level: FATAL, ERROR, WARNING, INFO, DEBUG
log_level: WARNING
# Don't log every syscall (performance)
log_fd: -1
# ============================================================================
# EXECUTION
# ============================================================================
# The actual command to execute is provided at runtime:
# nsjail --config /etc/nsjail/python-execution-full.cfg \
# --bindmount_ro /opt/python-runtime:/python \
# --bindmount_ro /tmp/bundles/<id>:/packages \
# --bindmount /tmp/executions/<id>/code.py:/home/code.py \
# -- /python/bin/python3 /home/code.py
# ============================================================================