trusted-sandbox 0.0.2.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 +7 -0
- data/.gitignore +8 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +27 -0
- data/LICENSE +24 -0
- data/README.md +400 -0
- data/Rakefile +1 -0
- data/bin/trusted_sandbox +4 -0
- data/lib/trusted_sandbox/cli.rb +43 -0
- data/lib/trusted_sandbox/config/trusted_sandbox.yml +32 -0
- data/lib/trusted_sandbox/config.rb +103 -0
- data/lib/trusted_sandbox/defaults.rb +33 -0
- data/lib/trusted_sandbox/errors.rb +8 -0
- data/lib/trusted_sandbox/request_serializer.rb +53 -0
- data/lib/trusted_sandbox/response.rb +63 -0
- data/lib/trusted_sandbox/runner.rb +129 -0
- data/lib/trusted_sandbox/server_images/2.1.2/Dockerfile +23 -0
- data/lib/trusted_sandbox/server_images/2.1.2/Gemfile +3 -0
- data/lib/trusted_sandbox/server_images/2.1.2/bundle_config +3 -0
- data/lib/trusted_sandbox/server_images/2.1.2/entrypoint.sh +15 -0
- data/lib/trusted_sandbox/server_images/2.1.2/run.rb +18 -0
- data/lib/trusted_sandbox/uid_pool.rb +153 -0
- data/lib/trusted_sandbox/version.rb +3 -0
- data/lib/trusted_sandbox.rb +59 -0
- data/trusted-sandbox.gemspec +26 -0
- metadata +128 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f04a7997f512db9c4d422f4b5ca6f13e26b96c0e
|
4
|
+
data.tar.gz: 4e1ff6efa79e0353d880a4ea7bac92f680e464e3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 72fb2983a31e0908a2d085b53c43d4e8452fb3edc3e81ca4d7af264bec9f8583211bc57a28c436dc6f3bade87645a94650e18f924ee27ba2780efd2afd56bd92
|
7
|
+
data.tar.gz: 6305312ad3faafa2295f16c47cbbfb4e8287185f986ac0c9ead45ef4340438e220c6f59f912e30f726c1fc689e1d4f92a459bb0e8c01211494f95fc6b4f00521
|
data/.gitignore
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
trusted-sandbox
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.1.2
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
trusted-sandbox (0.0.2.pre)
|
5
|
+
docker-api (~> 1.13)
|
6
|
+
thor (~> 0.19)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
archive-tar-minitar (0.5.2)
|
12
|
+
docker-api (1.13.6)
|
13
|
+
archive-tar-minitar
|
14
|
+
excon (>= 0.38.0)
|
15
|
+
json
|
16
|
+
excon (0.40.0)
|
17
|
+
json (1.8.1)
|
18
|
+
rake (10.1.0)
|
19
|
+
thor (0.19.1)
|
20
|
+
|
21
|
+
PLATFORMS
|
22
|
+
ruby
|
23
|
+
|
24
|
+
DEPENDENCIES
|
25
|
+
bundler (~> 1.3)
|
26
|
+
rake
|
27
|
+
trusted-sandbox!
|
data/LICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Amit Aharoni
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person
|
6
|
+
obtaining a copy of this software and associated documentation
|
7
|
+
files (the "Software"), to deal in the Software without
|
8
|
+
restriction, including without limitation the rights to use,
|
9
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
copies of the Software, and to permit persons to whom the
|
11
|
+
Software is furnished to do so, subject to the following
|
12
|
+
conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be
|
15
|
+
included in all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
19
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
21
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
22
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
23
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
24
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,400 @@
|
|
1
|
+
# Trusted Sandbox
|
2
|
+
|
3
|
+
Run untrusted ruby code in a contained sandbox, using Docker. This gem was inspired by [Harry Marr's work][1].
|
4
|
+
|
5
|
+
## Instant gratification
|
6
|
+
|
7
|
+
Trusted Sandbox makes it simple to execute Ruby classes that `eval` untrusted code in a resource-controlled docker container.
|
8
|
+
```ruby
|
9
|
+
# lib/my_function.rb
|
10
|
+
|
11
|
+
class MyFunction
|
12
|
+
attr_reader :input
|
13
|
+
|
14
|
+
def initialize(user_code, input)
|
15
|
+
@user_code = user_code
|
16
|
+
@input = input
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
eval @user_code
|
21
|
+
end
|
22
|
+
end
|
23
|
+
```
|
24
|
+
```ruby
|
25
|
+
# somewhere_else.rb
|
26
|
+
require 'trusted_sandbox'
|
27
|
+
require 'lib/my_function'
|
28
|
+
|
29
|
+
untrusted_code = "input[:number] ** 2"
|
30
|
+
|
31
|
+
# The following will run inside a Docker container
|
32
|
+
output = TrustedSandbox.run! MyFunction, untrusted_code, {number: 10}
|
33
|
+
# => 100
|
34
|
+
```
|
35
|
+
|
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
|
+
## Installing
|
40
|
+
|
41
|
+
### Step 1
|
42
|
+
Add this line to your application's Gemfile:
|
43
|
+
```
|
44
|
+
gem 'trusted-sandbox'
|
45
|
+
```
|
46
|
+
|
47
|
+
And then execute:
|
48
|
+
```
|
49
|
+
$ bundle
|
50
|
+
```
|
51
|
+
Or install it yourself as:
|
52
|
+
```
|
53
|
+
$ gem install trusted-sandbox
|
54
|
+
```
|
55
|
+
|
56
|
+
Then, run the following command which will copy the `trusted_sandbox.yml` file into your current directory, or
|
57
|
+
`config` directory if it exists:
|
58
|
+
```
|
59
|
+
$ trusted_sandbox install
|
60
|
+
```
|
61
|
+
|
62
|
+
### Step 2
|
63
|
+
Install Docker. Refer to the Docker documentation to see how to install Docker on your environment.
|
64
|
+
|
65
|
+
### Step 3
|
66
|
+
Install the image. This step is optional, as Docker automatically installs images when you first run them. However,
|
67
|
+
since it takes a few minutes we suggest you do this in advance.
|
68
|
+
```
|
69
|
+
$ docker run --rm vaharoni/trusted_sandbox:2.1.2.v1
|
70
|
+
```
|
71
|
+
If you see the message "you must provide a uid", then you are set.
|
72
|
+
|
73
|
+
If you receive an error that looks like this: `Error response from daemon: Cannot start container 9f3bd8d72f0704980cedacc068261c38e280e7314916245550a6d48431ea8f11: fork/exec /var/lib/docker/init/dockerinit-1.0.1: cannot allocate memory`
|
74
|
+
consider restarting docker:
|
75
|
+
```
|
76
|
+
$ sudo service docker.io restart
|
77
|
+
```
|
78
|
+
and then try again.
|
79
|
+
|
80
|
+
### Step 4
|
81
|
+
|
82
|
+
If you'd like to limit swap memory or set user quotas you'll have to install additional programs on your server.
|
83
|
+
Follow the instructions in the relevant sections of the configuration guide.
|
84
|
+
|
85
|
+
## Configuring Trusted Sandbox
|
86
|
+
|
87
|
+
Let's go over the sections of the YAML configuration file you created in step 1 above.
|
88
|
+
|
89
|
+
### Docker access
|
90
|
+
```ruby
|
91
|
+
# ENV['DOCKER_HOST'] is used if omitted
|
92
|
+
docker_url: https://192.168.59.103:2376
|
93
|
+
|
94
|
+
# ENV['DOCKER_CERT_PATH'] is used if omitted
|
95
|
+
docker_cert_path: ~/.boot2docker/certs/boot2docker-vm
|
96
|
+
|
97
|
+
docker_image_name: vaharoni/trusted_sandbox:2.1.2.v1
|
98
|
+
|
99
|
+
# Optional authentication
|
100
|
+
docker_login:
|
101
|
+
user: my_user
|
102
|
+
password: my_password
|
103
|
+
email: email@email.com
|
104
|
+
|
105
|
+
```
|
106
|
+
|
107
|
+
Trusted Sandbox uses the `docker-api` gem to communicate with docker. Some of the parameters above are used to setup
|
108
|
+
the global `Docker` class. For finer control of its configuration, you can add a `docker_options` hash entry to the
|
109
|
+
YAML file which will override any configuration and passed through to `Docker.options`.
|
110
|
+
|
111
|
+
### Limiting resources
|
112
|
+
CPU:
|
113
|
+
```ruby
|
114
|
+
cpu_shares: 1 # In relative units
|
115
|
+
```
|
116
|
+
Memory:
|
117
|
+
```ruby
|
118
|
+
memory_limit: 52_428_800 # In bytes
|
119
|
+
enable_swap_limit: false
|
120
|
+
memory_swap_limit: 52_428_800 # In bytes. Relevant only if enable_swap_limit is true.
|
121
|
+
```
|
122
|
+
Execution
|
123
|
+
```ruby
|
124
|
+
execution_timeout: 15 # In seconds
|
125
|
+
network_access: false
|
126
|
+
```
|
127
|
+
Quotas
|
128
|
+
```ruby
|
129
|
+
enable_quotas: false
|
130
|
+
```
|
131
|
+
Settings for UID-pool used for assigning user quotas. Always used, even if quota functionality is disabled.
|
132
|
+
It's very unlikely you'll need to touch these:
|
133
|
+
```ruby
|
134
|
+
pool_size: 5000
|
135
|
+
pool_min_uid: 20000
|
136
|
+
pool_timeout: 3
|
137
|
+
pool_retries: 5
|
138
|
+
pool_delay: 0.5
|
139
|
+
```
|
140
|
+
Note that controlling memory swap limits and user quotas requires additional steps as outlined below.
|
141
|
+
|
142
|
+
### Execution parameters
|
143
|
+
|
144
|
+
A temporary directory under which sub directories are created and mounted to containers.
|
145
|
+
The code and args exchange between the host and containers is done via these sub directories.
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
host_code_root_path: tmp/code_dirs
|
149
|
+
```
|
150
|
+
|
151
|
+
When set to true, the temporary sub directories will not be erased. This allows you to login to the container to
|
152
|
+
troubleshoot issues as explained in the "Troubleshooting" section.
|
153
|
+
```ruby
|
154
|
+
keep_code_folders: false
|
155
|
+
```
|
156
|
+
|
157
|
+
A directory used by the UID-pool to handle locks.
|
158
|
+
```ruby
|
159
|
+
host_uid_pool_lock_path: tmp/uid_pool_lock
|
160
|
+
```
|
161
|
+
|
162
|
+
### Limiting swap memory
|
163
|
+
|
164
|
+
In order to limit swap memory, you'll need to set up your host server to allow that.
|
165
|
+
The following should work for Debian / Ubuntu.
|
166
|
+
|
167
|
+
First, run:
|
168
|
+
```
|
169
|
+
$ sudoedit /etc/default/grub
|
170
|
+
```
|
171
|
+
and edit the following line:
|
172
|
+
```
|
173
|
+
GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"
|
174
|
+
```
|
175
|
+
Then run:
|
176
|
+
```
|
177
|
+
$ sudo update-grub
|
178
|
+
```
|
179
|
+
Reboot the server, and you should be set. Read more about it [here][2].
|
180
|
+
Remember to set `enable_swap_limit: true` in the YAML file.
|
181
|
+
|
182
|
+
### Limiting user quotas
|
183
|
+
|
184
|
+
Note: due to permission setting scheme, limiting user quota does not work on OS or Windows.
|
185
|
+
|
186
|
+
In order to control quotas we follow the technique suggested by [Harry Marr][3]. It makes use of the fact that
|
187
|
+
UIDs (user IDs) and GIDs (Group IDs) are shared between the host and its containers. When a container starts, we
|
188
|
+
run the untrusted code under an unprivileged user whose UID has a quota enforced by the host.
|
189
|
+
|
190
|
+
In order to enable quotas do the following on the server:
|
191
|
+
```
|
192
|
+
$ sudo apt-get install quota
|
193
|
+
```
|
194
|
+
And follow [these instructions][4], which are also brought here for completeness:
|
195
|
+
```
|
196
|
+
$ sudo vim /etc/fstab
|
197
|
+
```
|
198
|
+
Add `,usrquota` in the end of column no. 4 so it looks something like:
|
199
|
+
```
|
200
|
+
LABEL=cloudimg-rootfs / ext4 defaults,discard,usrquota 0 0
|
201
|
+
```
|
202
|
+
Then do:
|
203
|
+
```
|
204
|
+
$ mount -o remount
|
205
|
+
```
|
206
|
+
and reboot the server. Finally, run the following (quota is in KB):
|
207
|
+
```
|
208
|
+
$ trusted_sandbox set_quotas 10000
|
209
|
+
```
|
210
|
+
This sets ~10MB quota on all UIDs that are in the range defined by `pool_size` and `pool_min_uid` parameters. If you
|
211
|
+
change these configuration parameters you must rerun the `set_quotas` command.
|
212
|
+
|
213
|
+
Remember to set `enable_quotas: true` in the YAML file.
|
214
|
+
|
215
|
+
Note: At this time, there is no way to assign different quotas to different users.
|
216
|
+
|
217
|
+
### Limiting network
|
218
|
+
|
219
|
+
The only option available is to turn on and off network access using `enable_network`. Finer control of network
|
220
|
+
access is currently not supported. If you need this feature please open an issue and share your use case.
|
221
|
+
|
222
|
+
## Using Trusted Sandbox
|
223
|
+
|
224
|
+
### Class and argument serialization
|
225
|
+
|
226
|
+
The class you send to a container can be as elaborate as you want, providing a context of execution for the user code.
|
227
|
+
When you call `run` or `run!` with a class constant, the file where that class is defined is copied to the
|
228
|
+
`/home/sandbox/src` folder inside the container. Any arguments needed to instantiate an object from that class are
|
229
|
+
serialized. When the container starts, it deserializes these arguments, invokes the `new` method with them, and runs
|
230
|
+
`run` on the instantiated object. The output of that method is then serialized back to the host.
|
231
|
+
|
232
|
+
A less trivial example:
|
233
|
+
```ruby
|
234
|
+
# my_function.rb
|
235
|
+
|
236
|
+
# Example for requiring a gem, assuming it is in the Gemfile of both the container and the
|
237
|
+
# host. If you want to access a gem that is only available to the container, put the require
|
238
|
+
# directive inside `initialize` or `run` methods.
|
239
|
+
require 'hashie/mash'
|
240
|
+
|
241
|
+
class MyFunction
|
242
|
+
|
243
|
+
attr_reader :a, :b
|
244
|
+
def initialize(first_user_func, second_user_func, a, b)
|
245
|
+
@first_user_func = first_user_func
|
246
|
+
@second_user_func = second_user_func
|
247
|
+
@a = a
|
248
|
+
@b = b
|
249
|
+
end
|
250
|
+
|
251
|
+
def run
|
252
|
+
# Will have access to #a and #b through attr_reader
|
253
|
+
result1 = eval(@first_user_func)
|
254
|
+
|
255
|
+
result2 = Context.new(result1).run(@second_user_func)
|
256
|
+
[result1, result2]
|
257
|
+
end
|
258
|
+
|
259
|
+
class Context
|
260
|
+
attr_reader :x
|
261
|
+
def initialize(x)
|
262
|
+
@x = x
|
263
|
+
end
|
264
|
+
|
265
|
+
def run(code)
|
266
|
+
eval code
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
```
|
271
|
+
```ruby
|
272
|
+
# Somewhere else
|
273
|
+
require 'trusted_sandbox'
|
274
|
+
require 'my_function'
|
275
|
+
a, b = TrustedSandbox.run! MyFunction, "a + b", "x ** 2", 2, 5
|
276
|
+
# => 49
|
277
|
+
```
|
278
|
+
Because serialization occurs through Marshalling, you should use primitive Ruby classes for your inputs as much as
|
279
|
+
possible. You can prepare a docker image with additional gems and custom Ruby classes, as explained in the
|
280
|
+
"Using custom docker images" section.
|
281
|
+
|
282
|
+
### Running containers
|
283
|
+
|
284
|
+
There are two ways to run a container. Use `run!` to retrieve output from the container. If the user code raised
|
285
|
+
an exception, it will be raised by `run!`.
|
286
|
+
|
287
|
+
```ruby
|
288
|
+
output = TrustedSandbox.run! MyFunction, "input ** 2", 10
|
289
|
+
# => 100
|
290
|
+
```
|
291
|
+
Use `run` to retrieve a response object. The response object provides additional useful information about the
|
292
|
+
container execution.
|
293
|
+
|
294
|
+
Here is an error scenario:
|
295
|
+
```ruby
|
296
|
+
response = TrustedSandbox.run MyFunction, "raise 'error!'", 10
|
297
|
+
|
298
|
+
response.status
|
299
|
+
# => "error"
|
300
|
+
|
301
|
+
response.valid?
|
302
|
+
# => false
|
303
|
+
|
304
|
+
response.output
|
305
|
+
# => nil
|
306
|
+
|
307
|
+
response.output!
|
308
|
+
# => TrustedSandbox::UserCodeError: error!
|
309
|
+
|
310
|
+
response.error
|
311
|
+
# => #<RuntimeError: error!>
|
312
|
+
|
313
|
+
response.error.backtrace
|
314
|
+
# => /home/sandbox/src/my_function.rb:14:in `eval'
|
315
|
+
# => /home/sandbox/src/my_function.rb:14:in `eval'
|
316
|
+
# => /home/sandbox/src/my_function.rb:14:in `run'
|
317
|
+
|
318
|
+
# Can be useful if MyFunction prints to stdout
|
319
|
+
puts response.stdout
|
320
|
+
|
321
|
+
# Can be useful for environment related errors
|
322
|
+
puts response.stderr
|
323
|
+
```
|
324
|
+
Here is a success scenario:
|
325
|
+
```ruby
|
326
|
+
response = TrustedSandbox.run MyFunction, "input ** 2", 10
|
327
|
+
|
328
|
+
response.status
|
329
|
+
# => "success"
|
330
|
+
|
331
|
+
response.valid?
|
332
|
+
# => true
|
333
|
+
|
334
|
+
response.output
|
335
|
+
# => 100
|
336
|
+
|
337
|
+
response.output!
|
338
|
+
# => 100
|
339
|
+
|
340
|
+
response.error
|
341
|
+
# => nil
|
342
|
+
```
|
343
|
+
### Overriding specific invocations
|
344
|
+
|
345
|
+
To override a configuration parameter for a specific invocation, use `with_options`:
|
346
|
+
```ruby
|
347
|
+
TrustedSandbox.with_options(cpu_shares: 2) do |s|
|
348
|
+
s.run! MyFunction, untrusted_code, input
|
349
|
+
end
|
350
|
+
```
|
351
|
+
You should not override user quota related parameters, as they must be prepared on the host in advance of execution.
|
352
|
+
|
353
|
+
## Using custom docker images
|
354
|
+
|
355
|
+
Trusted Sandbox comes with one ready-to-use image that includes Ruby 2.1.2. It is hosted on Docker Hub under
|
356
|
+
`vaharoni/trusted_sandbox:2.1.2.v1`.
|
357
|
+
|
358
|
+
To use a different image from your Docker Hub account simply change the configuration parameters in the YAML file.
|
359
|
+
|
360
|
+
To customize the provided images, run the following. It will copy the image definition to your current directory under
|
361
|
+
`trusted_sandbox_images/2.1.2`.
|
362
|
+
```
|
363
|
+
$ trusted_sandbox generate_image
|
364
|
+
```
|
365
|
+
|
366
|
+
After modifying the files to your satisfaction, you can either push it to your Docker Hub account, or build directly
|
367
|
+
on the server. Assuming you kept the image under trusted_sandbox_images/2.1.2:
|
368
|
+
```
|
369
|
+
$ docker build -t "your_user/your_image_name:your_image_version" trusted_sandbox_images/2.1.2
|
370
|
+
```
|
371
|
+
|
372
|
+
## Troubleshooting
|
373
|
+
|
374
|
+
If you encounter issues, try troubleshooting them by accessing your container's bash. Make the following change in the
|
375
|
+
YAML file:
|
376
|
+
|
377
|
+
```ruby
|
378
|
+
keep_code_folders: true
|
379
|
+
```
|
380
|
+
This will keep your code folders from getting deleted when containers stop running. This allows you to do the
|
381
|
+
following from your command line (adjust to your environment):
|
382
|
+
```
|
383
|
+
$ docker run -it -v /home/MyUser/my_app/tmp/code_dirs/20000:/home/sandbox/src --entrypoint="/bin/bash" my_user/my_image:my_tag
|
384
|
+
```
|
385
|
+
|
386
|
+
## Contributing
|
387
|
+
|
388
|
+
1. Fork it
|
389
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
390
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
391
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
392
|
+
5. Create new Pull Request
|
393
|
+
|
394
|
+
## License
|
395
|
+
Licensed under the [MIT license](http://opensource.org/licenses/MIT).
|
396
|
+
|
397
|
+
[1]: http://hmarr.com/2013/oct/16/codecube-runnable-gists/
|
398
|
+
[2]: https://www.digitalocean.com/community/articles/how-to-enable-user-quotas
|
399
|
+
[3]: http://hmarr.com/2013/oct/16/codecube-runnable-gists/
|
400
|
+
[4]: https://www.digitalocean.com/community/tutorials/how-to-enable-user-quotas
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/bin/trusted_sandbox
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module TrustedSandbox
|
4
|
+
class Cli < Thor
|
5
|
+
desc 'install', 'Creates trusted_sandbox.yml in `config`, if this directory exists, or in the current directory otherwise'
|
6
|
+
def install
|
7
|
+
curr_dir_file = 'trusted_sandbox.yml'
|
8
|
+
config_dir_file = 'config/trusted_sandbox.yml'
|
9
|
+
|
10
|
+
puts "#{curr_dir_file} already exists" or return if File.exist?(curr_dir_file)
|
11
|
+
puts "#{config_dir_file} already exists" or return if File.exist?(config_dir_file)
|
12
|
+
|
13
|
+
target_file = Dir.exist?('config') ? config_dir_file : curr_dir_file
|
14
|
+
|
15
|
+
puts "Creating #{target_file}"
|
16
|
+
FileUtils.cp File.expand_path('../config/trusted_sandbox.yml', __FILE__), target_file
|
17
|
+
end
|
18
|
+
|
19
|
+
desc 'generate_image VERSION', 'Creates the Docker image files and places them into the `trusted_sandbox_images` directory. Default version is 2.1.2'
|
20
|
+
def generate_image(image_version = '2.1.2')
|
21
|
+
target_dir = 'trusted_sandbox_images'
|
22
|
+
target_image_path = "#{target_dir}/#{image_version}"
|
23
|
+
gem_image_path = File.expand_path("../server_images/#{image_version}", __FILE__)
|
24
|
+
|
25
|
+
puts "Image #{image_version} does not exist" unless Dir.exist?(gem_image_path)
|
26
|
+
puts "Directory #{target_image_path} already exists" or return if Dir.exist?(target_image_path)
|
27
|
+
|
28
|
+
puts "Copying #{image_version} into #{target_image_path}"
|
29
|
+
FileUtils.mkdir_p target_dir
|
30
|
+
FileUtils.cp_r gem_image_path, target_image_path
|
31
|
+
end
|
32
|
+
|
33
|
+
desc 'set_quotas QUOTA_KB', 'Sets the quota for all the UIDs in the pool. This requires additional installation. Refer to the README file.'
|
34
|
+
def set_quotas(quota_kb)
|
35
|
+
from = TrustedSandbox.config.pool_min_uid
|
36
|
+
to = TrustedSandbox.config.pool_max_uid
|
37
|
+
puts "Configuring quota for UIDs [#{from}..#{to}]"
|
38
|
+
(from..to).each do |uid|
|
39
|
+
`setquota -u #{uid} 0 #{quota_kb} 0 0 /`
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
development:
|
2
|
+
# Optional login information
|
3
|
+
docker_login:
|
4
|
+
user: my_user
|
5
|
+
password: my_password
|
6
|
+
email: email@email.com
|
7
|
+
|
8
|
+
docker_url: https://192.168.59.103:2376
|
9
|
+
docker_cert_path: ~/.boot2docker/certs/boot2docker-vm
|
10
|
+
|
11
|
+
docker_image_name: vaharoni/trusted_sandbox:2.1.2.v1
|
12
|
+
|
13
|
+
cpu_shares: 1
|
14
|
+
|
15
|
+
memory_limit: 52_428_800 # 50 MB
|
16
|
+
enable_swap_limit: false
|
17
|
+
memory_swap_limit: 52_428_800 # 50 MB
|
18
|
+
|
19
|
+
execution_timeout: 15
|
20
|
+
network_access: false
|
21
|
+
|
22
|
+
enable_quotas: false
|
23
|
+
|
24
|
+
host_code_root_path: tmp/code_dirs
|
25
|
+
host_uid_pool_lock_path: tmp/uid_pool_lock
|
26
|
+
keep_code_folders: false
|
27
|
+
|
28
|
+
pool_size: 5000
|
29
|
+
pool_min_uid: 20000
|
30
|
+
pool_timeout: 3
|
31
|
+
pool_retries: 5
|
32
|
+
pool_delay: 0.5
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module TrustedSandbox
|
2
|
+
|
3
|
+
# Allows chaining so that specific invocations can override configurations.
|
4
|
+
# Usage:
|
5
|
+
# general_config = Defaults.new.override(pool_size: 10, memory_limit: 100)
|
6
|
+
# specific_invocation = general_config.override(memory_limit: 200)
|
7
|
+
#
|
8
|
+
class Config
|
9
|
+
attr_reader :other_config
|
10
|
+
|
11
|
+
def self.attr_reader_with_fallback(*names)
|
12
|
+
names.each do |name|
|
13
|
+
define_method name do
|
14
|
+
value = instance_variable_get("@#{name}")
|
15
|
+
return value unless value.nil?
|
16
|
+
return other_config.send(name) if other_config.respond_to?(name)
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.attr_accessor_with_fallback(*names)
|
23
|
+
names.each do |name|
|
24
|
+
attr_reader_with_fallback(name)
|
25
|
+
attr_writer(name)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_accessor_with_fallback :pool_size, :pool_min_uid, :pool_timeout, :pool_retries, :pool_delay, :docker_options,
|
30
|
+
:memory_limit, :memory_swap_limit, :cpu_shares, :docker_image_name,
|
31
|
+
:execution_timeout, :network_access, :enable_swap_limit, :enable_quotas,
|
32
|
+
:container_code_path, :container_input_filename, :container_output_filename,
|
33
|
+
:keep_code_folders
|
34
|
+
|
35
|
+
attr_reader_with_fallback :host_code_root_path, :host_uid_pool_lock_path
|
36
|
+
|
37
|
+
attr_reader :docker_url, :docker_cert_path, :docker_auth_email, :docker_auth_user, :docker_auth_password,
|
38
|
+
:docker_auth_needed
|
39
|
+
|
40
|
+
def override(params={})
|
41
|
+
Config.send :new, self, params
|
42
|
+
end
|
43
|
+
|
44
|
+
def pool_max_uid
|
45
|
+
pool_min_uid + pool_size - 1
|
46
|
+
end
|
47
|
+
|
48
|
+
def docker_url=(value)
|
49
|
+
@docker_url = value
|
50
|
+
Docker.url = value
|
51
|
+
end
|
52
|
+
|
53
|
+
def docker_cert_path=(value)
|
54
|
+
@docker_cert_path = File.expand_path(value)
|
55
|
+
Docker.options = {
|
56
|
+
private_key_path: "#{@docker_cert_path}/key.pem",
|
57
|
+
certificate_path: "#{@docker_cert_path}/cert.pem",
|
58
|
+
ssl_verify_peer: false
|
59
|
+
}.merge(docker_options)
|
60
|
+
end
|
61
|
+
|
62
|
+
def host_code_root_path=(path)
|
63
|
+
@host_code_root_path = File.expand_path(path)
|
64
|
+
end
|
65
|
+
|
66
|
+
def host_uid_pool_lock_path=(path)
|
67
|
+
@host_uid_pool_lock_path = File.expand_path(path)
|
68
|
+
end
|
69
|
+
|
70
|
+
# All keys are mandatory
|
71
|
+
# @option :user [String]
|
72
|
+
# @option :password [String]
|
73
|
+
# @option :email [String]
|
74
|
+
def docker_login=(options={})
|
75
|
+
@docker_auth_needed = true
|
76
|
+
@docker_auth_user = options[:user] || options['user']
|
77
|
+
@docker_auth_password = options[:password] || options['password']
|
78
|
+
@docker_auth_email = options[:email] || options['email']
|
79
|
+
end
|
80
|
+
|
81
|
+
# Called to do any necessary setup to allow staged configuration
|
82
|
+
# @return [Config] self for chaining
|
83
|
+
def finished_configuring
|
84
|
+
return self unless @docker_auth_needed
|
85
|
+
Docker.authenticate! username: @docker_auth_user, password: @docker_auth_password, email: @docker_auth_email
|
86
|
+
@docker_auth_needed = false
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
private_class_method :new
|
91
|
+
|
92
|
+
# @params other_config [Config] config object that will be deferred to if the current config object does not
|
93
|
+
# contain a value for the requested configuration options
|
94
|
+
# @params params [Hash] hash containing configuration options
|
95
|
+
def initialize(other_config, params={})
|
96
|
+
@other_config = other_config
|
97
|
+
params.each do |key, value|
|
98
|
+
send "#{key}=", value
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|