unicorn-lockdown 0.12.0 → 1.1.0
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 +4 -4
- data/CHANGELOG +30 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +158 -88
- data/bin/unicorn-lockdown-add +1 -223
- data/bin/unicorn-lockdown-setup +1 -67
- data/files/unicorn_lockdown_add.rb +227 -0
- data/files/unicorn_lockdown_setup.rb +74 -0
- data/lib/rack/email_exceptions.rb +6 -1
- data/lib/roda/plugins/pg_disconnect.rb +8 -9
- data/lib/unicorn-lockdown.rb +93 -101
- data/lib/unveiler.rb +42 -0
- metadata +69 -8
- data/lib/chrooter.rb +0 -110
data/lib/unicorn-lockdown.rb
CHANGED
@@ -1,25 +1,38 @@
|
|
1
|
-
# unicorn-lockdown is designed to
|
2
|
-
#
|
1
|
+
# unicorn-lockdown is designed to handle fork+exec, unveil, and pledge support
|
2
|
+
# when using Unicorn, including:
|
3
|
+
# * restricting file system access using unveil
|
3
4
|
# * pledging the app to restrict allowed syscalls at the appropriate point
|
4
|
-
# * handling notifications of worker crashes
|
5
|
-
#
|
6
|
-
# * stripping path prefixes from the reloader in development mode
|
5
|
+
# * handling notifications of worker crashes (which are likely due to pledge
|
6
|
+
# violations)
|
7
7
|
|
8
8
|
require 'pledge'
|
9
|
+
require 'unveil'
|
9
10
|
|
10
|
-
#
|
11
|
+
# Load common encodings
|
11
12
|
"\255".force_encoding('ISO8859-1').encode('UTF-8')
|
12
|
-
|
13
|
-
# Load encodings
|
14
13
|
''.force_encoding('UTF-16LE')
|
15
14
|
''.force_encoding('UTF-16BE')
|
16
15
|
|
17
16
|
class Unicorn::HttpServer
|
18
|
-
# The file name in which to store request information.
|
19
|
-
# /var/www/
|
20
|
-
# to
|
17
|
+
# The file name in which to store request information.
|
18
|
+
# The /var/www/request-error-data/$app_name folder is accessable
|
19
|
+
# only to the user of the application.
|
21
20
|
def request_filename(pid)
|
22
|
-
"/
|
21
|
+
"#{Unicorn.unicorn_lockdown_prefix}/www/request-error-data/#{Unicorn.app_name}/#{pid}.txt"
|
22
|
+
end
|
23
|
+
|
24
|
+
unless ENV['UNICORN_WORKER']
|
25
|
+
alias _original_spawn_missing_workers spawn_missing_workers
|
26
|
+
|
27
|
+
# This is the master process, set the master pledge before spawning
|
28
|
+
# workers, because spawning workers will also need to be done at runtime.
|
29
|
+
def spawn_missing_workers
|
30
|
+
if pledge = Unicorn.master_pledge
|
31
|
+
Unicorn.master_pledge = nil
|
32
|
+
Pledge.pledge(pledge, Unicorn.master_execpledge)
|
33
|
+
end
|
34
|
+
_original_spawn_missing_workers
|
35
|
+
end
|
23
36
|
end
|
24
37
|
|
25
38
|
# Override the process name for the unicorn processes, both master and
|
@@ -49,23 +62,32 @@ class << Unicorn
|
|
49
62
|
# to enable programmers to debug and fix the issue.
|
50
63
|
attr_accessor :request_logger
|
51
64
|
|
52
|
-
# The
|
53
|
-
attr_accessor :
|
65
|
+
# The pledge string to use for the master process's spawned processes by default.
|
66
|
+
attr_accessor :master_execpledge
|
54
67
|
|
55
|
-
# The
|
56
|
-
|
57
|
-
attr_accessor :group_name
|
68
|
+
# The pledge string to use for the master process.
|
69
|
+
attr_accessor :master_pledge
|
58
70
|
|
59
|
-
# The pledge string to use.
|
71
|
+
# The pledge string to use for worker processes.
|
60
72
|
attr_accessor :pledge
|
61
73
|
|
62
|
-
# The
|
74
|
+
# The hash of unveil paths to use.
|
75
|
+
attr_accessor :unveil
|
76
|
+
|
77
|
+
# The hash of additional unveil paths to use if in the development environment.
|
78
|
+
attr_accessor :dev_unveil
|
79
|
+
|
80
|
+
# The address to email for crash and unhandled exception notifications.
|
63
81
|
attr_accessor :email
|
64
82
|
|
83
|
+
# The prefix for unicorn lockdown files
|
84
|
+
attr_reader :unicorn_lockdown_prefix
|
85
|
+
Unicorn.instance_variable_set(:@unicorn_lockdown_prefix, ENV['UNICORN_LOCKDOWN_PREFIX'] || '/var')
|
86
|
+
|
65
87
|
# Helper method to write request information to the request logger.
|
66
88
|
# +email_message+ should be an email message including headers and body.
|
67
89
|
# This should be called at the top of the Roda route block for the
|
68
|
-
# application.
|
90
|
+
# application (or at some early point before processing in other web frameworks).
|
69
91
|
def write_request(email_message)
|
70
92
|
request_logger.seek(0, IO::SEEK_SET)
|
71
93
|
request_logger.truncate(0)
|
@@ -73,28 +95,33 @@ class << Unicorn
|
|
73
95
|
request_logger.fsync
|
74
96
|
end
|
75
97
|
|
76
|
-
# Helper method that sets up all necessary code for
|
98
|
+
# Helper method that sets up all necessary code for unveil/pledge support.
|
77
99
|
# This should be called inside the appropriate unicorn.conf file.
|
78
100
|
# The configurator should be self in the top level scope of the
|
79
101
|
# unicorn.conf file, and this takes options:
|
80
102
|
#
|
81
103
|
# Options:
|
82
|
-
# :app :: The name of the application
|
104
|
+
# :app (required) :: The name of the application
|
83
105
|
# :email : The email to notify for worker crashes
|
84
|
-
# :
|
85
|
-
# :
|
86
|
-
#
|
87
|
-
#
|
88
|
-
#
|
106
|
+
# :pledge :: The string to use when pledging worker processes after loading the app
|
107
|
+
# :master_pledge :: The string to use when pledging the master process before
|
108
|
+
# spawning worker processes
|
109
|
+
# :master_execpledge :: The pledge string for processes spawned by the master
|
110
|
+
# process (i.e. worker processes before loading the app)
|
111
|
+
# :unveil :: A hash of unveil paths, passed to Pledge.unveil.
|
112
|
+
# :dev_unveil :: A hash of unveil paths to use in development, in addition
|
113
|
+
# to the ones in :unveil.
|
89
114
|
def lockdown(configurator, opts)
|
90
115
|
Unicorn.app_name = opts.fetch(:app)
|
91
|
-
Unicorn.user_name = opts.fetch(:user)
|
92
|
-
Unicorn.group_name = opts[:group] || opts[:user]
|
93
116
|
Unicorn.email = opts[:email]
|
117
|
+
Unicorn.master_pledge = opts[:master_pledge]
|
118
|
+
Unicorn.master_execpledge = opts[:master_execpledge]
|
94
119
|
Unicorn.pledge = opts[:pledge]
|
120
|
+
Unicorn.unveil = opts[:unveil]
|
121
|
+
Unicorn.dev_unveil = opts[:dev_unveil]
|
95
122
|
|
96
123
|
configurator.instance_exec do
|
97
|
-
listen "/
|
124
|
+
listen "#{Unicorn.unicorn_lockdown_prefix}/www/sockets/#{Unicorn.app_name}.sock"
|
98
125
|
|
99
126
|
# Buffer all client bodies in memory. This assumes an Nginx limit of 10MB,
|
100
127
|
# by using 11MB this ensures that client bodies are always buffered in
|
@@ -105,23 +132,25 @@ class << Unicorn
|
|
105
132
|
# Run all worker processes with unique memory layouts
|
106
133
|
worker_exec true
|
107
134
|
|
135
|
+
# :nocov:
|
108
136
|
# Only change the log path if daemonizing.
|
109
137
|
# Otherwise, continue to log to stdout/stderr.
|
110
138
|
if Unicorn::Configurator::RACKUP[:daemonize]
|
111
|
-
|
112
|
-
|
139
|
+
log_path = "#{Unicorn.unicorn_lockdown_prefix}/log/unicorn/#{Unicorn.app_name}.log"
|
140
|
+
stdout_path log_path
|
141
|
+
stderr_path log_path
|
113
142
|
end
|
143
|
+
# :nocov:
|
114
144
|
|
115
145
|
after_fork do |server, worker|
|
116
146
|
server.logger.info("worker=#{worker.nr} spawned pid=#{$$}")
|
117
147
|
|
118
|
-
# Set the request logger for the worker process after forking.
|
119
|
-
# process is still root here, so it can open the file in write mode.
|
148
|
+
# Set the request logger for the worker process after forking.
|
120
149
|
Unicorn.request_logger = File.open(server.request_filename($$), "wb")
|
121
150
|
Unicorn.request_logger.sync = true
|
122
151
|
end
|
123
152
|
|
124
|
-
if wrap_app = Unicorn.email
|
153
|
+
if wrap_app = Unicorn.email
|
125
154
|
require 'rack/email_exceptions'
|
126
155
|
end
|
127
156
|
|
@@ -137,55 +166,31 @@ class << Unicorn
|
|
137
166
|
end
|
138
167
|
end
|
139
168
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
# Start with rack, which uses autoload for all constants.
|
146
|
-
# Most of rack's constants are not used at runtime, this
|
147
|
-
# lists the ones most commonly needed.
|
148
|
-
Rack::Multipart
|
149
|
-
Rack::Multipart::Parser
|
150
|
-
Rack::Multipart::Generator
|
151
|
-
Rack::Multipart::UploadedFile
|
152
|
-
Rack::Mime
|
153
|
-
Rack::Auth::Digest::Params
|
154
|
-
|
155
|
-
# In the development environment, reference all middleware
|
156
|
-
# the unicorn will load by default, unless unicorn is
|
157
|
-
# set to not load middleware by default.
|
158
|
-
if ENV['RACK_ENV'] == 'development' && (!respond_to?(:set) || set[:default_middleware] != false)
|
159
|
-
Rack::ContentLength
|
160
|
-
Rack::CommonLogger
|
161
|
-
Rack::Chunked
|
162
|
-
Rack::Lint
|
163
|
-
Rack::ShowExceptions
|
164
|
-
Rack::TempfileReaper
|
169
|
+
unveil = if Unicorn.dev_unveil && ENV['RACK_ENV'] == 'development'
|
170
|
+
Unicorn.unveil.merge(Unicorn.dev_unveil)
|
171
|
+
else
|
172
|
+
Hash[Unicorn.unveil]
|
165
173
|
end
|
166
174
|
|
167
|
-
#
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
end
|
175
|
+
# Don't allow loading files in rack and mail gems if not using rubygems
|
176
|
+
if defined?(Gem) && Gem.respond_to?(:loaded_specs)
|
177
|
+
# Allow read access to the rack gem directory, as rack autoloads constants.
|
178
|
+
if defined?(Rack) && Gem.loaded_specs['rack']
|
179
|
+
unveil['rack'] = :gem
|
180
|
+
end
|
174
181
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
182
|
+
# If using the mail library, allow read access to the mail gem directory,
|
183
|
+
# as mail autoloads constants.
|
184
|
+
if defined?(Mail) && Gem.loaded_specs['mail']
|
185
|
+
unveil['mail'] = :gem
|
186
|
+
end
|
187
|
+
end
|
179
188
|
|
180
|
-
#
|
181
|
-
|
182
|
-
worker.user(Unicorn.user_name, Unicorn.group_name, pwd)
|
189
|
+
# Restrict access to the file system based on the specified unveil.
|
190
|
+
Pledge.unveil(unveil)
|
183
191
|
|
184
|
-
|
185
|
-
|
186
|
-
# privileges requires a separate pledge.
|
187
|
-
Pledge.pledge(Unicorn.pledge)
|
188
|
-
end
|
192
|
+
# Pledge after unveiling, because unveiling requires a separate pledge.
|
193
|
+
Pledge.pledge(Unicorn.pledge)
|
189
194
|
end
|
190
195
|
|
191
196
|
# the last time there was a worker crash and the request information
|
@@ -227,33 +232,16 @@ class << Unicorn
|
|
227
232
|
unless skip_email
|
228
233
|
# If the request filename exists and the worker process crashed,
|
229
234
|
# send a notification email.
|
230
|
-
Process.waitpid(fork do
|
231
|
-
# Load net/smtp early
|
235
|
+
Process.waitpid(Process.fork do
|
236
|
+
# Load net/smtp early
|
232
237
|
require 'net/smtp'
|
233
238
|
|
234
239
|
# When setting the email, first get the contents of the email
|
235
240
|
# from the request file.
|
236
241
|
body = File.read(file)
|
237
242
|
|
238
|
-
# Then get information from /etc and drop group privileges
|
239
|
-
uid = Etc.getpwnam(Unicorn.user_name).uid
|
240
|
-
group = Unicorn.group_name
|
241
|
-
group = group.first if group.is_a?(Array)
|
242
|
-
gid = Etc.getgrnam(group).gid
|
243
|
-
if gid && Process.egid != gid
|
244
|
-
Process.initgroups(Unicorn.user_name, gid)
|
245
|
-
Process::GID.change_privilege(gid)
|
246
|
-
end
|
247
|
-
|
248
|
-
# Then chroot
|
249
|
-
Dir.chroot(Dir.pwd)
|
250
|
-
Dir.chdir('/')
|
251
|
-
|
252
|
-
# Then drop user privileges
|
253
|
-
Process.euid != uid and Process::UID.change_privilege(uid)
|
254
|
-
|
255
243
|
# Then use a restrictive pledge
|
256
|
-
Pledge.pledge('inet prot_exec')
|
244
|
+
Pledge.pledge(ENV['UNICORN_LOCKDOWN_WORKER_CRASH_PLEDGE'] || 'inet prot_exec')
|
257
245
|
|
258
246
|
# If body empty, crash happened before a request was received,
|
259
247
|
# try to at least provide the application name in this case.
|
@@ -261,8 +249,13 @@ class << Unicorn
|
|
261
249
|
body = "Subject: [#{Unicorn.app_name}] Unicorn Worker Process Crash\r\n\r\nNo email content provided for app: #{Unicorn.app_name}"
|
262
250
|
end
|
263
251
|
|
252
|
+
# :nocov:
|
253
|
+
# Don't verify localhost hostname, to avoid SSL errors raised in newer versions of net/smtp
|
254
|
+
smtp_params = Net::SMTP.method(:start).parameters.include?([:key, :tls_verify]) ? {tls_verify: false, tls_hostname: 'localhost'} : {}
|
255
|
+
# :nocov:
|
256
|
+
|
264
257
|
# Finally send an email to localhost via SMTP.
|
265
|
-
Net::SMTP.start('127.0.0.1'){|s| s.send_message(body, Unicorn.email, Unicorn.email)}
|
258
|
+
Net::SMTP.start('127.0.0.1', **smtp_params){|s| s.send_message(body, Unicorn.email, Unicorn.email)}
|
266
259
|
end)
|
267
260
|
end
|
268
261
|
end
|
@@ -274,4 +267,3 @@ class << Unicorn
|
|
274
267
|
end
|
275
268
|
end
|
276
269
|
end
|
277
|
-
|
data/lib/unveiler.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'pledge'
|
2
|
+
require 'unveil'
|
3
|
+
|
4
|
+
# Load encodings
|
5
|
+
"\255".force_encoding('ISO8859-1').encode('UTF-8')
|
6
|
+
''.force_encoding('UTF-16LE')
|
7
|
+
''.force_encoding('UTF-16BE')
|
8
|
+
|
9
|
+
# Don't run external diff program for failures
|
10
|
+
Minitest::Assertions.diff = false if defined?(Minitest::Assertions)
|
11
|
+
|
12
|
+
# Unveiler allows for testing programs using pledge and unveil.
|
13
|
+
module Unveiler
|
14
|
+
# Use Pledge.unveil to limit access to the file system based on the
|
15
|
+
# +unveil+ argument. Then pledge the process with the given +pledge+
|
16
|
+
# permissions. This will automatically unveil the rack and mail gems
|
17
|
+
# if they are loaded.
|
18
|
+
def self.pledge_and_unveil(pledge, unveil)
|
19
|
+
unveil = Hash[unveil]
|
20
|
+
|
21
|
+
if defined?(Gem) && Gem.respond_to?(:loaded_specs)
|
22
|
+
if defined?(Rack) && Gem.loaded_specs['rack']
|
23
|
+
unveil['rack'] = :gem
|
24
|
+
end
|
25
|
+
if defined?(Mail) && Gem.loaded_specs['mail']
|
26
|
+
unveil['mail'] = :gem
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Pledge.unveil(unveil)
|
31
|
+
|
32
|
+
# :nocov:
|
33
|
+
if defined?(SimpleCov)
|
34
|
+
# :nocov:
|
35
|
+
# If running coverage tests, add necessary pledges for
|
36
|
+
# coverage testing to work.
|
37
|
+
pledge = (pledge.split + %w'rpath wpath cpath flock').uniq.join(' ')
|
38
|
+
end
|
39
|
+
|
40
|
+
Pledge.pledge(pledge)
|
41
|
+
end
|
42
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unicorn-lockdown
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-07-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pledge
|
@@ -38,6 +38,62 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rack
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: mail
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: roda
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: minitest-global_expectations
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
41
97
|
description:
|
42
98
|
email: code@jeremyevans.net
|
43
99
|
executables:
|
@@ -52,14 +108,20 @@ files:
|
|
52
108
|
- bin/unicorn-lockdown-add
|
53
109
|
- bin/unicorn-lockdown-setup
|
54
110
|
- files/rc.unicorn
|
55
|
-
-
|
111
|
+
- files/unicorn_lockdown_add.rb
|
112
|
+
- files/unicorn_lockdown_setup.rb
|
56
113
|
- lib/rack/email_exceptions.rb
|
57
114
|
- lib/roda/plugins/pg_disconnect.rb
|
58
115
|
- lib/unicorn-lockdown.rb
|
116
|
+
- lib/unveiler.rb
|
59
117
|
homepage: https://github.com/jeremyevans/unicorn-lockdown
|
60
118
|
licenses:
|
61
119
|
- MIT
|
62
|
-
metadata:
|
120
|
+
metadata:
|
121
|
+
bug_tracker_uri: https://github.com/jeremyevans/unicorn-lockdown/issues
|
122
|
+
changelog_uri: https://github.com/jeremyevans/unicorn-lockdown/blob/master/CHANGELOG
|
123
|
+
mailing_list_uri: https://github.com/jeremyevans/unicorn-lockdown/discussions
|
124
|
+
source_code_uri: https://github.com/jeremyevans/unicorn-lockdown
|
63
125
|
post_install_message:
|
64
126
|
rdoc_options: []
|
65
127
|
require_paths:
|
@@ -68,16 +130,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
68
130
|
requirements:
|
69
131
|
- - ">="
|
70
132
|
- !ruby/object:Gem::Version
|
71
|
-
version:
|
133
|
+
version: 2.0.0
|
72
134
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
135
|
requirements:
|
74
136
|
- - ">="
|
75
137
|
- !ruby/object:Gem::Version
|
76
138
|
version: '0'
|
77
139
|
requirements: []
|
78
|
-
rubygems_version: 3.
|
140
|
+
rubygems_version: 3.3.7
|
79
141
|
signing_key:
|
80
142
|
specification_version: 4
|
81
|
-
summary: Helper library for running Unicorn with
|
82
|
-
on OpenBSD
|
143
|
+
summary: Helper library for running Unicorn with fork+exec/unveil/pledge on OpenBSD
|
83
144
|
test_files: []
|
data/lib/chrooter.rb
DELETED
@@ -1,110 +0,0 @@
|
|
1
|
-
require 'etc'
|
2
|
-
require 'pledge'
|
3
|
-
|
4
|
-
# Loading single_byte encoding
|
5
|
-
"\255".force_encoding('ISO8859-1').encode('UTF-8')
|
6
|
-
|
7
|
-
# Load encodings
|
8
|
-
''.force_encoding('UTF-16LE')
|
9
|
-
''.force_encoding('UTF-16BE')
|
10
|
-
|
11
|
-
# Don't run external diff program for failures
|
12
|
-
Minitest::Assertions.diff = false
|
13
|
-
|
14
|
-
if defined?(SimpleCov) && Process.euid == 0
|
15
|
-
# Prevent coverage testing in chroot mode. Chroot vs
|
16
|
-
# non-chroot should not matter in terms of lines covered,
|
17
|
-
# and running coverage tests in chroot mode will probable fail
|
18
|
-
# when it comes time to write the coverage files.
|
19
|
-
SimpleCov.at_exit{}
|
20
|
-
raise "cannot run coverage testing in chroot mode"
|
21
|
-
end
|
22
|
-
|
23
|
-
# Chrooter allows for testing programs in both chroot and non
|
24
|
-
# chroot modes.
|
25
|
-
module Chrooter
|
26
|
-
# If the current user is the super user, change to the given
|
27
|
-
# user/group, chroot to the given directory, and pledge
|
28
|
-
# the process with the given permissions (if given).
|
29
|
-
#
|
30
|
-
# If the current user is not the super user, freeze
|
31
|
-
# $LOADED_FEATURES to more easily detect problems with
|
32
|
-
# autoloaded constants, and just pledge the process with the
|
33
|
-
# given permissions (if given).
|
34
|
-
#
|
35
|
-
# This will reference common autoloaded constants in the
|
36
|
-
# rack and mail libraries if they are defined. Other
|
37
|
-
# autoloaded constants should be referenced before calling
|
38
|
-
# this method.
|
39
|
-
#
|
40
|
-
# In general this should be called inside an at_exit block
|
41
|
-
# after loading minitest/autorun, so it will run after all
|
42
|
-
# specs are loaded, but before running specs.
|
43
|
-
def self.chroot(user, pledge=nil, group=user, dir=Dir.pwd)
|
44
|
-
# Work around autoload issues in libraries.
|
45
|
-
# autoload is problematic when chrooting because if the
|
46
|
-
# constant is not referenced before chrooting, an
|
47
|
-
# exception is raised if the constant is raised
|
48
|
-
# after chrooting.
|
49
|
-
#
|
50
|
-
# The constants listed here are the autoloaded constants
|
51
|
-
# known to be used by any applications. This list
|
52
|
-
# may need to be updated when libraries are upgraded
|
53
|
-
# and add new constants, or when applications start
|
54
|
-
# using new features.
|
55
|
-
if defined?(Rack)
|
56
|
-
Rack::MockRequest if defined?(Rack::MockRequest)
|
57
|
-
Rack::Auth::Digest::Params if defined?(Rack::Auth::Digest::Params)
|
58
|
-
if defined?(Rack::Multipart)
|
59
|
-
Rack::Multipart
|
60
|
-
Rack::Multipart::Parser
|
61
|
-
Rack::Multipart::Generator
|
62
|
-
Rack::Multipart::UploadedFile
|
63
|
-
end
|
64
|
-
end
|
65
|
-
if defined?(Mail)
|
66
|
-
Mail::Address
|
67
|
-
Mail::AddressList
|
68
|
-
Mail::Parsers::AddressListsParser
|
69
|
-
Mail::ContentTransferEncodingElement
|
70
|
-
Mail::ContentDispositionElement
|
71
|
-
Mail::MessageIdsElement
|
72
|
-
Mail::MimeVersionElement
|
73
|
-
Mail::OptionalField
|
74
|
-
Mail::ContentTypeElement
|
75
|
-
end
|
76
|
-
|
77
|
-
if Process.euid == 0
|
78
|
-
uid = Etc.getpwnam(user).uid
|
79
|
-
gid = Etc.getgrnam(group).gid
|
80
|
-
if Process.egid != gid
|
81
|
-
Process.initgroups(user, gid)
|
82
|
-
Process::GID.change_privilege(gid)
|
83
|
-
end
|
84
|
-
Dir.chroot(dir)
|
85
|
-
Dir.chdir('/')
|
86
|
-
Process.euid != uid and Process::UID.change_privilege(uid)
|
87
|
-
puts "Chrooted to #{dir}, running as user #{user}"
|
88
|
-
else
|
89
|
-
# Load minitest plugins before freezing loaded features,
|
90
|
-
# so they don't break.
|
91
|
-
Minitest.load_plugins
|
92
|
-
|
93
|
-
# Emulate chroot not working by freezing $LOADED_FEATURES
|
94
|
-
# This allows to more easily catch bugs that only occur
|
95
|
-
# when chrooted, such as referencing an autoloaded constant
|
96
|
-
# that wasn't loaded before the chroot.
|
97
|
-
$LOADED_FEATURES.freeze
|
98
|
-
end
|
99
|
-
|
100
|
-
unless defined?(SimpleCov)
|
101
|
-
if pledge
|
102
|
-
# If running coverage tests, don't run pledged as coverage
|
103
|
-
# testing can require many additional permissions.
|
104
|
-
Pledge.pledge(pledge)
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
nil
|
109
|
-
end
|
110
|
-
end
|