dify-sandbox学习笔记

Posted by iceyao on Thursday, August 8, 2024

1. dify-sandbox简介

dify-Sandbox这个系统具有让不可信的代码得以运行的功能,且能保障运行环境的安全性。其设计初衷是适应多租户的场景,意味着众多用户都有提交需执行代码的权限。而代码的执行处于沙盒这样一种特定环境中,在此环境里,对代码所能获取的资源以及能够进行的系统调用都施加了限制,以确保安全性和稳定性。

2. 代码实现

以tag 2.0.4为例,采用golang编写

git clong https://github.com/langgenius/dify-sandbox.git
git checkout 2.0.4

2.1 主函数

dify-sandbox本身是一个golang gin框架的api server服务

package main

import "github.com/langgenius/dify-sandbox/internal/server"

func main() {
	// 主函数
	server.Run()
}
package server

import (
	"fmt"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/langgenius/dify-sandbox/internal/controller"
	"github.com/langgenius/dify-sandbox/internal/core/runner/python"
	"github.com/langgenius/dify-sandbox/internal/static"
	"github.com/langgenius/dify-sandbox/internal/utils/log"
)

func initConfig() {
	// 从config.yaml配置文件中初始化配置
	err := static.InitConfig("conf/config.yaml")
	if err != nil {
		log.Panic("failed to init config: %v", err)
	}
	log.Info("config init success")

    // 从dependencies/python-requirements.txt文件中读取需要安装的python依赖
	err = static.SetupRunnerDependencies()
	if err != nil {
		log.Error("failed to setup runner dependencies: %v", err)
	}
	log.Info("runner dependencies init success")
}

func initServer() {
    // 获取全局的dify-sandbox配置,是个单例
	config := static.GetDifySandboxGlobalConfigurations()
	if !config.App.Debug {
		gin.SetMode(gin.ReleaseMode)
	}
    // 获取gin Engine实例
	r := gin.Default()
    // 设置middleware和路由
	controller.Setup(r)
    // 启动gin server
	r.Run(fmt.Sprintf(":%d", config.App.Port))
}

func initDependencies() {
	log.Info("installing python dependencies...")
	// 获取需要安装的python依赖
	dependenices := static.GetRunnerDependencies()
	// 使用exec.Command安装python依赖
	err := python.InstallDependencies(dependenices.PythonRequirements)
	if err != nil {
		log.Panic("failed to install python dependencies: %v", err)
	}
	log.Info("python dependencies installed")

	log.Info("initializing python dependencies sandbox...")
	// 初始化python代码执行模块的环境
	err = python.PreparePythonDependenciesEnv()
	if err != nil {
		log.Panic("failed to initialize python dependencies sandbox: %v", err)
	}
	log.Info("python dependencies sandbox initialized")

	// 每隔30分钟更新一次sandbox的python依赖安装
	go func() {
		ticker := time.NewTicker(30 * time.Minute)
		for range ticker.C {
			log.Info("updating python dependencies...")
			err := python.InstallDependencies(dependenices.PythonRequirements)
			if err != nil {
				log.Error("failed to update python dependencies: %v", err)
			}
			log.Info("python dependencies updated")
		}
	}()
}

func Run() {
    // 初始化配置
	initConfig()
    // 初始化依赖和sandbox环境
	initDependencies()
    // 初始化gin server
	initServer()
}

2.2 GetDifySandboxGlobalConfigurations

获取全局配置的函数

// 声明一个全局配置的变量difySandboxGlobalConfigurations
var difySandboxGlobalConfigurations types.DifySandboxGlobalConfigurations

