trusted-sandbox 0.0.10.pre → 0.0.11.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
|