python.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. package python
  2. import (
  3. "crypto/rand"
  4. _ "embed"
  5. "encoding/base64"
  6. "fmt"
  7. "os"
  8. "os/exec"
  9. "path"
  10. "strconv"
  11. "strings"
  12. "time"
  13. "github.com/google/uuid"
  14. "github.com/langgenius/dify-sandbox/internal/core/runner"
  15. "github.com/langgenius/dify-sandbox/internal/core/runner/types"
  16. "github.com/langgenius/dify-sandbox/internal/static"
  17. )
  18. type PythonRunner struct {
  19. runner.TempDirRunner
  20. }
  21. //go:embed prescript.py
  22. var sandbox_fs []byte
  23. var (
  24. REQUIRED_FS = []string{
  25. path.Join(LIB_PATH, LIB_NAME),
  26. }
  27. )
  28. func (p *PythonRunner) Run(
  29. code string,
  30. timeout time.Duration,
  31. stdin []byte,
  32. preload string,
  33. options *types.RunnerOptions,
  34. ) (chan []byte, chan []byte, chan bool, error) {
  35. configuration := static.GetDifySandboxGlobalConfigurations()
  36. // initialize the environment
  37. untrusted_code_path, key, err := p.InitializeEnvironment(code, preload, options)
  38. if err != nil {
  39. return nil, nil, nil, err
  40. }
  41. // capture the output
  42. output_handler := runner.NewOutputCaptureRunner()
  43. output_handler.SetTimeout(timeout)
  44. err = p.WithTempDir(LIB_PATH, REQUIRED_FS, func(root_path string) error {
  45. // cleanup
  46. output_handler.SetAfterExitHook(func() {
  47. os.RemoveAll(root_path)
  48. os.Remove(root_path)
  49. })
  50. // create a new process
  51. cmd := exec.Command(
  52. configuration.PythonPath,
  53. untrusted_code_path,
  54. LIB_PATH,
  55. key,
  56. )
  57. cmd.Env = []string{}
  58. if configuration.Proxy.Socks5 != "" {
  59. cmd.Env = append(cmd.Env, fmt.Sprintf("HTTPS_PROXY=%s", configuration.Proxy.Socks5))
  60. cmd.Env = append(cmd.Env, fmt.Sprintf("HTTP_PROXY=%s", configuration.Proxy.Socks5))
  61. } else if configuration.Proxy.Https != "" || configuration.Proxy.Http != "" {
  62. if configuration.Proxy.Https != "" {
  63. cmd.Env = append(cmd.Env, fmt.Sprintf("HTTPS_PROXY=%s", configuration.Proxy.Https))
  64. }
  65. if configuration.Proxy.Http != "" {
  66. cmd.Env = append(cmd.Env, fmt.Sprintf("HTTP_PROXY=%s", configuration.Proxy.Http))
  67. }
  68. }
  69. err = output_handler.CaptureOutput(cmd)
  70. if err != nil {
  71. return err
  72. }
  73. return nil
  74. })
  75. if err != nil {
  76. return nil, nil, nil, err
  77. }
  78. return output_handler.GetStdout(), output_handler.GetStderr(), output_handler.GetDone(), nil
  79. }
  80. func (p *PythonRunner) InitializeEnvironment(code string, preload string, options *types.RunnerOptions) (string, string, error) {
  81. if !checkLibAvaliable() {
  82. // ensure environment is reversed
  83. releaseLibBinary()
  84. }
  85. // create a tmp dir and copy the python script
  86. temp_code_name := strings.ReplaceAll(uuid.New().String(), "-", "_")
  87. temp_code_name = strings.ReplaceAll(temp_code_name, "/", ".")
  88. script := strings.Replace(
  89. string(sandbox_fs),
  90. "{{uid}}", strconv.Itoa(static.SANDBOX_USER_UID), 1,
  91. )
  92. script = strings.Replace(
  93. script,
  94. "{{gid}}", strconv.Itoa(static.SANDBOX_GROUP_ID), 1,
  95. )
  96. if options.EnableNetwork {
  97. script = strings.Replace(
  98. script,
  99. "{{enable_network}}", "1", 1,
  100. )
  101. } else {
  102. script = strings.Replace(
  103. script,
  104. "{{enable_network}}", "0", 1,
  105. )
  106. }
  107. script = strings.Replace(
  108. script,
  109. "{{preload}}",
  110. fmt.Sprintf("%s\n", preload),
  111. 1,
  112. )
  113. // generate a random 512 bit key
  114. key_len := 64
  115. key := make([]byte, key_len)
  116. _, err := rand.Read(key)
  117. if err != nil {
  118. return "", "", err
  119. }
  120. // encrypt the code
  121. encrypted_code := make([]byte, len(code))
  122. for i := 0; i < len(code); i++ {
  123. encrypted_code[i] = code[i] ^ key[i%key_len]
  124. }
  125. // encode code using base64
  126. code = base64.StdEncoding.EncodeToString(encrypted_code)
  127. // encode key using base64
  128. encoded_key := base64.StdEncoding.EncodeToString(key)
  129. code = strings.Replace(
  130. script,
  131. "{{code}}",
  132. code,
  133. 1,
  134. )
  135. untrusted_code_path := fmt.Sprintf("%s/tmp/%s.py", LIB_PATH, temp_code_name)
  136. err = os.MkdirAll(path.Dir(untrusted_code_path), 0755)
  137. if err != nil {
  138. return "", "", err
  139. }
  140. err = os.WriteFile(untrusted_code_path, []byte(code), 0755)
  141. if err != nil {
  142. return "", "", err
  143. }
  144. return untrusted_code_path, encoded_key, nil
  145. }