func InitConfig(path string) error {
	difySandboxGlobalConfigurations = types.DifySandboxGlobalConfigurations{}

	// read config file
	// 默认从conf/config.yaml读取配置
	configFile, err := os.Open(path)
	if err != nil {
		return err
	}

	defer configFile.Close()

	// parse config file
	decoder := yaml.NewDecoder(configFile)
	err = decoder.Decode(&difySandboxGlobalConfigurations)
	if err != nil {
		return err
	}

	// 支持环境变量覆盖配置
	debug, err := strconv.ParseBool(os.Getenv("DEBUG"))
	if err == nil {
		difySandboxGlobalConfigurations.App.Debug = debug
	}

	max_workers := os.Getenv("MAX_WORKERS")
	if max_workers != "" {
		difySandboxGlobalConfigurations.MaxWorkers, _ = strconv.Atoi(max_workers)
	}

	max_requests := os.Getenv("MAX_REQUESTS")
	if max_requests != "" {
		difySandboxGlobalConfigurations.MaxRequests, _ = strconv.Atoi(max_requests)
	}

	port := os.Getenv("SANDBOX_PORT")
	if port != "" {
		difySandboxGlobalConfigurations.App.Port, _ = strconv.Atoi(port)
	}

	timeout := os.Getenv("WORKER_TIMEOUT")
	if timeout != "" {
		difySandboxGlobalConfigurations.WorkerTimeout, _ = strconv.Atoi(timeout)
	}

	api_key := os.Getenv("API_KEY")
	if api_key != "" {
		difySandboxGlobalConfigurations.App.Key = api_key
	}

	python_path := os.Getenv("PYTHON_PATH")
	if python_path != "" {
		difySandboxGlobalConfigurations.PythonPath = python_path
	}

	if difySandboxGlobalConfigurations.PythonPath == "" {
		difySandboxGlobalConfigurations.PythonPath = "/usr/local/bin/python3"
	}

	python_lib_path := os.Getenv("PYTHON_LIB_PATH")
	if python_lib_path != "" {
		difySandboxGlobalConfigurations.PythonLibPaths = strings.Split(python_lib_path, ",")
	}

	// PythonLibPaths如果为空,设置默认值,从常量DEFAULT_PYTHON_LIB_REQUIREMENTS读取
	if len(difySandboxGlobalConfigurations.PythonLibPaths) == 0 {
		difySandboxGlobalConfigurations.PythonLibPaths = DEFAULT_PYTHON_LIB_REQUIREMENTS
	}

	nodejs_path := os.Getenv("NODEJS_PATH")
	if nodejs_path != "" {
		difySandboxGlobalConfigurations.NodejsPath = nodejs_path
	}

	if difySandboxGlobalConfigurations.NodejsPath == "" {
		difySandboxGlobalConfigurations.NodejsPath = "/usr/local/bin/node"
	}

	enable_network := os.Getenv("ENABLE_NETWORK")
	if enable_network != "" {
		difySandboxGlobalConfigurations.EnableNetwork, _ = strconv.ParseBool(enable_network)
	}

	if difySandboxGlobalConfigurations.EnableNetwork {
		log.Info("network has been enabled")
		socks5_proxy := os.Getenv("SOCKS5_PROXY")
		if socks5_proxy != "" {
			difySandboxGlobalConfigurations.Proxy.Socks5 = socks5_proxy
		}

		if difySandboxGlobalConfigurations.Proxy.Socks5 != "" {
			log.Info("using socks5 proxy: %s", difySandboxGlobalConfigurations.Proxy.Socks5)
		}

		https_proxy := os.Getenv("HTTPS_PROXY")
		if https_proxy != "" {
			difySandboxGlobalConfigurations.Proxy.Https = https_proxy
		}

		if difySandboxGlobalConfigurations.Proxy.Https != "" {
			log.Info("using https proxy: %s", difySandboxGlobalConfigurations.Proxy.Https)
		}

		http_proxy := os.Getenv("HTTP_PROXY")
		if http_proxy != "" {
			difySandboxGlobalConfigurations.Proxy.Http = http_proxy
		}

		if difySandboxGlobalConfigurations.Proxy.Http != "" {
			log.Info("using http proxy: %s", difySandboxGlobalConfigurations.Proxy.Http)
		}
	}
	return nil
}

