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
@@ -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
|