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