// avoid global modification, use value copy instead
// 获取全局配置
func GetDifySandboxGlobalConfigurations() types.DifySandboxGlobalConfigurations {
	return difySandboxGlobalConfigurations
}
// 常量DEFAULT_PYTHON_LIB_REQUIREMENTS的定义
var DEFAULT_PYTHON_LIB_REQUIREMENTS = []string{
	"/usr/local/lib/python3.10",
	"/usr/lib/python3.10",
	"/usr/lib/python3",
	"/usr/lib/x86_64-linux-gnu/libssl.so.3",
	"/usr/lib/x86_64-linux-gnu/libcrypto.so.3",
	"/etc/ssl/certs/ca-certificates.crt",
	"/etc/nsswitch.conf",
	"/etc/hosts",
	"/etc/resolv.conf",
	"/run/systemd/resolve/stub-resolv.conf",
	"/run/resolvconf/resolv.conf",
}

2.3 PreparePythonDependenciesEnv

初始化python代码执行模块的环境

// 在Go 1.16中embed是新加入的包,通过go:embed指令在编译阶段将静态资源文件打包进程序并提供访问能力
//go:embed env.sh
var env_script string

func PreparePythonDependenciesEnv() error {
	// 获取全局配置
	config := static.GetDifySandboxGlobalConfigurations()
	// 初始化TempDirRunner实例
	runner := runner.TempDirRunner{}
	// 调用WithTempDir方法创建临时目录:
	// 1.创建/tmp/sandbox-xxx目录
	// 2.`cp -r`WithTempDir函数中的paths到/tmp/sandbox-xxx目录下
	// 3.Chdir切换工作目录到/tmp/sandbox-xxx
	// 4.调用WithTempDir函数中的closures回调函数
	err := runner.WithTempDir("/", []string{}, func(root_path string) error {
		// 执行回调函数中的逻辑
		// 1.将env.sh文件写入/tmp/sandbox-xxx目录下
		err := os.WriteFile(path.Join(root_path, "env.sh"), []byte(env_script), 0755)
		if err != nil {
			return err
		}
		// 2.遍历config.PythonLibPaths,默认读取的是常量DEFAULT_PYTHON_LIB_REQUIREMENTS,执行env.sh脚本
		for _, lib_path := range config.PythonLibPaths {
			// check if the lib path is available
			if _, err := os.Stat(lib_path); err != nil {
				log.Warn("python lib path %s is not available", lib_path)
				continue
			}
			// 常量LIB_PATH="/var/sandbox/sandbox-python"
			// 执行env.sh复制文件逻辑
			exec_cmd := exec.Command(
				"bash",
				path.Join(root_path, "env.sh"),
				lib_path,
				LIB_PATH,
			)
			exec_cmd.Stderr = os.Stderr

			if err := exec_cmd.Run(); err != nil {
				return err
			}
		}
		// 删除/tmp/sandbox-xxx目录
		os.RemoveAll(root_path)
		os.Remove(root_path)
		return nil
	})

	return err
}
#!/bin/bash

# Check if the correct number of arguments are provided
if [ "$#" -ne 2 ]; then
    echo "Usage: $0 <src> <dest>"
    exit 1
fi

src="$1"
dest="$2"

# Function to copy and link files
copy_and_link() {
    local src_file="$1"
    local dest_file="$2"

    if [ -L "$src_file" ]; then
        # If src_file is a symbolic link, copy it without changing permissions
        cp -P "$src_file" "$dest_file"
    elif [ -b "$src_file" ] || [ -c "$src_file" ]; then
        # If src_file is a device file, copy it and change permissions
        cp "$src_file" "$dest_file"
        chmod 444 "$dest_file"
    else
        # Otherwise, create a hard link and change the permissions to read-only
        ln -f "$src_file" "$dest_file" 2>/dev/null || { cp "$src_file" "$dest_file" && chmod 444 "$dest_file"; }
    fi
}

