trusted-sandbox 0.0.10.pre → 0.0.11.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/Gemfile.lock +17 -1
- data/README.md +63 -19
- data/lib/trusted_sandbox/cli.rb +17 -6
- data/lib/trusted_sandbox/config/trusted_sandbox.yml +5 -1
- data/lib/trusted_sandbox/config.rb +47 -13
- data/lib/trusted_sandbox/defaults.rb +4 -1
- data/lib/trusted_sandbox/general_purpose.rb +26 -0
- data/lib/trusted_sandbox/response.rb +30 -11
- data/lib/trusted_sandbox/runner.rb +22 -5
- data/lib/trusted_sandbox/server_images/{2.1.2 → ruby-2.1.2}/Dockerfile +0 -0
- data/lib/trusted_sandbox/server_images/{2.1.2 → ruby-2.1.2}/Gemfile +0 -0
- data/lib/trusted_sandbox/server_images/{2.1.2 → ruby-2.1.2}/bundle_config +0 -0
- data/lib/trusted_sandbox/server_images/{2.1.2 → ruby-2.1.2}/entrypoint.sh +0 -0
- data/lib/trusted_sandbox/server_images/{2.1.2 → ruby-2.1.2}/run.rb +0 -0
- data/lib/trusted_sandbox/uid_pool.rb +4 -0
- data/lib/trusted_sandbox/version.rb +1 -1
- data/lib/trusted_sandbox.rb +9 -0
- data/spec/integration/integration_spec.rb +85 -0
- data/spec/integration/quota_spec.rb +25 -0
- data/spec/lib/trusted_sandbox/config_spec.rb +91 -0
- data/spec/lib/trusted_sandbox/request_serializer_spec.rb +47 -0
- data/spec/lib/trusted_sandbox/response_spec.rb +90 -0
- data/spec/lib/trusted_sandbox/runner_spec.rb +171 -0
- data/spec/lib/trusted_sandbox/uid_pool_spec.rb +110 -0
- data/spec/lib/trusted_sandbox_spec.rb +15 -0
- data/spec/spec_helper.rb +95 -0
- data/trusted-sandbox.gemspec +4 -2
- metadata +58 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cc841b00560c9a8bd0b26bcc9ce940b095fed242
|
4
|
+
data.tar.gz: 7bd8e5c072575fea41bda718a32a99a6015d4d46
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2b63ac24b3daa49808d3ede5cef2fe21fbd7f0adf109412d06c91d5c77b78c2260d897af2ad292ccebccaea02937904e6209ff5bedbadef8b6e546512046617
|
7
|
+
data.tar.gz: 3acab287609c12d11a6b7e1c4635b41ea5afb94a1c2f4babb5cc43b6edbd9c2f38004d78bc4018aa68976d10c7f47f7080a1a9603bbbca9f85c9001c7431a313
|
data/.rspec
ADDED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
trusted-sandbox (0.0.
|
4
|
+
trusted-sandbox (0.0.11.pre)
|
5
5
|
docker-api (~> 1.13)
|
6
6
|
thor (~> 0.19)
|
7
7
|
|
@@ -9,6 +9,7 @@ GEM
|
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
11
|
archive-tar-minitar (0.5.2)
|
12
|
+
diff-lcs (1.2.5)
|
12
13
|
docker-api (1.13.6)
|
13
14
|
archive-tar-minitar
|
14
15
|
excon (>= 0.38.0)
|
@@ -16,6 +17,19 @@ GEM
|
|
16
17
|
excon (0.40.0)
|
17
18
|
json (1.8.1)
|
18
19
|
rake (10.1.0)
|
20
|
+
rr (1.1.2)
|
21
|
+
rspec (3.1.0)
|
22
|
+
rspec-core (~> 3.1.0)
|
23
|
+
rspec-expectations (~> 3.1.0)
|
24
|
+
rspec-mocks (~> 3.1.0)
|
25
|
+
rspec-core (3.1.7)
|
26
|
+
rspec-support (~> 3.1.0)
|
27
|
+
rspec-expectations (3.1.2)
|
28
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
29
|
+
rspec-support (~> 3.1.0)
|
30
|
+
rspec-mocks (3.1.3)
|
31
|
+
rspec-support (~> 3.1.0)
|
32
|
+
rspec-support (3.1.2)
|
19
33
|
thor (0.19.1)
|
20
34
|
|
21
35
|
PLATFORMS
|
@@ -24,4 +38,6 @@ PLATFORMS
|
|
24
38
|
DEPENDENCIES
|
25
39
|
bundler (~> 1.3)
|
26
40
|
rake
|
41
|
+
rr
|
42
|
+
rspec
|
27
43
|
trusted-sandbox!
|
data/README.md
CHANGED
@@ -1,10 +1,31 @@
|
|
1
1
|
# Trusted Sandbox
|
2
2
|
|
3
|
-
Run untrusted
|
3
|
+
Run untrusted code in a contained sandbox, using Docker. This gem was inspired by [Harry Marr's work][1].
|
4
4
|
|
5
5
|
## Instant gratification
|
6
6
|
|
7
|
-
Trusted Sandbox makes it simple to execute
|
7
|
+
Trusted Sandbox makes it simple to execute classes that `eval` untrusted code in a resource-controlled docker
|
8
|
+
container.
|
9
|
+
|
10
|
+
The simplest way to get started is run "inline" code within a container:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
require 'trusted_sandbox'
|
14
|
+
|
15
|
+
untrusted_code = "input[:number] ** 2"
|
16
|
+
|
17
|
+
# The following will run inside a Docker container
|
18
|
+
output = TrustedSandbox.run_code! untrusted_code, input: {number: 10}
|
19
|
+
# => 100
|
20
|
+
```
|
21
|
+
|
22
|
+
`run_code!` receives user code and an arguments hash. Any key in the arguments hash is available when the user code
|
23
|
+
executes.
|
24
|
+
|
25
|
+
In addition, you can send any class to execute within a Docker container. All you need is to have the class respond to
|
26
|
+
`initialize` and `run`. Trusted Sandbox loads the container, copies the class file to the container, serializes the
|
27
|
+
arguments sent to `initialize`, instantiates an object, calls `run`, and serializes its return value back to the host.
|
28
|
+
|
8
29
|
```ruby
|
9
30
|
# lib/my_function.rb
|
10
31
|
|
@@ -33,9 +54,6 @@ output = TrustedSandbox.run! MyFunction, untrusted_code, {number: 10}
|
|
33
54
|
# => 100
|
34
55
|
```
|
35
56
|
|
36
|
-
Classes you want to run in a container need to respond to #initialize and #run. Trusted Sandbox serializes the
|
37
|
-
arguments sent to #initialize, loads the container, instantiates an object, and calls #run.
|
38
|
-
|
39
57
|
## Installing
|
40
58
|
|
41
59
|
### Step 1
|
@@ -89,7 +107,7 @@ $ trusted_sandbox test
|
|
89
107
|
Install the image. This step is optional, as Docker automatically installs images when you first run them. However,
|
90
108
|
since it takes a few minutes we suggest you do this in advance.
|
91
109
|
```
|
92
|
-
$ docker run --rm vaharoni/trusted_sandbox:2.1.2.v1
|
110
|
+
$ docker run --rm vaharoni/trusted_sandbox:ruby-2.1.2.v1
|
93
111
|
```
|
94
112
|
If you see the message "you must provide a uid", then you are set.
|
95
113
|
|
@@ -115,7 +133,7 @@ environment variables.
|
|
115
133
|
|
116
134
|
### Docker connection
|
117
135
|
|
118
|
-
Trusted Sandbox uses the `docker-api` gem to communicate with docker. `docker-api`'s
|
136
|
+
Trusted Sandbox uses the `docker-api` gem to communicate with docker. `docker-api`'s defaults work quite well for a
|
119
137
|
Linux host, and you should be good by omitting `docker_url` and `docker_cert_path` all together.
|
120
138
|
|
121
139
|
```ruby
|
@@ -130,7 +148,7 @@ YAML file which will override any configuration and passed through to `Docker.op
|
|
130
148
|
|
131
149
|
In addition, these docker-related configuration parameters can be used:
|
132
150
|
```ruby
|
133
|
-
docker_image_name: vaharoni/trusted_sandbox:2.1.2.v1
|
151
|
+
docker_image_name: vaharoni/trusted_sandbox:ruby-2.1.2.v1
|
134
152
|
|
135
153
|
# Optional authentication
|
136
154
|
docker_login:
|
@@ -182,6 +200,11 @@ host_code_root_path: tmp/code_dirs
|
|
182
200
|
# to the container to troubleshoot issues as explained in the "Troubleshooting" section.
|
183
201
|
keep_code_folders: false
|
184
202
|
|
203
|
+
# When set to true, containers will not be erased after they finish running. This allows you
|
204
|
+
# to troubleshoot issues by viewing container parameters and logs as explained in the
|
205
|
+
# "Troubleshooting" section.
|
206
|
+
keep_containers: false
|
207
|
+
|
185
208
|
# A folder used by the UID-pool to handle locks.
|
186
209
|
host_uid_pool_lock_path: tmp/uid_pool_lock
|
187
210
|
```
|
@@ -218,7 +241,9 @@ In order to enable quotas do the following on the server:
|
|
218
241
|
```
|
219
242
|
$ sudo apt-get install quota
|
220
243
|
```
|
221
|
-
And follow [these instructions][4], which
|
244
|
+
And follow [these instructions][4] as well as [this resource][6], which we bring here for completeness. Note that these
|
245
|
+
may vary for your distro.
|
246
|
+
|
222
247
|
```
|
223
248
|
$ sudo vim /etc/fstab
|
224
249
|
```
|
@@ -228,9 +253,21 @@ LABEL=cloudimg-rootfs / ext4 defaults,discard,usrquota 0 0
|
|
228
253
|
```
|
229
254
|
Then do:
|
230
255
|
```
|
231
|
-
$
|
256
|
+
$ sudo touch /aquota.user
|
257
|
+
$ sudo chmod 600 /aquota.*
|
258
|
+
$ sudo mount -o remount /
|
259
|
+
```
|
260
|
+
and **reboot the server**. Then do:
|
261
|
+
```
|
262
|
+
$ sudo quotacheck -avum
|
263
|
+
$ sudo quotaon -avu
|
232
264
|
```
|
233
|
-
|
265
|
+
You should see something like this:
|
266
|
+
```
|
267
|
+
/dev/disk/by-uuid/d36a9e2f-dae9-477f-8aea-29f1bdd1c04e [/]: user quotas turned on
|
268
|
+
```
|
269
|
+
|
270
|
+
To actually set the quotas, run the following (quota is in KB):
|
234
271
|
```
|
235
272
|
$ sudo trusted_sandbox set_quotas 10000
|
236
273
|
```
|
@@ -239,7 +276,10 @@ change these configuration parameters you must rerun the `set_quotas` command.
|
|
239
276
|
|
240
277
|
Remember to set `enable_quotas: true` in the YAML file.
|
241
278
|
|
242
|
-
|
279
|
+
To get a quota report, do:
|
280
|
+
```
|
281
|
+
$ sudo repquota -a
|
282
|
+
```
|
243
283
|
|
244
284
|
### Limiting network
|
245
285
|
|
@@ -380,20 +420,23 @@ You should not override user quota related parameters, as they must be prepared
|
|
380
420
|
## Using custom docker images
|
381
421
|
|
382
422
|
Trusted Sandbox comes with one ready-to-use image that includes Ruby 2.1.2. It is hosted on Docker Hub under
|
383
|
-
`vaharoni/trusted_sandbox:2.1.2.v1`.
|
423
|
+
`vaharoni/trusted_sandbox:ruby-2.1.2.v1`.
|
424
|
+
|
425
|
+
We are actively looking for contributors who are willing to help expand the library of Docker images to support other
|
426
|
+
languages and environments.
|
384
427
|
|
385
428
|
To use a different image from your Docker Hub account simply change the configuration parameters in the YAML file.
|
386
429
|
|
387
430
|
To customize the provided images, run the following. It will copy the image definition to your current directory under
|
388
|
-
`trusted_sandbox_images/2.1.2`.
|
431
|
+
`trusted_sandbox_images/ruby-2.1.2`.
|
389
432
|
```
|
390
433
|
$ trusted_sandbox generate_image
|
391
434
|
```
|
392
435
|
|
393
436
|
After modifying the files to your satisfaction, you can either push it to your Docker Hub account, or build directly
|
394
|
-
on the server. Assuming you kept the image under trusted_sandbox_images/2.1.2:
|
437
|
+
on the server. Assuming you kept the image under trusted_sandbox_images/ruby-2.1.2:
|
395
438
|
```
|
396
|
-
$ docker build -t "your_user/your_image_name:your_image_version" trusted_sandbox_images/2.1.2
|
439
|
+
$ docker build -t "your_user/your_image_name:your_image_version" trusted_sandbox_images/ruby-2.1.2
|
397
440
|
```
|
398
441
|
|
399
442
|
## Troubleshooting
|
@@ -421,7 +464,7 @@ $ trusted_sandbox reset_uid_pool
|
|
421
464
|
|
422
465
|
To avoid containers from being deleted after they finish running, set:
|
423
466
|
```ruby
|
424
|
-
|
467
|
+
keep_containers: true
|
425
468
|
```
|
426
469
|
This will allow you to view containers by running `docker ps -a` and then check out container logs
|
427
470
|
`docker logs CONTAINER_ID` or container parameters `docker inspect CONTAINER_ID`.
|
@@ -443,7 +486,8 @@ $ docker ps -aq | xargs docker rm
|
|
443
486
|
Licensed under the [MIT license](http://opensource.org/licenses/MIT).
|
444
487
|
|
445
488
|
[1]: http://hmarr.com/2013/oct/16/codecube-runnable-gists/
|
446
|
-
[2]: https://www.digitalocean.com/community/
|
489
|
+
[2]: https://www.digitalocean.com/community/tutorials/how-to-enable-user-and-group-quotas
|
447
490
|
[3]: http://hmarr.com/2013/oct/16/codecube-runnable-gists/
|
448
491
|
[4]: https://www.digitalocean.com/community/tutorials/how-to-enable-user-quotas
|
449
|
-
[5]: http://askubuntu.com/questions/477551/how-can-i-use-docker-without-sudo
|
492
|
+
[5]: http://askubuntu.com/questions/477551/how-can-i-use-docker-without-sudo
|
493
|
+
[6]: http://www.howtoforge.com/how-to-set-up-journaled-quota-on-debian-lenny
|
data/lib/trusted_sandbox/cli.rb
CHANGED
@@ -30,20 +30,31 @@ module TrustedSandbox
|
|
30
30
|
`docker run -it -v #{local_code_dir}:/home/sandbox/src --entrypoint="/bin/bash" #{TrustedSandbox.config.docker_image_name} -s`
|
31
31
|
end
|
32
32
|
|
33
|
-
desc 'generate_image
|
34
|
-
def generate_image(
|
33
|
+
desc 'generate_image IMAGE_NAME', 'Creates the Docker image files and places them into the `trusted_sandbox_images` directory. Default name is ruby-2.1.2'
|
34
|
+
def generate_image(image_name = 'ruby-2.1.2')
|
35
35
|
target_dir = 'trusted_sandbox_images'
|
36
|
-
target_image_path = "#{target_dir}/#{
|
37
|
-
gem_image_path = File.expand_path("../server_images/#{
|
36
|
+
target_image_path = "#{target_dir}/#{image_name}"
|
37
|
+
gem_image_path = File.expand_path("../server_images/#{image_name}", __FILE__)
|
38
38
|
|
39
|
-
puts "Image #{
|
39
|
+
puts "Image #{image_name} does not exist" or return unless Dir.exist?(gem_image_path)
|
40
40
|
puts "Directory #{target_image_path} already exists" or return if Dir.exist?(target_image_path)
|
41
41
|
|
42
|
-
puts "Copying #{
|
42
|
+
puts "Copying #{image_name} into #{target_image_path}"
|
43
43
|
FileUtils.mkdir_p target_dir
|
44
44
|
FileUtils.cp_r gem_image_path, target_image_path
|
45
45
|
end
|
46
46
|
|
47
|
+
desc 'generate_images', 'Copies all Docker images files into `trusted_sandbox_images` directory'
|
48
|
+
def generate_images
|
49
|
+
target_dir = 'trusted_sandbox_images'
|
50
|
+
source_dir = File.expand_path("../server_images", __FILE__)
|
51
|
+
|
52
|
+
puts "Directory #{target_dir} already exists" or return if Dir.exist?(target_dir)
|
53
|
+
puts "Copying images into #{target_dir}"
|
54
|
+
|
55
|
+
FileUtils.cp_r source_dir, target_dir
|
56
|
+
end
|
57
|
+
|
47
58
|
desc 'set_quotas QUOTA_KB', 'Sets the quota for all the UIDs in the pool. This requires additional installation. Refer to the README file.'
|
48
59
|
def set_quotas(quota_kb)
|
49
60
|
from = TrustedSandbox.config.pool_min_uid
|
@@ -9,7 +9,7 @@ development:
|
|
9
9
|
# docker_url: https://192.168.59.103:2376
|
10
10
|
# docker_cert_path: ~/.boot2docker/certs/boot2docker-vm
|
11
11
|
|
12
|
-
docker_image_name: vaharoni/trusted_sandbox:2.1.2.v1
|
12
|
+
docker_image_name: vaharoni/trusted_sandbox:ruby-2.1.2.v1
|
13
13
|
|
14
14
|
cpu_shares: 1
|
15
15
|
|
@@ -28,6 +28,10 @@ development:
|
|
28
28
|
keep_code_folders: false
|
29
29
|
keep_containers: false
|
30
30
|
|
31
|
+
# When this is set to false and keep_code_folders is true, you'll
|
32
|
+
# receive helpful messages about how to connect to your containers
|
33
|
+
quiet_mode: false
|
34
|
+
|
31
35
|
# # It's very unlikely you'll need to change these
|
32
36
|
# pool_size: 5000
|
33
37
|
# pool_min_uid: 20000
|
@@ -6,19 +6,38 @@ module TrustedSandbox
|
|
6
6
|
# specific_invocation = general_config.override(memory_limit: 200)
|
7
7
|
#
|
8
8
|
class Config
|
9
|
-
attr_reader :
|
10
|
-
|
9
|
+
attr_reader :fallback_config
|
10
|
+
|
11
|
+
# = Class macros
|
12
|
+
|
13
|
+
# Usage:
|
14
|
+
# attr_reader_with_fallback :my_attribute
|
15
|
+
#
|
16
|
+
# Equivalent to:
|
17
|
+
# def my_attribute
|
18
|
+
# return @my_attribute if @my_attribute
|
19
|
+
# return fallback_config.my_attribute if @my_attribute.nil? and fallback_config.respond_to?(:my_attribute)
|
20
|
+
# nil
|
21
|
+
# end
|
22
|
+
#
|
11
23
|
def self.attr_reader_with_fallback(*names)
|
12
24
|
names.each do |name|
|
13
25
|
define_method name do
|
14
26
|
value = instance_variable_get("@#{name}")
|
15
27
|
return value unless value.nil?
|
16
|
-
return
|
28
|
+
return fallback_config.send(name) if fallback_config.respond_to?(name)
|
17
29
|
nil
|
18
30
|
end
|
19
31
|
end
|
20
32
|
end
|
21
33
|
|
34
|
+
# Usage:
|
35
|
+
# attr_accessor_with_fallback :my_attribute
|
36
|
+
#
|
37
|
+
# Equivalent to:
|
38
|
+
# attr_reader_with_fallback :my_attribute
|
39
|
+
# attr_writer :my_attribute
|
40
|
+
#
|
22
41
|
def self.attr_accessor_with_fallback(*names)
|
23
42
|
names.each do |name|
|
24
43
|
attr_reader_with_fallback(name)
|
@@ -30,28 +49,36 @@ module TrustedSandbox
|
|
30
49
|
:memory_limit, :memory_swap_limit, :cpu_shares, :docker_image_name,
|
31
50
|
:execution_timeout, :network_access, :enable_swap_limit, :enable_quotas,
|
32
51
|
:container_code_path, :container_input_filename, :container_output_filename,
|
33
|
-
:keep_code_folders, :keep_containers
|
52
|
+
:keep_code_folders, :keep_containers, :quiet_mode
|
34
53
|
|
35
54
|
attr_reader_with_fallback :host_code_root_path, :host_uid_pool_lock_path
|
36
55
|
|
37
56
|
attr_reader :docker_url, :docker_cert_path, :docker_auth_email, :docker_auth_user, :docker_auth_password,
|
38
57
|
:docker_auth_needed
|
39
58
|
|
59
|
+
# @param params [Hash] hash of parameters used to override the existing config object's attributes
|
60
|
+
# @return [Config] a new object with the fallback object set to self
|
40
61
|
def override(params={})
|
41
62
|
Config.send :new, self, params
|
42
63
|
end
|
43
64
|
|
65
|
+
# @return [Integer] the upper boundary of the uid pool based on pool_min_uid and pool_size
|
44
66
|
def pool_max_uid
|
45
67
|
pool_min_uid + pool_size - 1
|
46
68
|
end
|
47
69
|
|
48
|
-
|
49
|
-
|
50
|
-
|
70
|
+
# @param url [String] URL for Docker daemon. Will be sent to the Docker class
|
71
|
+
# @return [String] the URL
|
72
|
+
def docker_url=(url)
|
73
|
+
@docker_url = url
|
74
|
+
Docker.url = url
|
51
75
|
end
|
52
76
|
|
53
|
-
|
54
|
-
|
77
|
+
# Prepare to set Docker.options appropriately given a path to the cert directory.
|
78
|
+
# @param path [String] path to the certificate directory
|
79
|
+
# @return [Hash] of docker options that will be set
|
80
|
+
def docker_cert_path=(path)
|
81
|
+
@docker_cert_path = File.expand_path(path)
|
55
82
|
@docker_options_for_cert = {
|
56
83
|
private_key_path: "#{@docker_cert_path}/key.pem",
|
57
84
|
certificate_path: "#{@docker_cert_path}/cert.pem",
|
@@ -59,14 +86,19 @@ module TrustedSandbox
|
|
59
86
|
}
|
60
87
|
end
|
61
88
|
|
89
|
+
# @param path [String] shorthand version of the path. E.g.: '~/tmp'
|
90
|
+
# @return [String] the full path that was set. E.g.: '/home/user/tmp'
|
62
91
|
def host_code_root_path=(path)
|
63
92
|
@host_code_root_path = File.expand_path(path)
|
64
93
|
end
|
65
94
|
|
95
|
+
# @param path [String] shorthand version of the path
|
96
|
+
# @return [String] the full path that was set
|
66
97
|
def host_uid_pool_lock_path=(path)
|
67
98
|
@host_uid_pool_lock_path = File.expand_path(path)
|
68
99
|
end
|
69
100
|
|
101
|
+
# Set hash used to authenticate with Docker
|
70
102
|
# All keys are mandatory
|
71
103
|
# @option :user [String]
|
72
104
|
# @option :password [String]
|
@@ -78,7 +110,9 @@ module TrustedSandbox
|
|
78
110
|
@docker_auth_email = options[:email] || options['email']
|
79
111
|
end
|
80
112
|
|
81
|
-
# Called to do any necessary setup to allow staged configuration
|
113
|
+
# Called to do any necessary setup to allow staged configuration. These involve:
|
114
|
+
# - Setting Docker.options based on the cert path
|
115
|
+
# - Calling Docker.authenticate! with the login parameters, if these were entered
|
82
116
|
# @return [Config] self for chaining
|
83
117
|
def finished_configuring
|
84
118
|
Docker.options = @docker_options_for_cert.merge(docker_options)
|
@@ -91,12 +125,12 @@ module TrustedSandbox
|
|
91
125
|
|
92
126
|
private_class_method :new
|
93
127
|
|
94
|
-
# @params
|
128
|
+
# @params fallback_config [Config] config object that will be deferred to if the current config object does not
|
95
129
|
# contain a value for the requested configuration options
|
96
130
|
# @params params [Hash] hash containing configuration options
|
97
|
-
def initialize(
|
131
|
+
def initialize(fallback_config, params={})
|
98
132
|
@docker_options_for_cert = {}
|
99
|
-
@
|
133
|
+
@fallback_config = fallback_config
|
100
134
|
params.each do |key, value|
|
101
135
|
send "#{key}=", value
|
102
136
|
end
|
@@ -3,7 +3,7 @@ module TrustedSandbox
|
|
3
3
|
|
4
4
|
def initialize
|
5
5
|
self.docker_options = {}
|
6
|
-
self.docker_image_name = 'vaharoni/trusted_sandbox:2.1.2.v1'
|
6
|
+
self.docker_image_name = 'vaharoni/trusted_sandbox:ruby-2.1.2.v1'
|
7
7
|
self.memory_limit = 50 * 1024 * 1024
|
8
8
|
self.memory_swap_limit = 50 * 1024 * 1024
|
9
9
|
self.cpu_shares = 1
|
@@ -27,6 +27,9 @@ module TrustedSandbox
|
|
27
27
|
self.pool_size = 5000
|
28
28
|
|
29
29
|
self.keep_code_folders = false
|
30
|
+
self.keep_containers = false
|
31
|
+
|
32
|
+
self.quiet_mode = false
|
30
33
|
end
|
31
34
|
|
32
35
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module TrustedSandbox
|
2
|
+
|
3
|
+
# This is a general purpose class that can be used to run untrusted code in a container using TrustedSandbox.
|
4
|
+
# Usage:
|
5
|
+
#
|
6
|
+
# TrustedSandbox.run! TrustedSandbox::GeneralPurpose, "1 + 1"
|
7
|
+
# # => 2
|
8
|
+
#
|
9
|
+
# TrustedSandbox.run! TrustedSandbox::GeneralPurpose, "input[:a] + input[:b]", input: {a: 1, b: 2}
|
10
|
+
# # => 3
|
11
|
+
#
|
12
|
+
class GeneralPurpose
|
13
|
+
def initialize(code, args={})
|
14
|
+
@code = code
|
15
|
+
args.each do |name, value|
|
16
|
+
singleton_klass = class << self; self; end
|
17
|
+
singleton_klass.class_eval { attr_reader name }
|
18
|
+
instance_variable_set "@#{name}", value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
eval @code
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -4,32 +4,44 @@ module TrustedSandbox
|
|
4
4
|
attr_reader :host_code_dir_path, :output_file_name, :stdout, :stderr,
|
5
5
|
:raw_response, :status, :error, :error_to_raise, :output
|
6
6
|
|
7
|
+
# @param stdout [String, Array] response of stdout from the container
|
8
|
+
# @param stderr [String, Array] response of stderr from the container
|
7
9
|
# @param host_code_dir_path [String] path to the folder where the argument value needs to be stored
|
8
10
|
# @param output_file_name [String] name of output file inside the host_code_dir_path
|
9
|
-
def initialize(
|
10
|
-
@host_code_dir_path = host_code_dir_path
|
11
|
-
@output_file_name = output_file_name
|
11
|
+
def initialize(stdout = nil, stderr = nil, host_code_dir_path = nil, output_file_name = nil)
|
12
12
|
@stdout = stdout
|
13
13
|
@stderr = stderr
|
14
|
-
|
14
|
+
@host_code_dir_path = host_code_dir_path
|
15
|
+
@output_file_name = output_file_name
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [Response] object initialized with timeout error details
|
19
|
+
def self.timeout_error(err, logs)
|
20
|
+
obj = new(logs)
|
21
|
+
obj.instance_eval do
|
22
|
+
@status = 'error'
|
23
|
+
@error = err
|
24
|
+
@error_to_raise = TrustedSandbox::ExecutionTimeoutError.new(err)
|
25
|
+
end
|
26
|
+
obj
|
15
27
|
end
|
16
28
|
|
29
|
+
# @return [Boolean]
|
17
30
|
def valid?
|
18
31
|
status == 'success'
|
19
32
|
end
|
20
33
|
|
34
|
+
# @return [Object] the output returned by the container. Raises errors if encountered.
|
35
|
+
# @raise [ContainerError, UserCodeError, InternalError] if errors were raised by the container, they are bubbled
|
36
|
+
# as UserCodeError
|
21
37
|
def output!
|
22
38
|
propagate_errors!
|
23
39
|
output
|
24
40
|
end
|
25
41
|
|
26
|
-
|
27
|
-
|
28
|
-
def
|
29
|
-
File.join(host_code_dir_path, output_file_name)
|
30
|
-
end
|
31
|
-
|
32
|
-
def parse_output_file
|
42
|
+
# Parses the output file and stores the values in the appropriate ivars
|
43
|
+
# @return [nil]
|
44
|
+
def parse!
|
33
45
|
begin
|
34
46
|
data = File.binread output_file_path
|
35
47
|
@raw_response = Marshal.load(data)
|
@@ -51,6 +63,13 @@ module TrustedSandbox
|
|
51
63
|
@output = @raw_response[:output]
|
52
64
|
@error = @raw_response[:error]
|
53
65
|
@error_to_raise = UserCodeError.new(@error) if @error
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def output_file_path
|
72
|
+
File.join(host_code_dir_path, output_file_name)
|
54
73
|
end
|
55
74
|
|
56
75
|
def propagate_errors!
|
@@ -27,12 +27,27 @@ module TrustedSandbox
|
|
27
27
|
|
28
28
|
# @param klass [Class] the class object that should be run
|
29
29
|
# @param *args [Array] arguments to send to klass#initialize
|
30
|
-
# @return [
|
30
|
+
# @return [Object] return value from the #eval method
|
31
31
|
# @raise [InternalError, UserCodeError, ContainerError]
|
32
32
|
def run!(klass, *args)
|
33
33
|
run(klass, *args).output!
|
34
34
|
end
|
35
35
|
|
36
|
+
# @param code [String] code to be evaluated
|
37
|
+
# @param args [Hash] hash to send to GeneralPurpose
|
38
|
+
# @return [Response]
|
39
|
+
def run_code(code, args={})
|
40
|
+
run(GeneralPurpose, code, args)
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param code [String] code to be evaluated
|
44
|
+
# @param args [Hash] hash to send to GeneralPurpose
|
45
|
+
# @return [Object] return value from the #eval method
|
46
|
+
# @raise [InternalError, UserCodeError, ContainerError]
|
47
|
+
def run_code!(code, args={})
|
48
|
+
run!(GeneralPurpose, code, args)
|
49
|
+
end
|
50
|
+
|
36
51
|
private
|
37
52
|
|
38
53
|
def obtain_uid
|
@@ -52,8 +67,7 @@ module TrustedSandbox
|
|
52
67
|
end
|
53
68
|
|
54
69
|
def create_code_dir
|
55
|
-
if config.keep_code_folders
|
56
|
-
|
70
|
+
if config.keep_code_folders and !config.quiet_mode
|
57
71
|
puts "Creating #{code_dir_path}"
|
58
72
|
puts nil
|
59
73
|
puts 'To launch and ssh into a new docker container with this directory mounted, run:'
|
@@ -80,9 +94,12 @@ module TrustedSandbox
|
|
80
94
|
Timeout.timeout(config.execution_timeout) do
|
81
95
|
stdout, stderr = @container.attach(stream: true, stdin: nil, stdout: true, stderr: true, logs: true, tty: false)
|
82
96
|
end
|
83
|
-
TrustedSandbox::Response.new code_dir_path, config.container_output_filename
|
97
|
+
response = TrustedSandbox::Response.new stdout, stderr, code_dir_path, config.container_output_filename
|
98
|
+
response.parse!
|
99
|
+
response
|
84
100
|
rescue Timeout::Error => e
|
85
|
-
|
101
|
+
logs = @container.logs(stdout: true, stderr: true)
|
102
|
+
TrustedSandbox::Response.timeout_error(e, logs)
|
86
103
|
end
|
87
104
|
|
88
105
|
def remove_container
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -50,7 +50,9 @@ module TrustedSandbox
|
|
50
50
|
"#<TrustedSandbox::UidPool used: #{used}, available: #{available}, used_uids: #{used_uids}>"
|
51
51
|
end
|
52
52
|
|
53
|
+
# Locks one UID from the pool, in a cross-process atomic manner
|
53
54
|
# @return [Integer]
|
55
|
+
# @raise [PoolTimeoutError] if no ID is available after retries
|
54
56
|
def lock
|
55
57
|
retries.times do
|
56
58
|
atomically(timeout) do
|
@@ -74,7 +76,9 @@ module TrustedSandbox
|
|
74
76
|
self
|
75
77
|
end
|
76
78
|
|
79
|
+
# Releases one UID
|
77
80
|
# @param uid [Integer]
|
81
|
+
# @return [Integer] UID removed
|
78
82
|
def release(uid)
|
79
83
|
atomically(timeout) do
|
80
84
|
release_uid uid
|
data/lib/trusted_sandbox.rb
CHANGED
@@ -5,6 +5,7 @@ module TrustedSandbox
|
|
5
5
|
require 'trusted_sandbox/config'
|
6
6
|
require 'trusted_sandbox/defaults'
|
7
7
|
require 'trusted_sandbox/errors'
|
8
|
+
require 'trusted_sandbox/general_purpose'
|
8
9
|
require 'trusted_sandbox/request_serializer'
|
9
10
|
require 'trusted_sandbox/response'
|
10
11
|
require 'trusted_sandbox/runner'
|
@@ -55,6 +56,14 @@ module TrustedSandbox
|
|
55
56
|
new_runner.run!(klass, *args)
|
56
57
|
end
|
57
58
|
|
59
|
+
def self.run_code(code, args={})
|
60
|
+
new_runner.run_code(code, args)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.run_code!(code, args={})
|
64
|
+
new_runner.run_code!(code, args)
|
65
|
+
end
|
66
|
+
|
58
67
|
def self.new_runner(config_override = {})
|
59
68
|
Runner.new(config, uid_pool, config_override)
|
60
69
|
end
|