trusted-sandbox 0.0.2.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,33 @@
|
|
1
|
+
module TrustedSandbox
|
2
|
+
class Defaults < Config
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
self.docker_options = {}
|
6
|
+
self.docker_image_name = 'vaharoni/trusted_sandbox:2.1.2.v1'
|
7
|
+
self.memory_limit = 50 * 1024 * 1024
|
8
|
+
self.memory_swap_limit = 50 * 1024 * 1024
|
9
|
+
self.cpu_shares = 1
|
10
|
+
self.execution_timeout = 15
|
11
|
+
self.network_access = false
|
12
|
+
self.enable_swap_limit = false
|
13
|
+
self.enable_quotas = false
|
14
|
+
self.host_code_root_path = 'tmp/code_dirs'
|
15
|
+
self.host_uid_pool_lock_path = 'tmp/uid_pool_lock'
|
16
|
+
|
17
|
+
self.docker_url = ENV['DOCKER_HOST']
|
18
|
+
self.docker_cert_path = ENV['DOCKER_CERT_PATH']
|
19
|
+
|
20
|
+
# Note, changing these may require changing Dockerfile and run.rb and rebuilding the docker image
|
21
|
+
self.container_code_path = '/home/sandbox/src'
|
22
|
+
self.container_input_filename = 'input'
|
23
|
+
self.container_output_filename = 'output'
|
24
|
+
|
25
|
+
# Note, changing these requires running `rake trusted_sandbox:set_quotas`
|
26
|
+
self.pool_min_uid = 20000
|
27
|
+
self.pool_size = 5000
|
28
|
+
|
29
|
+
self.keep_code_folders = false
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
module TrustedSandbox
|
2
|
+
class InvocationError < StandardError ; end
|
3
|
+
class PoolTimeoutError < StandardError; end
|
4
|
+
class ContainerError < StandardError; end
|
5
|
+
class UserCodeError < StandardError; end
|
6
|
+
class InternalError < StandardError; end
|
7
|
+
class ExecutionTimeoutError < StandardError; end
|
8
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module TrustedSandbox
|
2
|
+
class RequestSerializer
|
3
|
+
|
4
|
+
attr_reader :host_code_dir_path, :input_file_name
|
5
|
+
|
6
|
+
# @param host_code_dir_path [String] path to the folder where the argument value needs to be stored
|
7
|
+
# @param input_file_name [String] name of input file inside the host_code_dir_path
|
8
|
+
def initialize(host_code_dir_path, input_file_name)
|
9
|
+
@host_code_dir_path = host_code_dir_path
|
10
|
+
@input_file_name = input_file_name
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param klass [Class] class name to be serialized
|
14
|
+
# @param args [Array] the array of argument values
|
15
|
+
# @return [String] full path of the argument that was stored
|
16
|
+
def serialize(klass, *args)
|
17
|
+
self.klass = klass
|
18
|
+
copy_code_file
|
19
|
+
|
20
|
+
data = Marshal.dump([klass.name, dest_file_name, args])
|
21
|
+
File.binwrite input_file_path, data
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def input_file_path
|
27
|
+
File.join host_code_dir_path, input_file_name
|
28
|
+
end
|
29
|
+
|
30
|
+
# = Methods depending on @klass
|
31
|
+
|
32
|
+
attr_accessor :klass
|
33
|
+
|
34
|
+
def source_file_path
|
35
|
+
file, _line = klass.instance_method(:initialize).source_location
|
36
|
+
raise InvocationError.new("Cannot find location of class #{klass.name}") unless File.exist?(file.to_s)
|
37
|
+
file
|
38
|
+
end
|
39
|
+
|
40
|
+
def dest_file_name
|
41
|
+
File.basename(source_file_path)
|
42
|
+
end
|
43
|
+
|
44
|
+
def dest_file_path
|
45
|
+
File.join host_code_dir_path, dest_file_name
|
46
|
+
end
|
47
|
+
|
48
|
+
def copy_code_file
|
49
|
+
FileUtils.cp source_file_path, dest_file_path
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module TrustedSandbox
|
2
|
+
class Response
|
3
|
+
|
4
|
+
attr_reader :host_code_dir_path, :output_file_name, :stdout, :stderr,
|
5
|
+
:raw_response, :status, :error, :error_to_raise, :output
|
6
|
+
|
7
|
+
# @param host_code_dir_path [String] path to the folder where the argument value needs to be stored
|
8
|
+
# @param output_file_name [String] name of output file inside the host_code_dir_path
|
9
|
+
def initialize(host_code_dir_path, output_file_name, stdout, stderr)
|
10
|
+
@host_code_dir_path = host_code_dir_path
|
11
|
+
@output_file_name = output_file_name
|
12
|
+
@stdout = stdout
|
13
|
+
@stderr = stderr
|
14
|
+
parse_output_file
|
15
|
+
end
|
16
|
+
|
17
|
+
def valid?
|
18
|
+
status == 'success'
|
19
|
+
end
|
20
|
+
|
21
|
+
def output!
|
22
|
+
propagate_errors!
|
23
|
+
output
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def output_file_path
|
29
|
+
File.join(host_code_dir_path, output_file_name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse_output_file
|
33
|
+
begin
|
34
|
+
data = File.binread output_file_path
|
35
|
+
@raw_response = Marshal.load(data)
|
36
|
+
rescue => e
|
37
|
+
@status = 'error'
|
38
|
+
@error = e
|
39
|
+
@error_to_raise = ContainerError.new(e)
|
40
|
+
return
|
41
|
+
end
|
42
|
+
|
43
|
+
unless ['success', 'error'].include? @raw_response[:status]
|
44
|
+
@status = 'error'
|
45
|
+
@error = ContainerError.new('Output file has invalid format')
|
46
|
+
@error_to_raise = @error
|
47
|
+
return
|
48
|
+
end
|
49
|
+
|
50
|
+
@status = @raw_response[:status]
|
51
|
+
@output = @raw_response[:output]
|
52
|
+
@error = @raw_response[:error]
|
53
|
+
@error_to_raise = UserCodeError.new(@error) if @error
|
54
|
+
end
|
55
|
+
|
56
|
+
def propagate_errors!
|
57
|
+
return if valid?
|
58
|
+
raise InternalError.new 'Response object is invalid but no errors were recorded.' unless error
|
59
|
+
raise error_to_raise
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module TrustedSandbox
|
2
|
+
class Runner
|
3
|
+
|
4
|
+
attr_reader :uid_pool, :config
|
5
|
+
|
6
|
+
# @param config [Config]
|
7
|
+
# @param uid_pool [UidPool]
|
8
|
+
# @param config_override [Hash] allows overriding configurations for a specific invocation
|
9
|
+
def initialize(config, uid_pool, config_override={})
|
10
|
+
@config = config.override(config_override)
|
11
|
+
@uid_pool = uid_pool
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param klass [Class] the class object that should be run
|
15
|
+
# @param *args [Array] arguments to send to klass#initialize
|
16
|
+
# @return [Response]
|
17
|
+
def run(klass, *args)
|
18
|
+
create_code_dir
|
19
|
+
serialize_request(klass, *args)
|
20
|
+
create_container
|
21
|
+
start_container
|
22
|
+
ensure
|
23
|
+
release_uid
|
24
|
+
remove_code_dir unless config.keep_code_folders
|
25
|
+
remove_container
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param klass [Class] the class object that should be run
|
29
|
+
# @param *args [Array] arguments to send to klass#initialize
|
30
|
+
# @return [Response]
|
31
|
+
# @raise [InternalError, UserCodeError, ContainerError]
|
32
|
+
def run!(klass, *args)
|
33
|
+
run(klass, *args).output!
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def obtain_uid
|
39
|
+
@uid ||= uid_pool.lock
|
40
|
+
end
|
41
|
+
|
42
|
+
def release_uid
|
43
|
+
uid_pool.release(@uid) if @uid
|
44
|
+
end
|
45
|
+
|
46
|
+
def code_dir_path
|
47
|
+
@code_dir_path ||= File.join config.host_code_root_path, obtain_uid.to_s
|
48
|
+
end
|
49
|
+
|
50
|
+
def remove_code_dir
|
51
|
+
FileUtils.rm_rf code_dir_path
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_code_dir
|
55
|
+
FileUtils.mkdir_p code_dir_path
|
56
|
+
end
|
57
|
+
|
58
|
+
def serialize_request(klass, *args)
|
59
|
+
serializer = RequestSerializer.new(code_dir_path, config.container_input_filename)
|
60
|
+
serializer.serialize(klass, *args)
|
61
|
+
end
|
62
|
+
|
63
|
+
def create_container
|
64
|
+
@container = Docker::Container.create create_container_request
|
65
|
+
end
|
66
|
+
|
67
|
+
def start_container
|
68
|
+
@container.start start_container_request
|
69
|
+
stdout, stderr = nil, nil
|
70
|
+
Timeout.timeout(config.execution_timeout) do
|
71
|
+
stdout, stderr = @container.attach(stream: true, stdin: nil, stdout: true, stderr: true, logs: true, tty: false)
|
72
|
+
end
|
73
|
+
TrustedSandbox::Response.new code_dir_path, config.container_output_filename, stdout, stderr
|
74
|
+
rescue Timeout::Error => e
|
75
|
+
raise TrustedSandbox::ExecutionTimeoutError.new(e)
|
76
|
+
end
|
77
|
+
|
78
|
+
def remove_container
|
79
|
+
return unless @container
|
80
|
+
@container.delete force: true
|
81
|
+
end
|
82
|
+
|
83
|
+
def create_container_request
|
84
|
+
basic_request = {
|
85
|
+
# 'Hostname' => '',
|
86
|
+
# 'Domainname' => '',
|
87
|
+
# 'User' => '',
|
88
|
+
'CpuShares' => config.cpu_shares,
|
89
|
+
'Memory' => config.memory_limit,
|
90
|
+
# 'Cpuset' => '0,1',
|
91
|
+
'AttachStdin' => false,
|
92
|
+
'AttachStdout' => true,
|
93
|
+
'AttachStderr' => true,
|
94
|
+
# 'PortSpecs' => null,
|
95
|
+
'Tty' => false,
|
96
|
+
'OpenStdin' => false,
|
97
|
+
'StdinOnce' => false,
|
98
|
+
'Cmd' => [@uid.to_s],
|
99
|
+
'Image' => config.docker_image_name,
|
100
|
+
'Volumes' => {
|
101
|
+
config.container_code_path => {}
|
102
|
+
},
|
103
|
+
# 'WorkingDir' => '',
|
104
|
+
'NetworkDisabled' => !config.network_access,
|
105
|
+
# 'ExposedPorts' => {
|
106
|
+
# '22/tcp' => {}
|
107
|
+
# }
|
108
|
+
}
|
109
|
+
basic_request.merge!('MemorySwap' => config.memory_swap_limit) if config.enable_swap_limit
|
110
|
+
basic_request.merge!('Env' => ['USE_QUOTAS=1']) if config.enable_quotas
|
111
|
+
basic_request
|
112
|
+
end
|
113
|
+
|
114
|
+
def start_container_request
|
115
|
+
{
|
116
|
+
'Binds' => ["#{code_dir_path}:#{config.container_code_path}"],
|
117
|
+
# 'Links' => ['redis3:redis'],
|
118
|
+
# 'LxcConf' => {'lxc.utsname' => 'docker'},
|
119
|
+
# 'PortBindings' => {'22/tcp' => [{'HostPort' => '11022'}]},
|
120
|
+
# 'PublishAllPorts' => false,
|
121
|
+
# 'Privileged' => false,
|
122
|
+
# 'Dns' => ['8.8.8.8'],
|
123
|
+
# 'VolumesFrom' => ['parent', 'other:ro'],
|
124
|
+
# 'CapAdd' => ['NET_ADMIN'],
|
125
|
+
# 'CapDrop' => ['MKNOD']
|
126
|
+
}
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
FROM ubuntu:14.04
|
2
|
+
MAINTAINER Amit Aharoni <amit.sites@gmail.com>
|
3
|
+
|
4
|
+
RUN apt-get -y update
|
5
|
+
RUN apt-get -y install build-essential zlib1g-dev libssl-dev libreadline6-dev libyaml-dev wget
|
6
|
+
RUN cd /tmp && wget http://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz && tar -xvzf ruby-2.1.2.tar.gz
|
7
|
+
RUN cd /tmp/ruby-2.1.2/ && ./configure --prefix=/usr/local && make && make install
|
8
|
+
|
9
|
+
RUN groupadd app && useradd -m -G app -d /home/sandbox sandbox
|
10
|
+
|
11
|
+
RUN gem install bundler
|
12
|
+
ADD Gemfile /home/sandbox/Gemfile
|
13
|
+
ADD bundle_config /home/sandbox/.bundle/config
|
14
|
+
RUN chown sandbox /home/sandbox/Gemfile && \
|
15
|
+
chown sandbox /home/sandbox/.bundle && \
|
16
|
+
chown sandbox /home/sandbox/.bundle/config && \
|
17
|
+
sudo -u sandbox -i bundle install
|
18
|
+
|
19
|
+
ADD entrypoint.sh entrypoint.sh
|
20
|
+
ADD run.rb /home/sandbox/run.rb
|
21
|
+
RUN chown sandbox /home/sandbox/run.rb
|
22
|
+
|
23
|
+
ENTRYPOINT ["/bin/bash", "entrypoint.sh"]
|
@@ -0,0 +1,15 @@
|
|
1
|
+
uid=$1
|
2
|
+
|
3
|
+
if [ -z $uid ]; then
|
4
|
+
echo "you must provide a uid"
|
5
|
+
exit 1
|
6
|
+
fi
|
7
|
+
|
8
|
+
echo "127.0.0.1 $(hostname)" >> /etc/hosts
|
9
|
+
|
10
|
+
if [ -n "$USE_QUOTAS" -a "$USE_QUOTAS" != "0" -a "$USE_QUOTAS" != "false" ]; then
|
11
|
+
usermod -u $uid sandbox
|
12
|
+
chown sandbox /home/sandbox/src
|
13
|
+
fi
|
14
|
+
|
15
|
+
sudo -u sandbox -i bundle exec ruby run.rb
|
@@ -0,0 +1,18 @@
|
|
1
|
+
begin
|
2
|
+
require 'active_support'
|
3
|
+
|
4
|
+
input_file_path = '/home/sandbox/src/input'
|
5
|
+
output_file_path = '/home/sandbox/src/output'
|
6
|
+
|
7
|
+
data = File.binread(input_file_path)
|
8
|
+
klass_name, file_name, args = Marshal.load(data)
|
9
|
+
require File.join('/home/sandbox/src', file_name)
|
10
|
+
klass = ActiveSupport::Inflector.constantize klass_name
|
11
|
+
|
12
|
+
obj = klass.new(*args)
|
13
|
+
output = obj.run
|
14
|
+
|
15
|
+
File.binwrite output_file_path, Marshal.dump(status: 'success', output: output)
|
16
|
+
rescue => e
|
17
|
+
File.binwrite output_file_path, Marshal.dump(status: 'error', error: e)
|
18
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
module TrustedSandbox
|
2
|
+
|
3
|
+
# Offers intra-server inter-process pool of Uids. In other words:
|
4
|
+
# - Every server has its own pool. Since Docker containers live within a server, this is what we want.
|
5
|
+
# - Processes within the same server share the pool.
|
6
|
+
#
|
7
|
+
# Usage:
|
8
|
+
# The following will behave the same when different processes try to perform #lock and #release.
|
9
|
+
#
|
10
|
+
# pool = UidPool.new 100, 101
|
11
|
+
# pool.lock
|
12
|
+
# # => 100
|
13
|
+
#
|
14
|
+
# pool.lock
|
15
|
+
# # => 101
|
16
|
+
#
|
17
|
+
# pool.lock
|
18
|
+
# # => RuntimeError: No available UIDs in the pool. Please try again later.
|
19
|
+
#
|
20
|
+
# pool.release(100)
|
21
|
+
# # => 100
|
22
|
+
#
|
23
|
+
# pool.lock
|
24
|
+
# # => 100
|
25
|
+
#
|
26
|
+
# pool.release_all
|
27
|
+
#
|
28
|
+
class UidPool
|
29
|
+
|
30
|
+
attr_reader :lock_dir, :master_lock_file, :lower, :upper, :timeout, :retries, :delay
|
31
|
+
|
32
|
+
# @param lower [Integer] lower bound of the pool
|
33
|
+
# @param upper [Integer] upper bound of the pool
|
34
|
+
# @option timeout [Integer] number of seconds to wait for the lock
|
35
|
+
# @option retries [Integer] number of attempts to retry to acquire a uid
|
36
|
+
# @option delay [Float] delay between retries
|
37
|
+
def initialize(lock_dir, lower, upper, timeout: nil, retries: nil, delay: nil)
|
38
|
+
@lock_dir = lock_dir
|
39
|
+
FileUtils.mkdir_p(lock_dir)
|
40
|
+
|
41
|
+
@master_lock_file = lock_file_path_for('master')
|
42
|
+
@lower = lower
|
43
|
+
@upper = upper
|
44
|
+
@timeout = timeout || 3
|
45
|
+
@retries = retries || 5
|
46
|
+
@delay = delay || 0.5
|
47
|
+
end
|
48
|
+
|
49
|
+
def inspect
|
50
|
+
"#<TrustedSandbox::UidPool used: #{used}, available: #{available}, used_uids: #{used_uids}>"
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [Integer]
|
54
|
+
def lock
|
55
|
+
retries.times do
|
56
|
+
atomically(timeout) do
|
57
|
+
uid = available_uid
|
58
|
+
if uid
|
59
|
+
lock_uid uid
|
60
|
+
return uid.to_i
|
61
|
+
end
|
62
|
+
end
|
63
|
+
sleep(delay)
|
64
|
+
end
|
65
|
+
raise PoolTimeoutError.new('No available UIDs in the pool. Please try again later.')
|
66
|
+
end
|
67
|
+
|
68
|
+
# Releases all UIDs
|
69
|
+
# @return [UidPool] self
|
70
|
+
def release_all
|
71
|
+
all_uids.each do |uid|
|
72
|
+
release uid
|
73
|
+
end
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
# @param uid [Integer]
|
78
|
+
def release(uid)
|
79
|
+
atomically(timeout) do
|
80
|
+
release_uid uid
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# @return [Integer] number of used UIDs
|
85
|
+
def used
|
86
|
+
used_uids.length
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [Integer] number of availabld UIDs
|
90
|
+
def available
|
91
|
+
available_uids.length
|
92
|
+
end
|
93
|
+
|
94
|
+
# @return [Array<Integer>] all taken uids
|
95
|
+
def used_uids
|
96
|
+
uids = Dir.entries(lock_dir) - %w(. .. master)
|
97
|
+
uids.map(&:to_i)
|
98
|
+
end
|
99
|
+
|
100
|
+
# @return [Array<Integer>] all non taken uids
|
101
|
+
def available_uids
|
102
|
+
all_uids - used_uids
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
# @return [Array<Integer>] all uids in range
|
108
|
+
def all_uids
|
109
|
+
[*lower..upper]
|
110
|
+
end
|
111
|
+
|
112
|
+
# @param uid [Integer]
|
113
|
+
# @return [String] full path for the UID lock file
|
114
|
+
def lock_file_path_for(uid)
|
115
|
+
File.join lock_dir, uid.to_s
|
116
|
+
end
|
117
|
+
|
118
|
+
# Creates a UID lock file in the lock_dir
|
119
|
+
#
|
120
|
+
# @param uid [Integer]
|
121
|
+
# @return [Integer] the UID locked
|
122
|
+
def lock_uid(uid)
|
123
|
+
File.open lock_file_path_for(uid), 'w'
|
124
|
+
uid
|
125
|
+
end
|
126
|
+
|
127
|
+
# Removes a UID lock file from the lock_dir
|
128
|
+
#
|
129
|
+
# @param uid [Integer]
|
130
|
+
# @return [Integer] the UID removed
|
131
|
+
def release_uid(uid)
|
132
|
+
FileUtils.rm lock_file_path_for(uid), force: true
|
133
|
+
uid
|
134
|
+
end
|
135
|
+
|
136
|
+
# @param timeout [Integer]
|
137
|
+
# @return yield return value
|
138
|
+
def atomically(timeout)
|
139
|
+
Timeout.timeout(timeout) do
|
140
|
+
File.open(master_lock_file, File::RDWR|File::CREAT, 0644) do |f|
|
141
|
+
f.flock File::LOCK_EX
|
142
|
+
yield
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# @return [Integer, nil] one available uid or nil if none is available
|
148
|
+
def available_uid
|
149
|
+
available_uids.first
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module TrustedSandbox
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'docker'
|
5
|
+
require 'trusted_sandbox/config'
|
6
|
+
require 'trusted_sandbox/defaults'
|
7
|
+
require 'trusted_sandbox/errors'
|
8
|
+
require 'trusted_sandbox/request_serializer'
|
9
|
+
require 'trusted_sandbox/response'
|
10
|
+
require 'trusted_sandbox/runner'
|
11
|
+
require 'trusted_sandbox/uid_pool'
|
12
|
+
require 'trusted_sandbox/version'
|
13
|
+
|
14
|
+
# Usage:
|
15
|
+
# TrustedSandbox.config do |c|
|
16
|
+
# c.pool_size = 10
|
17
|
+
# # ...
|
18
|
+
# end
|
19
|
+
def self.config
|
20
|
+
@config ||= Defaults.send(:new).override config_overrides_from_file
|
21
|
+
yield @config if block_given?
|
22
|
+
@config.finished_configuring
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.config_overrides_from_file(env = nil)
|
26
|
+
yaml_path = %w(trusted_sandbox.yml config/trusted_sandbox.yml).find {|x| File.exist?(x)}
|
27
|
+
return {} unless yaml_path
|
28
|
+
|
29
|
+
env ||= ENV['TRUSTED_SANDBOX_ENV'] || ENV['RAILS_ENV'] || 'development'
|
30
|
+
YAML.load_file(yaml_path)[env]
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.uid_pool
|
34
|
+
@uid_pool ||= UidPool.new config.host_uid_pool_lock_path, config.pool_min_uid, config.pool_max_uid,
|
35
|
+
timeout: config.pool_timeout, retries: config.pool_retries, delay: config.pool_delay
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param config_override [Hash] allows overriding configurations for a specific invocation
|
39
|
+
def self.with_options(config_override={})
|
40
|
+
yield new_runner(config_override)
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param klass [Class] the class to be instantiated in the safe sandbox
|
44
|
+
# @param *args [Array] arguments to send to klass#new
|
45
|
+
def self.run(klass, *args)
|
46
|
+
new_runner.run(klass, *args)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.run!(klass, *args)
|
50
|
+
new_runner.run!(klass, *args)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.new_runner(config_override = {})
|
54
|
+
Runner.new(config, uid_pool, config_override)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Run the configuration block
|
59
|
+
TrustedSandbox.config
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'trusted_sandbox/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'trusted-sandbox'
|
8
|
+
spec.version = TrustedSandbox::VERSION
|
9
|
+
spec.authors = ['Amit Aharoni']
|
10
|
+
spec.email = ['amit.sites@gmail.com']
|
11
|
+
spec.description = %q{Trusted Sandbox makes it simple to execute Ruby classes that eval untrusted code in a resource-controlled docker container}
|
12
|
+
spec.summary = %q{Run untrusted Ruby code in a contained sandbox using Docker}
|
13
|
+
spec.homepage = 'https://github.com/vaharoni/trusted-sandbox'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
22
|
+
spec.add_development_dependency 'rake'
|
23
|
+
|
24
|
+
spec.add_runtime_dependency 'docker-api', '~> 1.13'
|
25
|
+
spec.add_runtime_dependency 'thor', '~> 0.19'
|
26
|
+
end
|