# Check if src is a file or directory
if [ -f "$src" ]; then
    # src is a file, create hard link directly in dest
    mkdir -p "$(dirname "$dest/$src")"
    copy_and_link "$src" "$dest/$src"
elif [ -d "$src" ]; then
    # src is a directory, process as before
    mkdir -p "$dest/$src"

    # Find all files in the source directory
    find "$src" -type f | while read -r file; do
        # Get the relative path of the file
        rel_path="${file#$src/}"
        # Get the directory of the relative path
        rel_dir=$(dirname "$rel_path")
        # Create the same directory structure in the destination
        mkdir -p "$dest/$src/$rel_dir"
        # Copy and link the file
        copy_and_link "$file" "$dest/$src/$rel_path"
    done
else
    echo "Error: $src is neither a file nor a directory"
    exit 1
fi

env.sh脚本逻辑: 1.检查是否提供了正确数量的参数(源路径 和目标路径 ) 2.定义 copy_and_link 函数: - 如果源文件是符号链接,则复制它且保留权限。 - 如果源文件是设备文件,则复制它并设置只读权限。 - 否则创建硬链接,并设置只读权限;若失败则复制文件并设为只读。 3.判断源路径 : - 若是文件,则在目标目录下创建硬链接。 - 若是目录,则递归处理所有文件,创建相应的目录结构,并对每个文件调用 copy_and_link 函数。 - 若 既不是文件也不是目录,则输出错误信息并退出。

2.4 gin middleware和路由

dify-sandbox中的gin api middleware和路由定义

func Setup(eng *gin.Engine) {
	// 校验X-Api-Key是否等于config.App.Key,默认值为dify-sandbox
	eng.Use(middleware.Auth())
	// 在sandbox中运行代码,支持python、nodejs语言
	eng.POST(
		"/v1/sandbox/run",
		middleware.MaxRequest(static.GetDifySandboxGlobalConfigurations().MaxRequests),
		middleware.MaxWorker(static.GetDifySandboxGlobalConfigurations().MaxWorkers),
		RunSandboxController,
	)
	// 获取python依赖包列表,默认包含jinja2、httpx、requests
	eng.GET(
		"/v1/sandbox/dependencies",
		GetDependencies,
	)
	// 直接触发PreparePythonDependenciesEnv
	eng.POST(
		"/v1/sandbox/dependencies/update",
		UpdateDependencies,
	)
}

2.4.1 RunSandboxController

/v1/sandbox/run在sandbox中运行代码的处理逻辑

func RunSandboxController(c *gin.Context) {
	BindRequest(c, func(req struct {
		Language      string `json:"language" form:"language" binding:"required"`
		Code          string `json:"code" form:"code" binding:"required"`
		Preload       string `json:"preload" form:"preload"`
		EnableNetwork bool   `json:"enable_network" form:"enable_network"`
	}) {
		switch req.Language {
		// python语言
		case "python3":
			c.JSON(200, service.RunPython3Code(req.Code, req.Preload, &runner_types.RunnerOptions{
				EnableNetwork: req.EnableNetwork,
			}))
		// nodejs语言
		case "nodejs":
			c.JSON(200, service.RunNodeJsCode(req.Code, req.Preload, &runner_types.RunnerOptions{
				EnableNetwork: req.EnableNetwork,
			}))
		default:
			c.JSON(400, types.ErrorResponse(-400, "unsupported language"))
		}
	})
}

针对python语言的处理逻辑

