trusted-sandbox 0.0.10.pre → 0.0.11.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,85 @@
1
+ require 'spec_helper'
2
+
3
+ # This should not be run by CI as it requires server installation.
4
+
5
+ describe 'integration testing' do
6
+
7
+ describe 'sanity test' do
8
+ it 'works for inline' do
9
+ TrustedSandbox.run_code!('input[:x] ** 2', input: {x: 10}).should == 100
10
+
11
+ response = TrustedSandbox.run_code('puts "hi"; input[:x] ** 2', input: {x: 10})
12
+ response.valid?.should == true
13
+ response.output.should == 100
14
+ response.stdout.should == ["hi\n"]
15
+ end
16
+
17
+ it 'works for a class' do
18
+ TrustedSandbox.run!(TrustedSandbox::GeneralPurpose, 'input[:x] ** 2', input: {x: 10}).should == 100
19
+
20
+ response = TrustedSandbox.run(TrustedSandbox::GeneralPurpose, 'puts "hi"; input[:x] ** 2', input: {x: 10})
21
+ response.valid?.should == true
22
+ response.output.should == 100
23
+ response.stdout.should == ["hi\n"]
24
+ end
25
+
26
+ it 'works when there is an error' do
27
+ expect {TrustedSandbox.run_code!('asfsadf')}.to raise_error(TrustedSandbox::UserCodeError)
28
+
29
+ response = TrustedSandbox.run_code('asfsadf')
30
+ response.valid?.should == false
31
+ response.output.should == nil
32
+ response.status.should == 'error'
33
+ response.error.is_a?(NameError).should == true
34
+ response.error_to_raise.is_a?(TrustedSandbox::UserCodeError).should == true
35
+ end
36
+ end
37
+
38
+ describe 'memory limit' do
39
+ it 'raises error when limited' do
40
+ response = TrustedSandbox.with_options(memory_limit: 50_000_000) do |s|
41
+ s.run_code('x = "*" * 50_000_000')
42
+ end
43
+ response.valid?.should == false
44
+ response.stderr.should == ["Killed\n"]
45
+ end
46
+
47
+ it 'works when not limited' do
48
+ response = TrustedSandbox.with_options(memory_limit: 100_000_000) do |s|
49
+ s.run_code('x = "*" * 50_000_000')
50
+ end
51
+ response.stderr.should be_empty
52
+ response.stdout.should be_empty
53
+ response.valid?.should == true
54
+ end
55
+ end
56
+
57
+ describe 'network limit' do
58
+ it 'raises error when limited' do
59
+ response = TrustedSandbox.with_options(network_access: false) do |s|
60
+ s.run_code('`ping www.google.com -c 1; echo $?`.split("\n").last')
61
+ end
62
+ response.stderr.should == ["ping: unknown host www.google.com\n"]
63
+ response.output.to_i.should_not == 0
64
+ end
65
+
66
+ it 'works when not limited' do
67
+ response = TrustedSandbox.with_options(network_access: true) do |s|
68
+ s.run_code('`ping www.google.com -c 1; echo $?`.split("\n").last')
69
+ end
70
+ response.stderr.should be_empty
71
+ response.output.to_i.should == 0
72
+ end
73
+ end
74
+
75
+ describe 'time limit' do
76
+ it 'raises error' do
77
+ response = TrustedSandbox.with_options(execution_timeout: 1) do |s|
78
+ s.run_code('puts "hi"; while true; end')
79
+ end
80
+ response.valid?.should == false
81
+ response.error.is_a?(Timeout::Error).should == true
82
+ response.error_to_raise.is_a?(TrustedSandbox::ExecutionTimeoutError).should == true
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ # Note! Will only work on linux machine that is configured appropriately with user quotas of 10 MB.
4
+ # This should not be run by CI.
5
+
6
+ # Usage from a configured server:
7
+ # rspec spec/integration/quota_spec.rb
8
+
9
+ describe 'quota limit integration testing' do
10
+ it 'works when quotas are unlimited' do
11
+ response = TrustedSandbox.with_options(enable_quotas: false) do |s|
12
+ s.run_code "File.open('test','w') {|f| f.write '*' * 15_000_000}"
13
+ end
14
+ response.valid?.should == true
15
+ end
16
+
17
+ # rspec spec/integration/quota_spec.rb --example "quota limit integration testing does not work when quotas are limited"
18
+ it 'does not work when quotas are limited' do
19
+ response = TrustedSandbox.with_options(enable_quotas: true) do |s|
20
+ s.run_code "File.open('test','w') {|f| f.write '*' * 15_000_000}"
21
+ end
22
+ response.valid?.should == false
23
+ response.stderr.any? {|row| row =~ /disk quota exceeded/i}.should == true
24
+ end
25
+ end
@@ -0,0 +1,91 @@
1
+ require 'spec_helper'
2
+
3
+ describe TrustedSandbox::Config do
4
+ before do
5
+ @defaults = TrustedSandbox::Defaults.send(:new)
6
+ end
7
+
8
+ describe 'override mechanism' do
9
+ before do
10
+ @subject = @defaults.override cpu_shares: 2
11
+ end
12
+
13
+ it 'ensures defaults have what we expect' do
14
+ @defaults.cpu_shares.should == 1
15
+ @defaults.execution_timeout.should == 15
16
+ end
17
+
18
+ it 'works' do
19
+ @subject.cpu_shares.should == 2
20
+ @subject.execution_timeout.should == 15
21
+ end
22
+ end
23
+
24
+ describe '#pool_max_id' do
25
+ before do
26
+ @subject = @defaults.override pool_min_uid: 100, pool_size: 10
27
+ end
28
+ it 'works' do
29
+ @subject.pool_max_uid.should == 109
30
+ end
31
+ end
32
+
33
+ describe 'docker_url=' do
34
+ before do
35
+ @url = 'http://localhost'
36
+ @subject = @defaults.override docker_url: @url
37
+ end
38
+ it 'sets up Docker' do
39
+ Docker.url.should == @url
40
+ end
41
+ end
42
+
43
+ describe 'host_code_root_path= and host_uid_pool_lock_path=' do
44
+ before do
45
+ @subject = @defaults.override host_code_root_path: '~/tmp', host_uid_pool_lock_path: '~/tmp2'
46
+ end
47
+ it 'expands the path' do
48
+ @subject.host_code_root_path.should == File.expand_path('~/tmp')
49
+ @subject.host_uid_pool_lock_path.should == File.expand_path('~/tmp2')
50
+ end
51
+ end
52
+
53
+ describe 'docker_cert_path and docker_options' do
54
+ before do
55
+ @subject = @defaults.override docker_cert_path: '~/tmp', docker_options: { ssl_verify_peer: true }
56
+ end
57
+
58
+ it 'works' do
59
+ @subject.finished_configuring
60
+ Docker.options = { private_key_path: File.expand_path('~/tmp/key.pem'),
61
+ certificate_path: File.expand_path('~/tmp/cert.pem'),
62
+ ssl_verify_peer: true }
63
+ end
64
+ end
65
+
66
+ describe 'docker authentication' do
67
+ context 'user did not request to authenticate' do
68
+ before do
69
+ @subject = @defaults.override
70
+ dont_allow(Docker).authenticate!
71
+ end
72
+ it 'does not perform authentication' do
73
+ @subject.finished_configuring
74
+ end
75
+ end
76
+ context 'user requested to authenticate' do
77
+ before do
78
+ @subject = @defaults.override docker_login: {user: 'user', password: 'password', email: 'email'}
79
+ mock(Docker).authenticate!(username: 'user', password: 'password', email: 'email').times(1)
80
+ end
81
+
82
+ it 'call Docker.authenticate!' do
83
+ @subject.finished_configuring
84
+ end
85
+
86
+ it 'does not call Docker.authenticate! twice' do
87
+ 2.times { @subject.finished_configuring }
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe TrustedSandbox::RequestSerializer do
4
+ before do
5
+ @tmp_path = 'tmp/test/request_serializer'
6
+ @file_name = 'args'
7
+ @args_file_path = File.expand_path File.join(@tmp_path, @file_name)
8
+ FileUtils.rm_rf @tmp_path
9
+ FileUtils.mkdir_p @tmp_path
10
+ end
11
+
12
+ describe '#initialize' do
13
+ before do
14
+ @subject = TrustedSandbox::RequestSerializer.new(@tmp_path, @file_name)
15
+ end
16
+
17
+ it 'initializes attributes correctly' do
18
+ @subject.host_code_dir_path.should == @tmp_path
19
+ @subject.input_file_name.should == @file_name
20
+ end
21
+
22
+ end
23
+
24
+ describe '#serialize' do
25
+ before do
26
+ @subject = TrustedSandbox::RequestSerializer.new(@tmp_path, @file_name)
27
+ @arg1 = { test: 'working' }
28
+ @arg2 = { another_test: 'working too' }
29
+ @subject.serialize TrustedSandbox::RequestSerializer, @arg1, @arg2
30
+
31
+ @source_class_file = File.expand_path('lib/trusted_sandbox/request_serializer.rb')
32
+ @target_class_file = File.expand_path File.join(@tmp_path, 'request_serializer.rb')
33
+ end
34
+
35
+ it 'copies the class file' do
36
+ File.exists?(@target_class_file).should == true
37
+ File.read(@target_class_file).should == File.read(@source_class_file)
38
+ end
39
+
40
+ it 'serializes arguments' do
41
+ File.exists?(@args_file_path).should == true
42
+ data = File.binread(@args_file_path)
43
+ Marshal.load(data).should == ['TrustedSandbox::RequestSerializer', 'request_serializer.rb', [@arg1, @arg2]]
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ describe TrustedSandbox::Response do
4
+ before do
5
+ @tmp_path = 'tmp/test/response'
6
+ @file_name = 'hello'
7
+ @file_path = File.expand_path File.join(@tmp_path, @file_name)
8
+ FileUtils.rm_rf @tmp_path
9
+ FileUtils.mkdir_p @tmp_path
10
+ end
11
+
12
+ context 'no error' do
13
+ before do
14
+ File.binwrite @file_path, Marshal.dump(status: 'success', output: 'hi')
15
+ @subject = TrustedSandbox::Response.new('stdout', 'stderr', @tmp_path, @file_name)
16
+ @subject.parse!
17
+ end
18
+
19
+ it 'instantiates correctly' do
20
+ @subject.host_code_dir_path.should == @tmp_path
21
+ @subject.output_file_name.should == @file_name
22
+ @subject.stdout.should == 'stdout'
23
+ @subject.stderr.should == 'stderr'
24
+ end
25
+
26
+ it 'parses the file correctly' do
27
+ @subject.raw_response.should == {status: 'success', output: 'hi'}
28
+ @subject.status.should == 'success'
29
+ @subject.output.should == 'hi'
30
+ @subject.error.should be_nil
31
+ @subject.error_to_raise.should be_nil
32
+ @subject.valid?.should == true
33
+ end
34
+ end
35
+
36
+ context 'user error' do
37
+ before do
38
+ @err = 1 / 0 rescue $!
39
+ File.binwrite @file_path, Marshal.dump(status: 'error', error: @err)
40
+ @subject = TrustedSandbox::Response.new(nil, nil, @tmp_path, @file_name)
41
+ @subject.parse!
42
+ end
43
+
44
+ it 'initializes with an error' do
45
+ @subject.raw_response.should == {status: 'error', error: @err}
46
+ @subject.status.should == 'error'
47
+ @subject.output.should == nil
48
+ @subject.error.should == @err
49
+ @subject.error_to_raise.is_a?(TrustedSandbox::UserCodeError).should == true
50
+ expect {@subject.output!}.to raise_error(TrustedSandbox::UserCodeError)
51
+ @subject.valid?.should == false
52
+ end
53
+ end
54
+
55
+ context 'unexpected file format' do
56
+ before do
57
+ @err = 1 / 0 rescue $!
58
+ File.binwrite @file_path, Marshal.dump(status: 'unexpected', output: 'hi', error: @err)
59
+ @subject = TrustedSandbox::Response.new(nil, nil, @tmp_path, @file_name)
60
+ @subject.parse!
61
+ end
62
+
63
+ it 'initializes with an error' do
64
+ @subject.raw_response.should == {status: 'unexpected', output: 'hi', error: @err}
65
+ @subject.status.should == 'error'
66
+ @subject.output.should == nil
67
+ @subject.error.is_a?(TrustedSandbox::ContainerError).should == true
68
+ @subject.error_to_raise.is_a?(TrustedSandbox::ContainerError).should == true
69
+ expect {@subject.output!}.to raise_error(TrustedSandbox::ContainerError)
70
+ @subject.valid?.should == false
71
+ end
72
+ end
73
+
74
+ context 'file is missing' do
75
+ before do
76
+ @subject = TrustedSandbox::Response.new(nil, nil, @tmp_path, @file_name)
77
+ @subject.parse!
78
+ end
79
+
80
+ it 'initializes with an error' do
81
+ @subject.raw_response.should == nil
82
+ @subject.status.should == 'error'
83
+ @subject.output.should == nil
84
+ @subject.error.is_a?(Errno::ENOENT).should == true
85
+ @subject.error_to_raise.is_a?(TrustedSandbox::ContainerError).should == true
86
+ expect {@subject.output!}.to raise_error(TrustedSandbox::ContainerError)
87
+ @subject.valid?.should == false
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,171 @@
1
+ require 'spec_helper'
2
+
3
+ describe TrustedSandbox::Runner do
4
+
5
+ before do
6
+ @defaults = TrustedSandbox::Defaults.send(:new).override(quiet_mode: true)
7
+ @uid_pool = Object.new
8
+ end
9
+
10
+ describe 'UID pool and code dir handling' do
11
+ context 'keep_code_folders=false' do
12
+ before do
13
+ mock(@uid_pool).lock { 100 }
14
+ mock(@uid_pool).release(100) {}
15
+ @subject = TrustedSandbox::Runner.new @defaults, @uid_pool, keep_code_folders: false
16
+ stub(@subject).create_container
17
+ stub(@subject).start_container
18
+ end
19
+
20
+ it 'locks and releases from UID pool' do
21
+ @subject.run TrustedSandbox::Runner
22
+ end
23
+
24
+ it 'deletes the code folder' do
25
+ @subject.run TrustedSandbox::Runner
26
+ Dir.exists?(@subject.send(:code_dir_path)).should == false
27
+ end
28
+ end
29
+
30
+ context 'keep_code_folders=true' do
31
+ before do
32
+ mock(@uid_pool).lock { 100 }
33
+ dont_allow(@uid_pool).release
34
+ @subject = TrustedSandbox::Runner.new @defaults, @uid_pool, keep_code_folders: true
35
+ stub(@subject).create_container
36
+ stub(@subject).start_container
37
+ end
38
+
39
+ it 'locks but does not release from UID pool' do
40
+ @subject.run TrustedSandbox::Runner
41
+ end
42
+
43
+ it 'does not delete the code folder' do
44
+ @subject.run TrustedSandbox::Runner
45
+ Dir.exists?(@subject.send(:code_dir_path)).should == true
46
+ end
47
+ end
48
+ end
49
+
50
+ describe 'container creation' do
51
+ before do
52
+ stub(@uid_pool).lock { 100 }
53
+ stub(@uid_pool).release(100) {}
54
+
55
+ container = Object.new
56
+ @container = container
57
+
58
+ create_req = {}
59
+ stub(Docker::Container).create { |req| create_req.clear; create_req.merge!(req); container }
60
+ @create_req = create_req
61
+
62
+ start_req = {}
63
+ stub(container).start { |req| start_req.clear; start_req.merge!(req) }
64
+ @start_req = start_req
65
+
66
+ mock(container).attach(stream: true, stdin: nil, stdout: true, stderr: true, logs: true, tty: false) { ['stdout', 'stderr'] }
67
+ end
68
+
69
+ context 'keep_containers=true' do
70
+ before do
71
+ @subject = TrustedSandbox::Runner.new @defaults, @uid_pool, keep_containers: true
72
+ dont_allow(@container).delete
73
+ end
74
+
75
+ it 'does not delete the container' do
76
+ @subject.run TrustedSandbox::Runner
77
+ end
78
+ end
79
+
80
+ context 'keep_containers=false' do
81
+ before do
82
+ @subject = TrustedSandbox::Runner.new @defaults, @uid_pool, keep_containers: false
83
+ mock(@container).delete(force: true) {}
84
+ end
85
+
86
+ it 'does not delete the container' do
87
+ @subject.run TrustedSandbox::Runner
88
+ end
89
+ end
90
+
91
+ context 'basic request parameters' do
92
+ before do
93
+ @subject = TrustedSandbox::Runner.new @defaults, @uid_pool, cpu_shares: 5, memory_limit: 100,
94
+ docker_image_name: 'image', container_code_path: '/code',
95
+ network_access: false, keep_containers: true
96
+ @subject.run TrustedSandbox::Runner
97
+ end
98
+
99
+ it 'sends the right requests' do
100
+ @create_req.should == {"CpuShares"=>5, "Memory"=>100, "AttachStdin"=>false, "AttachStdout"=>true, "AttachStderr"=>true, "Tty"=>false, "OpenStdin"=>false, "StdinOnce"=>false, "Cmd"=>["100"], "Image"=>"image", "Volumes"=>{"/code"=>{}}, "NetworkDisabled"=>true}
101
+ @start_req.should == {"Binds"=>["#{File.expand_path('tmp/code_dirs/100')}:/code"]}
102
+ end
103
+ end
104
+
105
+ context 'enable_quotas=true' do
106
+ before do
107
+ @subject = TrustedSandbox::Runner.new @defaults, @uid_pool, enable_quotas: true, keep_containers: true
108
+ @subject.run TrustedSandbox::Runner
109
+ end
110
+
111
+ it 'sends the right request' do
112
+ @create_req['Env'].should == ['USE_QUOTAS=1']
113
+ end
114
+ end
115
+
116
+ context 'enable_quotas=false' do
117
+ before do
118
+ @subject = TrustedSandbox::Runner.new @defaults, @uid_pool, enable_quotas: false, keep_containers: true
119
+ @subject.run TrustedSandbox::Runner
120
+ end
121
+
122
+ it 'sends the right request' do
123
+ @create_req['Env'].should be_nil
124
+ end
125
+ end
126
+
127
+ context 'enable_swap_limit=true' do
128
+ before do
129
+ @subject = TrustedSandbox::Runner.new @defaults, @uid_pool, enable_swap_limit: true, memory_swap_limit: 200, keep_containers: true
130
+ @subject.run TrustedSandbox::Runner
131
+ end
132
+
133
+ it 'sends the right request' do
134
+ @create_req['MemorySwap'].should == 200
135
+ end
136
+ end
137
+
138
+ context 'enable_swap_limit=false' do
139
+ before do
140
+ @subject = TrustedSandbox::Runner.new @defaults, @uid_pool, enable_swap_limit: false, memory_swap_limit: 200, keep_containers: true
141
+ @subject.run TrustedSandbox::Runner
142
+ end
143
+
144
+ it 'sends the right request' do
145
+ @create_req['MemorySwap'].should be_nil
146
+ end
147
+ end
148
+
149
+ context 'network_access=true' do
150
+ before do
151
+ @subject = TrustedSandbox::Runner.new @defaults, @uid_pool, network_access: true, keep_containers: true
152
+ @subject.run TrustedSandbox::Runner
153
+ end
154
+
155
+ it 'sends the right request' do
156
+ @create_req['NetworkDisabled'].should == false
157
+ end
158
+ end
159
+
160
+ context 'network_access=false' do
161
+ before do
162
+ @subject = TrustedSandbox::Runner.new @defaults, @uid_pool, network_access: false, keep_containers: true
163
+ @subject.run TrustedSandbox::Runner
164
+ end
165
+
166
+ it 'sends the right request' do
167
+ @create_req['NetworkDisabled'].should == true
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,110 @@
1
+ require 'spec_helper'
2
+
3
+ describe TrustedSandbox::UidPool do
4
+ before do
5
+ @tmp_dir = 'tmp/test/uid_pool'
6
+ FileUtils.rm_rf @tmp_dir
7
+ FileUtils.mkdir_p @tmp_dir
8
+ @class = TrustedSandbox::UidPool
9
+ end
10
+
11
+ describe '#initialize' do
12
+ context 'with defaults' do
13
+ before do
14
+ @subject = @class.new(@tmp_dir, 1, 3)
15
+ end
16
+
17
+ it 'sets up defaults correctly' do
18
+ @subject.timeout.should == 3
19
+ @subject.retries.should == 5
20
+ @subject.delay.should == 0.5
21
+ end
22
+
23
+ end
24
+
25
+ context 'with other values' do
26
+ before do
27
+ @subject = @class.new(@tmp_dir, 1, 3, 'timeout' => 5, retries: 10, 'delay' => 1)
28
+ end
29
+
30
+ it 'sets up values correctly' do
31
+ @subject.timeout.should == 5
32
+ @subject.retries.should == 10
33
+ @subject.delay.should == 1
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ describe 'usage' do
40
+ before do
41
+ @subject = @class.new(@tmp_dir, 1, 3, retries: 1, timeout: 0.1, delay: 0.1)
42
+ @subject.release_all
43
+ end
44
+
45
+ describe '#lock' do
46
+ context 'There are still available IDs' do
47
+ it 'gives the UIDs' do
48
+ [@subject.lock, @subject.lock, @subject.lock].should == [1, 2, 3]
49
+ end
50
+ end
51
+
52
+ context 'There are no available IDs' do
53
+ before do
54
+ 3.times { @subject.lock }
55
+ end
56
+
57
+ it 'raises an error' do
58
+ expect {@subject.lock}.to raise_error(TrustedSandbox::PoolTimeoutError)
59
+ end
60
+ end
61
+ end
62
+
63
+ describe '#available, #used, #release' do
64
+ before do
65
+ @subject.release_all
66
+ @uid = @subject.lock
67
+ end
68
+ it 'sets the right available and used' do
69
+ @subject.available.should == 2
70
+ @subject.used.should == 1
71
+ @subject.available_uids.should == [2,3]
72
+ @subject.used_uids.should == [1]
73
+ end
74
+ it 'releases the right uid' do
75
+ @subject.release @uid
76
+ @subject.available.should == 3
77
+ @subject.used.should == 0
78
+ @subject.available_uids.should == [1,2,3]
79
+ @subject.used_uids.should == []
80
+ end
81
+ it 'does not release the wrong uid' do
82
+ @subject.release @uid + 1
83
+ @subject.available.should == 2
84
+ @subject.used.should == 1
85
+ @subject.available_uids.should == [2,3]
86
+ @subject.used_uids.should == [1]
87
+ end
88
+ end
89
+
90
+ describe '#release_all' do
91
+ before do
92
+ @subject.release_all
93
+ 3.times { @subject.lock }
94
+ end
95
+ it 'passes sanity tests' do
96
+ @subject.available.should == 0
97
+ @subject.used.should == 3
98
+ @subject.available_uids.should == []
99
+ @subject.used_uids.should == [1,2,3]
100
+ end
101
+ it 'works' do
102
+ @subject.release_all
103
+ @subject.available.should == 3
104
+ @subject.used.should == 0
105
+ @subject.available_uids.should == [1,2,3]
106
+ @subject.used_uids.should == []
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe TrustedSandbox do
4
+ describe '#with_options' do
5
+ before do
6
+ @default_network_access = TrustedSandbox.config.network_access
7
+ end
8
+
9
+ it 'overrides configuration' do
10
+ TrustedSandbox.with_options(network_access: !@default_network_access) do |runner|
11
+ runner.config.network_access.should == !@default_network_access
12
+ end
13
+ end
14
+ end
15
+ end