func RunPython3Code(code string, preload string, options *runner_types.RunnerOptions) *types.DifySandboxResponse {
	// 校验enable_network参数是否一致
	if err := checkOptions(options); err != nil {
		return types.ErrorResponse(-400, err.Error())
	}
	// 获取全局配置中的worker_timeout参数,默认值为5s
	timeout := time.Duration(
		static.GetDifySandboxGlobalConfigurations().WorkerTimeout * int(time.Second),
	)

	// 创建python.PythonRunner对象,并调用其Run方法执行python代码
	runner := python.PythonRunner{}
	stdout, stderr, done, err := runner.Run(
		code, timeout, nil, preload, options,
	)
	if err != nil {
		return types.ErrorResponse(-500, err.Error())
	}

	stdout_str := ""
	stderr_str := ""

	defer close(done)
	defer close(stdout)
	defer close(stderr)

	for {
		select {
		// 如果done通道关闭,则返回成功响应
		case <-done:
			return types.SuccessResponse(&RunCodeResponse{
				Stdout: stdout_str,
				Stderr: stderr_str,
			})
		// 从stdout和stderr通道中读取数据,并拼接到stdout_str和stderr_str中	
		case out := <-stdout:
			stdout_str += string(out)
		case err := <-stderr:
			stderr_str += string(err)
		}
	}
}
func (p *PythonRunner) Run(
	code string,
	timeout time.Duration,
	stdin []byte,
	preload string,
	options *types.RunnerOptions,
) (chan []byte, chan []byte, chan bool, error) {
	// 获取全局配置
	configuration := static.GetDifySandboxGlobalConfigurations()

	// 初始化代码执行环境
	// 1.环境检查与清理:确保必要的库可用/var/sandbox/sandbox-python/python.so,若不可用则释放相关资源。
	// 2.临时脚本准备:
	//   生成一个随机的 UUID 并进行格式调整以适合作为临时 Python 脚本的文件名。
	//   根据提供的模板 sandbox_fs 创建一个脚本字符串,其中包含:用户 ID (static.SANDBOX_USER_UID)。组 ID (static.SANDBOX_GROUP_ID)。网络启用标志(根据 options.EnableNetwork)。预加载的代码或配置 (preload)。
	// 3.加密与编码:生成一个 512 位的随机密钥。使用此密钥对输入的 Python 代码 code 进行异或加密。将加密后的代码和密钥分别使用 Base64 编码。
	// 4.文件写入:将编码后的加密代码嵌入到脚本字符串中。在指定路径下创建一个临时目录,并将最终的脚本字符串写入到一个Python文件中。
	// 5.返回结果:返回临时Python脚本的路径、Base64编码后的密钥以及可能发生的错误。
	untrusted_code_path, key, err := p.InitializeEnvironment(code, preload, options)
	if err != nil {
		return nil, nil, nil, err
	}

	// capture the output
	output_handler := runner.NewOutputCaptureRunner()
	output_handler.SetTimeout(timeout)

	err = p.WithTempDir(LIB_PATH, REQUIRED_FS, func(root_path string) error {
		// 确保执行完python脚本后清除临时目录和文件
		output_handler.SetAfterExitHook(func() {
			os.RemoveAll(root_path)
			os.Remove(root_path)
		})

		// 创建进程执行编码后的python代码
		cmd := exec.Command(
			configuration.PythonPath,
			untrusted_code_path,
			LIB_PATH,
			key,
		)
		// 清空了继承的环境变量
		cmd.Env = []string{}

		if configuration.Proxy.Socks5 != "" {
			cmd.Env = append(cmd.Env, fmt.Sprintf("HTTPS_PROXY=%s", configuration.Proxy.Socks5))
			cmd.Env = append(cmd.Env, fmt.Sprintf("HTTP_PROXY=%s", configuration.Proxy.Socks5))
		} else if configuration.Proxy.Https != "" || configuration.Proxy.Http != "" {
			if configuration.Proxy.Https != "" {
				cmd.Env = append(cmd.Env, fmt.Sprintf("HTTPS_PROXY=%s", configuration.Proxy.Https))
			}
			if configuration.Proxy.Http != "" {
				cmd.Env = append(cmd.Env, fmt.Sprintf("HTTP_PROXY=%s", configuration.Proxy.Http))
			}
		}
		// CaptureOutput函数实现了对外部命令执行的监控,包括超时处理、输出捕获、错误处理等功能,确保了命令执行的安全性和可控性
		err = output_handler.CaptureOutput(cmd)
		if err != nil {
			return err
		}

		return nil
	})

	if err != nil {
		return nil, nil, nil, err
	}

	return output_handler.GetStdout(), output_handler.GetStderr(), output_handler.GetDone(), nil
}

2.4.2 python代码模板文件

在sandbox运行python代码,利用了这个python脚本模板prescript.py,把真正待执行的代码包含在其中。这个python脚本模板在开头部分加入了钩子处理、Seccomp校验系统调用权限。

import ctypes
import os
import sys
import traceback
# setup sys.excepthook
# 设置了一个自定义的异常处理钩子(excepthook),当Python程序中未被捕获的异常发生时,这个钩子会被调用。
def excepthook(type, value, tb):
    sys.stderr.write("".join(traceback.format_exception(type, value, tb)))
    sys.stderr.flush()
    sys.exit(-1)

sys.excepthook = excepthook

lib = ctypes.CDLL("./var/sandbox/sandbox-python/python.so")
lib.DifySeccomp.argtypes = [ctypes.c_uint32, ctypes.c_uint32, ctypes.c_bool]
lib.DifySeccomp.restype = None

# get running path
# 得到的是LIB_PATH=/var/sandbox/sandbox-python
running_path = sys.argv[1]
if not running_path:
    exit(-1)

# get decrypt key
# 获取用于解密的key
key = sys.argv[2]
if not key:
    exit(-1)

from base64 import b64decode
# key先进行base64解码
key = b64decode(key)

# 工作目录切换到了/var/sandbox/sandbox-python
os.chdir(running_path)

{{preload}}

# 触发python类语言的Seccomp实现,用于限制系统调用(nodejs语言也是类似的逻辑)
# uid=65537,sandbox用户
# gid=1000,sandbox用户组
# enable_network=true,允许网络访问
lib.DifySeccomp({{uid}}, {{gid}}, {{enable_network}})

# 待执行的代码base64解码
code = b64decode("{{code}}")

# 解密的逻辑:对一个数异或2次得到原数
def decrypt(code, key):
    key_len = len(key)
    code_len = len(code)
    code = bytearray(code)
    for i in range(code_len):
        code[i] = code[i] ^ key[i % key_len]
    return bytes(code)

code = decrypt(code, key)
exec(code)

2.4.3 python语言DifySeccomp

限制python语言的系统调用,核心逻辑实现:

func InitSeccomp(uid int, gid int, enable_network bool) error {
	err := syscall.Chroot(".")
	if err != nil {
		return err
	}
	err = syscall.Chdir("/")
	if err != nil {
		return err
	}
	// 调用系统调用SYS_PRCTL,执行PR_SET_NO_NEW_PRIVS操作,参数为(0x26, 1, 0, 0, 0, 0)。此操作用于限制进程获取新的特权。如果调用失败,函数返回错误码e,否则返回nil。
	lib.SetNoNewPrivs()

	allowed_syscalls := []int{}
	allowed_not_kill_syscalls := []int{}

	allowed_syscall := os.Getenv("ALLOWED_SYSCALLS")
	if allowed_syscall != "" {
		nums := strings.Split(allowed_syscall, ",")
		for num := range nums {
			syscall, err := strconv.Atoi(nums[num])
			if err != nil {
				continue
			}
			allowed_syscalls = append(allowed_syscalls, syscall)
		}
	} else {
		allowed_syscalls = append(allowed_syscalls, python_syscall.ALLOW_SYSCALLS...)

		if enable_network {
			allowed_syscalls = append(allowed_syscalls, python_syscall.ALLOW_NETWORK_SYSCALLS...)
		}
	}

	err = lib.Seccomp(allowed_syscalls, allowed_not_kill_syscalls)
	if err != nil {
		return err
	}

	// setuid
	err = syscall.Setuid(uid)
	if err != nil {
		return err
	}

	// setgid
	err = syscall.Setgid(gid)
	if err != nil {
		return err
	}

	return nil
}

初始化Seccomp,设置特定的系统调用白名单并更改进程的用户和组ID。功能点包括:

  1. 使用Chroot和Chdir系统调用更改根目录和当前工作目录。
  2. 设置禁止提升权限。
  3. 从环境变量或预定义列表中获取允许的系统调用,并存储到allowed_syscalls列表中。
  4. 如果启用了网络,则添加额外的允许网络系统调用。
  5. 使用allowed_syscalls和空的allowed_not_kill_syscalls调用lib的Seccomp函数。
  6. 设置进程的用户ID(setuid)和组ID(setgid)为传入的uid和gid。

2.4.4 nodejs语言DifySeccomp

限制nodejs语言的系统调用,跟上述限制python语言的系统调用处理逻辑一致

func InitSeccomp(uid int, gid int, enable_network bool) error {
	err := syscall.Chroot(".")
	if err != nil {
		return err
	}
	err = syscall.Chdir("/")
	if err != nil {
		return err
	}

	lib.SetNoNewPrivs()

	allowed_syscalls := []int{}
	allowed_not_kill_syscalls := []int{}

	allowed_syscall := os.Getenv("ALLOWED_SYSCALLS")
	if allowed_syscall != "" {
		nums := strings.Split(allowed_syscall, ",")
		for num := range nums {
			syscall, err := strconv.Atoi(nums[num])
			if err != nil {
				continue
			}
			allowed_syscalls = append(allowed_syscalls, syscall)
		}
	} else {
		allowed_syscalls = append(allowed_syscalls, nodejs_syscall.ALLOW_SYSCALLS...)
		allowed_not_kill_syscalls = append(allowed_not_kill_syscalls, nodejs_syscall.ALLOW_ERROR_SYSCALLS...)

		if enable_network {
			allowed_syscalls = append(allowed_syscalls, nodejs_syscall.ALLOW_NETWORK_SYSCALLS...)
		}
	}

	err = lib.Seccomp(allowed_syscalls, allowed_not_kill_syscalls)
	if err != nil {
		return err
	}

	// setuid
	err = syscall.Setuid(uid)
	if err != nil {
		return err
	}

	// setgid
	err = syscall.Setgid(gid)
	if err != nil {
		return err
	}

	return nil
}

自定义系统调用

最新版的dify-sandbox允许传入ALLOWED_SYSCALLS的变量,因为执行sandbox中的脚本并没有继承父进程的环境变量,代码中使用了cmd.Env = []string{},那个patch的逻辑是从环境变量或配置文件中获取,赋予给cmd.Env

if len(configuration.AllowedSyscalls) > 0 {
	cmd.Env = append(cmd.Env, fmt.Sprintf("ALLOWED_SYSCALLS=%s", strings.Trim(strings.Join(strings.Fields(fmt.Sprint(configuration.AllowedSyscalls)), ","), "[]")))
}

dify-sandbox支持自定义系统调用的配置文件如下:

# vim conf/config.yaml 
app:
  port: 8194
  debug: True
  key: dify-sandbox
max_workers: 4
max_requests: 50
worker_timeout: 5
python_path: /usr/local/bin/python3
enable_network: True # please make sure there is no network risk in your environment
allowed_syscalls:
  - 0
  - 1
  - 2
  - 3
  - 4
  - 5
  - 6
  - 7
  - 8
  - 9
  - 10
  - 11
  - 12
  - 13
  - 14
  - 15
  - 16
  - 17
  - 18
  - 19
  - 20
  - 21
  - 22
  - 23
  - 24
  - 25
  - 26
  - 27
  - 28
  - 29
  - 30
  - 31
  - 32
  - 33
  - 34
  - 35
  - 36
  - 37
  - 38
  - 39
proxy:
  socks5: ''
  http: ''
  https: ''

「真诚赞赏,手留余香」

爱折腾的工程师

真诚赞赏,手留余香

使用微信扫描二维码完成支付