unicorn-lockdown 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e3ce59cddf9d01567fd8a064530c8d1cc54e45a96eecb54fbacfc376ed73c030
4
- data.tar.gz: 7e52c5cc4e6ed7673e84fdce2b67516284a49409d1e5a5adcc0a489a401f52e6
3
+ metadata.gz: 931209c4514dbb9f15da327fac6ed81f82fed2a504c500a34aa5f6e2624cde5a
4
+ data.tar.gz: 160c9cf285e6c4f8abeb38bce3d1b7006f236607e6f18e606fdcbd8b53f73baa
5
5
  SHA512:
6
- metadata.gz: 42147643aa1eb541874fc61ef91152800617b91fb69e57f3765d0ea33f372f787a1fa9074eb9eb74400fe2f4ec4f699213875a0ec9446ac08ae6b8f1baad3e08
7
- data.tar.gz: c74fb916793633f0da277b1541cce8d9773a985035b0f36bfef9f545abf28e1c8f38796d0fd18da5525ab49ff2a46e4be428cc25384461e53c8615095b9a1ec9
6
+ metadata.gz: 9f457aefb1571441815165ef9fb032f2de71547debfa81d9a7d4a441019e0671e00258b77bdb4fdca29dca677a5b5e795c65a7f53483cbf67bd12ef2950e4545
7
+ data.tar.gz: de3ee5209b1cb08dd0918448689868cc7b5c423ec07c929df7a4475d222436b32ea271f972f789a7ee13ffcffc8a7bbafd78950741c40392d49b8c69af69473e
@@ -0,0 +1,39 @@
1
+ = 1.0.0 (2020-11-09)
2
+
3
+ * Require unicorn-lockdown-add -o and -u options, and require options have arguments (jeremyevans)
4
+
5
+ * Switch to starting unicorn master process as application user, drop chroot support, require unveil (jeremyevans)
6
+
7
+ * Remove chrooter library (jeremyevans)
8
+
9
+ * Add unveiler library for testing pledged/unveiled applications, similar to chrooter but smaller (jeremyevans)
10
+
11
+ * Add :master_execpledge option to Unicorn.lockdown, for initial pledge of worker processes (jeremyevans)
12
+
13
+ * Add :master_pledge option to Unicorn.lockdown, for pledging the master process (jeremyevans)
14
+
15
+ = 0.13.0 (2019-07-09)
16
+
17
+ * Add Chrooter.unveil for using unveil in tests (jeremyevans)
18
+
19
+ * Support Unicorn.lockdown :unveil and :dev_unveil options for use of unveil instead of chroot (jeremyevans)
20
+
21
+ = 0.12.0 (2019-04-29)
22
+
23
+ * Do not reference the rack middleware unicorn loads by default if unicorn is set to not load default middleware (jeremyevans)
24
+
25
+ = 0.11.0 (2019-03-18)
26
+
27
+ * Support separate log group and process primary group on Unicorn 5.5.0+ using :group option (jeremyevans)
28
+
29
+ * Make Roda pg_disconnect plugin support new Roda dispatch API (jeremyevans)
30
+
31
+ = 0.10.0 (2018-05-21)
32
+
33
+ * Use Mail.eager_autoload! if using the mail gem (jeremyevans)
34
+
35
+ * Add bin files to gemspec (jeremyevans)
36
+
37
+ = 0.9.0 (2018-05-02)
38
+
39
+ * Initial public release
@@ -1,4 +1,4 @@
1
- Copyright (c) 2018 Jeremy Evans
1
+ Copyright (c) 2018-2020 Jeremy Evans
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to
@@ -1,22 +1,20 @@
1
1
  = unicorn-lockdown
2
2
 
3
- unicorn-lockdown is a helper library for running Unicorn on OpenBSD in a way
4
- that supports security features such as chroot, privdrop, fork+exec,
5
- and pledge.
6
-
7
- With the configuration unicorn-lockdown uses, the unicorn process executes as root,
8
- and the unicorn master process continues to run as root. The master process
9
- forks worker processes, which re-exec (fork+exec) so that a new memory layout
10
- is used in each worker process. The work process then loads the application,
11
- after which it chroot's to the application's directory, drops root privileges
12
- and then runs as the application user (privdrop), then runs pledge to limit
13
- the allowed system calls to the minimum required to run the application.
3
+ unicorn-lockdown is a helper library for running Unicorn on OpenBSD with pledge,
4
+ unveil, and fork+exec for increased security.
5
+
6
+ With unicorn-lockdown, unicorn should be started as the application user, which
7
+ should be different than the user that owns the application's files. unicorn
8
+ will pledge the master process, then fork worker processes. The worker
9
+ processes will re-exec (fork+exec), then load the application, then set unveil
10
+ to restrict file system access, then pledge to limit the allowed system calls
11
+ at runtime.
14
12
 
15
13
  == Assumptions
16
14
 
17
- unicorn-lockdown assumes you are using OpenBSD 6.2+ with the nginx and
18
- rubyXY-unicorn packages installed, and that you have a unicorn symlink in
19
- the PATH to the appropriate unicornXY executable.
15
+ unicorn-lockdown assumes you are using OpenBSD 6.6+ with the nginx and
16
+ <tt>rubyXY-unicorn</tt> and <tt>rubyXY-pledge</tt> packages installed, and that
17
+ you have a +unicorn+ symlink in the PATH to the appropriate +unicornXY+ executable.
20
18
 
21
19
  It also assumes you have a SMTP server listening on localhost port 25 to
22
20
  receive notification emails of worker crashes, if you are notifying for those.
@@ -33,14 +31,14 @@ the following as root after reading the file and understanding what it does.
33
31
  Briefly, the configuration this uses the following directories:
34
32
 
35
33
  /var/www/sockets :: Stores unix sockets that Unicorn listens on and Nginx uses.
36
- /var/www/requests :: Stores temporary files for each request with request info,
37
- used for crash notifications
34
+ /var/www/request-error-info :: Stores temporary files for each request with request info,
35
+ used for crash notifications
38
36
  /var/log/unicorn :: Stores unicorn log files, one per application
39
37
  /var/log/nginx :: Stores nginx log files, two per application, one for access
40
38
  and one for errors
41
39
 
42
- This adds a _unicorn group that all per-application users will use as their
43
- group, as well as a /etc/rc.d/rc.unicorn file that the per application
40
+ This adds a _unicorn group that all application users will use as one of their
41
+ groups, as well as a /etc/rc.d/rc.unicorn file that the application
44
42
  /etc/rc.d/unicorn_* files will use.
45
43
 
46
44
  === unicorn-lockdown-add
@@ -48,30 +46,38 @@ group, as well as a /etc/rc.d/rc.unicorn file that the per application
48
46
  For each application you want to run with unicorn lockdown, run the following
49
47
  as root, again after reading the file and understanding what it does:
50
48
 
51
- unicorn-lockdown-add
49
+ unicorn-lockdown-add -o $owner -u $user $app_name
52
50
 
53
- Here's an excerpt of the usage:
51
+ Here's the usage:
54
52
 
55
- Usage: unicorn-lockdown-add [options] app_name
53
+ Usage: unicorn-lockdown-add -o owner -u user [options] app_name
56
54
  Options:
57
- -c rackup_file rackup configuration file
58
- -d dir application directory name
59
- -f unicorn_file unicorn configuration file relative to application directory
60
- -o owner operating system application owner
61
- -u user operating system user to run application
62
- --uid uid user id to use if creating the user when -U is specified
63
-
64
- It is a very good idea to specify -o and -u, the other options can be ignored
65
- if you are OK with the default values. The owner (-o) and the user (-u) should be
66
- different. The user is the user the application runs as, and should have very limited
67
- access to the application directory. The owner is the user that owns the application
68
- directory and can make modifications to the application.
55
+ -c RACKUP_FILE rackup configuration file
56
+ -d DIR application directory name
57
+ -f UNICORN_FILE unicorn configuration file relative to application directory
58
+ -o OWNER operating system application owner
59
+ -u USER operating system user to run application
60
+ --uid UID user id to use if creating the user when -U is specified
61
+ -h, -?, --help Show this message
62
+
63
+ The <tt>-o</tt> and <tt>-u</tt> options are required. Default values for other options are:
64
+
65
+ <tt>-c</tt> :: None, Unicorn will use <tt>config.ru</tt> by default.
66
+ <tt>-d</tt> :: Same as +app_name+. The value provided should a relative path under <tt>/var/www</tt>.
67
+ <tt>-f</tt> :: <tt>unicorn.conf</tt>. This file should be relative to +dir+.
68
+ <tt>--uid</tt> :: The uid automatically generated by +useradd+.
69
+
70
+ The owner <tt>-o</tt> and the user <tt>-u</tt> should be different. The user is the user the
71
+ application runs as, and should have read-only access to the application directory,
72
+ other than locations where you want the application user to be able to modify files
73
+ at runtime. The owner is the user that owns the application directory and can make
74
+ modifications to the application.
69
75
 
70
76
  === unicorn-lockdown
71
77
 
72
78
  unicorn-lockdown is the library required in your unicorn configuration
73
79
  file for the application, to handle configuring unicorn to run the app
74
- with chroot, privdrop, fork+exec, and pledge.
80
+ with fork+exec, unveil, and pledge.
75
81
 
76
82
  When you run unicorn-lockdown-add, it will create the unicorn configuration
77
83
  file for the app if one does not already exist, looking similar to:
@@ -80,37 +86,75 @@ file for the app if one does not already exist, looking similar to:
80
86
 
81
87
  Unicorn.lockdown(self,
82
88
  :app=>"app_name",
83
- :user=>"user_name", # Set application user here
84
- :pledge=>'rpath prot_exec inet unix' # More may be needed
85
- :email=>'root' # update this with correct email
89
+
90
+ # Update this with correct email
91
+ :email=>'root',
92
+
93
+ # More pledges may be needed depending on application
94
+ :pledge=>'rpath prot_exec inet unix',
95
+ :master_pledge=>'rpath prot_exec cpath wpath inet proc exec',
96
+ :master_execpledge=>'stdio rpath prot_exec inet unix cpath wpath unveil',
97
+
98
+ # More unveils may be needed depending on application
99
+ :unveil=>{
100
+ 'views'=>'r'
101
+ },
102
+ :dev_unveil=>{
103
+ 'models'=>'r'
104
+ },
86
105
  )
87
106
 
88
107
  Unicorn.lockdown options:
89
108
 
90
109
  :app :: (required) a short string for the name of the application, used
91
110
  for socket/log file names and in notifications
92
- :user :: (required) the user to drop privileges to
93
- :pledge :: (optional) a pledge string to limit the allowed system calls
94
- after privileges have been dropped
95
111
  :email :: (optional) an email address to use for notifications when the
96
112
  worker process crashes or an unhandled exception is raised by
97
113
  the application or middleware.
114
+ :pledge :: (optional) a pledge string to limit the allowed system calls
115
+ after privileges have been dropped
116
+ :master_pledge :: (optional) The string to use when pledging the master process before
117
+ spawning worker processes
118
+ :master_execpledge :: (optional) The pledge string for processes spawned by the master
119
+ process (i.e. worker processes before loading the app)
120
+ :unveil :: (required) a hash of paths to limit file system access, passed
121
+ to +Pledge.unveil+.
122
+ :dev_unveil :: (optional) a hash of paths to limit file system, merged into the :unveil
123
+ option paths if in the development environment. Useful if you are
124
+ allowing more access in development, such as access needed
125
+ for file reloading.
98
126
 
99
127
  With this example pledge:
100
128
 
101
- * rpath is needed to read files in the chrooted file system
129
+ * rpath is needed to read files
102
130
  * prot_exec is needed in most cases
103
131
  * unix is needed for the unix socket to nginx
104
- * inet is not needed in all cases, but in most applications need some form
105
- of network access. pf (OpenBSD's firewall) should be used to limit
106
- access for the application's operating system user to the minimum
107
- necessary access needed.
108
-
109
- unicorn-lockdown has specific support for allowing for emails to be sent
110
- for Unicorn worker crashes (e.g. pledge violations). It also has support
111
- for using rack-unreloader to run your application in development mode
112
- under the chroot while allowing for reloading files if they are modified.
113
- Additionally, unicorn-lockdown modifies unicorn's process status line in a
132
+ * inet is not needed in all cases, but most applications need some form
133
+ of network access, and it is needed by default for emailing about
134
+ exceptions that occur without process crashes. pf (OpenBSD's firewall)
135
+ should be used to limit access for the application's operating system
136
+ user to the minimum necessary access needed.
137
+
138
+ With this example master pledge:
139
+
140
+ * rpath is needed to read files
141
+ * prot_exec is needed in most cases
142
+ * cpath and wpath are needed to unlink the request error files
143
+ * inet is needed to send emails for worker crashes
144
+ * proc and exec are needed to spawn worker processes
145
+
146
+ With this examle master exec pledge:
147
+
148
+ * stdio must be added because ruby-pledge doesn't add it automatically
149
+ to execpromises, and Ruby requires it
150
+ * rpath, prot_exec, unix, inet are needed for the worker (see above)
151
+ * cpath and wpath are needed to create the request error files
152
+ * unveil is needed to restrict file system access
153
+
154
+ unicorn-lockdown has specific support for allowing emails to be sent
155
+ for Unicorn worker crashes (e.g. pledge violations) and unhandled
156
+ application exceptions (e.g. pledge violations). Additionally,
157
+ unicorn-lockdown modifies unicorn's process status line in a
114
158
  way that allows it to be controllable via OpenBSD's rcctl program for
115
159
  stopping/starting/reloading/restarting daemons.
116
160
 
@@ -119,7 +163,12 @@ with the expectation of an Nginx limit of 10MB, such that all client
119
163
  requests will be buffered in memory and unicorn will not need to write
120
164
  temporary files to disk. If this limit is not correct for your
121
165
  application, please call client_body_buffer_size after calling
122
- Unicorn.lockdown to set an appropriate limit.
166
+ Unicorn.lockdown to set an appropriate limit. Note that rack still
167
+ creates temporary files for file uploads by default, you'll need to
168
+ configure rack to disallow file uploads if your application does not
169
+ need to accept uploaded files and you don't want file upload attempts
170
+ to cause pledge violations. With Roda, you can use the
171
+ disallow_file_uploads plugin to prevent file upload attempts.
123
172
 
124
173
  When Unicorn.lockdown is used with the :email option, if the worker
125
174
  process crashes, it will email the address using the contents specified
@@ -138,6 +187,8 @@ and then at the top of the route block, do:
138
187
 
139
188
  if defined?(Unicorn) && Unicorn.respond_to?(:write_request)
140
189
  Unicorn.write_request(error_email_content("Unicorn Worker Process Crash"))
190
+ # or
191
+ Unicorn.write_request(error_mail_content("Unicorn Worker Process Crash"))
141
192
  end
142
193
 
143
194
  If you don't have useful information in the request file, an email will
@@ -149,10 +200,20 @@ underlying problem.
149
200
 
150
201
  If you are using PostgreSQL as the database for the application, and using
151
202
  unix sockets to connect the application to the database, if the database
152
- is restarted, the application will no longer be able to connect to it. The
153
- only way to fix this is to kill the worker process and have the master
154
- process spawn a new worker. The roda-pg_disconnect plugin is a plugin
155
- for the roda web toolkit to kill the worker if it detects the database
203
+ is restarted, the application will no longer be able to connect to it unless
204
+ you unveil the path the database socket (stored in /tmp by default). It can
205
+ be a better approach security wise not to allow this, to prevent the
206
+ application from being able to establish new database connections with
207
+ potentially different credentials, as a mitigation in case the server is
208
+ compromised.
209
+
210
+ To allow the application to handle cases where the database is disconnected,
211
+ such as due to a restart of PostgreSQL, you can kill the worker process if
212
+ a disconnect error is detected, and have the master process then spawn a new
213
+ worker.
214
+
215
+ The roda-pg_disconnect plugin is a plugin for the roda web toolkit to kill the
216
+ worker process after handling the connection if it detects the database
156
217
  connection has been lost. This plugin assumes the use of the Sequel database
157
218
  library and postgres adapter with the pg driver.
158
219
 
@@ -161,51 +222,63 @@ In your Roda application:
161
222
  # Sometime before loading the error_handler plugin
162
223
  plugin :pg_disconnect
163
224
 
225
+ To specifically restrict access to the database socket even when access to
226
+ /tmp is allowed, you can unveil the database socket path with no permissions:
227
+
228
+ '/tmp/.s.PGSQL.5432'=>''
229
+
230
+ Note that there are potentially other security issues with unveiling access
231
+ to /tmp beyond granting access to the database server, so it is recommended
232
+ you do not unveil it. If the application needs a directory for temporary
233
+ files (e.g. for handling uploaded files with rack), you can set the +TMPDIR+
234
+ environment variable to an appropriate directory that is writable by the
235
+ application user and not other users, and most web applications will respect
236
+ that (assuming they use the tmpfile/tmpdir libraries in the standard library).
237
+
164
238
  === rack-email_exceptions
165
239
 
166
- rack-email_exceptions is a rack middleware designed to be the first
167
- middleware loaded into applications. It rescues unhandled exceptions
240
+ rack-email_exceptions is a rack middleware designed to wrap all other
241
+ middleware and the application. It rescues unhandled exceptions
168
242
  raised by subsequent middleware or the application itself.
169
-
170
243
  Unicorn.lockdown will automatically setup this middleware if the :email
171
- option is used and the RACK_ENV environment variable is set to production,
172
- such that it wraps the application and all other middleware.
244
+ option is used.
173
245
 
174
246
  It is possible to use this middleware manually:
175
247
 
176
248
  require 'rack/email_exceptions'
177
249
  use Rack::EmailExceptions, "app_name", 'foo@example.com'
178
250
 
179
- === chrooter
251
+ === unveiler
180
252
 
181
- chrooter is a library designed to help with testing applications both in
182
- chroot mode and non-chroot mode. If you are running your application
183
- chrooted, you must support testing while chrooted otherwise it is very
184
- difficult to find problems that only occur when chrooted before putting
185
- the application into production, such as a file being read from outside
186
- the chroot.
253
+ unveiler is a library designed to help with testing applications that
254
+ use pledge and unveil. If you are running your application pledged and
255
+ unveiled, you want your tests to run pledged and unveiled to find
256
+ problems.
187
257
 
188
- chrooter assumes you are using minitest for testing. To use chrooter:
258
+ unveiler assumes you are using minitest for testing. To use unveiler:
189
259
 
190
260
  require 'minitest/autorun'
191
- require 'chrooter'
192
- at_exit{Chrooter.chroot('user_name', 'rpath prot_exec inet unix')}
193
-
194
- If you run your specs as a regular user, it will execute them without
195
- chrooting, but in a way that can still catch some problems that occur
196
- when chrooted. If you run your specs as root, it will chroot to
197
- the current directory after loading the specs, then drop
198
- privileges to the user given (and optionally pledging using the given
199
- pledge string), then run the specs.
261
+ require 'unveiler'
262
+ at_exit do
263
+ Unveiler.pledge_and_unveil('rpath prot_exec inet unix', 'views' => 'r')
264
+ end
200
265
 
201
266
  == autoload
202
267
 
203
- As you'll find out if you try to run your applications with chroot,
204
- autoload is the enemy. Both unicorn-lockdown and chrooter have support
205
- for handling common autoloaded constants in the rack and mail gems.
206
- If you use other gems that use autoload, you'll have to add code that
207
- references the autoloaded constants after the application is loaded but
208
- before chrooting.
268
+ As you'll find out if you try to run your applications with unveil,
269
+ autoload and other forms of runtime requires are the enemy. Both
270
+ unicorn-lockdown and unveiler have support for handling common autoloaded
271
+ constants in the rack and mail gems. If you use other gems that use
272
+ autoload or runtime requires, you'll have to add unveils for the appropriate
273
+ gems:
274
+
275
+ Unicorn.lockdown(self,
276
+ # ...
277
+ :unveil=>{
278
+ 'views' => 'r',
279
+ 'gem-name' => :gem,
280
+ }
281
+ )
209
282
 
210
283
  == Author
211
284
 
@@ -0,0 +1,222 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'etc'
4
+ require 'optparse'
5
+
6
+ def sh(*args)
7
+ puts "Running: #{args.join(' ')}"
8
+ system(*args) || raise("Error while running: #{args.join(' ')}")
9
+ end
10
+
11
+ unicorn = ''
12
+ rackup = ''
13
+ unicorn_file = 'unicorn.conf'
14
+ dir = nil
15
+ user = nil
16
+ new_user_uid = nil
17
+ owner = nil
18
+ owner_uid = nil
19
+ owner_gid = nil
20
+
21
+ options = OptionParser.new do |opts|
22
+ opts.banner = "Usage: unicorn-lockdown-add -o owner -u user [options] app_name"
23
+ opts.separator "Options:"
24
+
25
+ opts.on_tail("-h", "-?", "--help", "Show this message") do
26
+ puts opts
27
+ exit
28
+ end
29
+
30
+ opts.on("-c RACKUP_FILE", "rackup configuration file") do |v|
31
+ rackup = "rackup_file=#{v}\n"
32
+ end
33
+
34
+ opts.on("-d DIR", "application directory name") do |v|
35
+ dir = v
36
+ end
37
+
38
+ opts.on("-f UNICORN_FILE", "unicorn configuration file relative to application directory") do |v|
39
+ unicorn_file = v
40
+ unicorn = "unicorn_conf=#{v}\n"
41
+ end
42
+
43
+ opts.on("-o OWNER", "operating system application owner") do |v|
44
+ owner = v
45
+ ent = Etc.getpwnam(v)
46
+ owner_uid = ent.uid
47
+ owner_gid = ent.gid
48
+ end
49
+
50
+ opts.on("-u USER", "operating system user to run application") do |v|
51
+ user = v
52
+ end
53
+
54
+ opts.on("--uid UID", "user id to use if creating the user when -U is specified") do |v|
55
+ new_user_uid = Integer(v, 10)
56
+ end
57
+ end
58
+ options.parse!
59
+
60
+ unless user && owner
61
+ $stderr.puts "Must pass -o and -u options when calling unicorn_lockdown_add"
62
+ puts options
63
+ exit(1)
64
+ end
65
+
66
+ app = ARGV.shift
67
+ dir ||= app
68
+
69
+ root_id = 0
70
+ bin_id = 7
71
+ www_id = 67
72
+
73
+ www_root = '/var/www'
74
+ dir = "#{www_root}/#{dir}"
75
+ rc_file = "/etc/rc.d/unicorn_#{app.tr('-', '_')}"
76
+ nginx_file = "/etc/nginx/#{app}.conf"
77
+ unicorn_conf_file = "#{dir}/#{unicorn_file}"
78
+ nonroot_dir = "/var/www/request-error-data/#{app}"
79
+ unicorn_log_file = "/var/log/unicorn/#{app}.log"
80
+ nginx_access_log_file = "/var/log/nginx/#{app}.access.log"
81
+ nginx_error_log_file = "/var/log/nginx/#{app}.error.log"
82
+
83
+ # Add application user if it doesn't exist
84
+ passwd = begin
85
+ Etc.getpwnam(user)
86
+ rescue ArgumentError
87
+ args = ['/usr/sbin/useradd', '-d', '/var/empty', '-g', '=uid', '-G', '_unicorn', '-L', 'daemon', '-s', '/sbin/nologin']
88
+ if new_user_uid
89
+ args << '-u' << new_user_uid.to_s
90
+ end
91
+ args << user
92
+ sh(*args)
93
+ Etc.getpwnam(user)
94
+ end
95
+ app_uid = passwd.uid
96
+
97
+ # Create the subdirectory used for request error info when not running as root
98
+ unless File.directory?(nonroot_dir)
99
+ puts "Creating #{nonroot_dir}"
100
+ Dir.mkdir(nonroot_dir)
101
+ File.chmod(0700, nonroot_dir)
102
+ File.chown(app_uid, app_uid, nonroot_dir)
103
+ end
104
+
105
+ # Create application public directory if it doesn't exist (needed by nginx)
106
+ dirs = [dir, "#{dir}/public"]
107
+ dirs.each do |d|
108
+ unless File.directory?(d)
109
+ puts "Creating #{d}"
110
+ Dir.mkdir(d)
111
+ File.chmod(0755, d)
112
+ File.chown(owner_uid, owner_gid, d)
113
+ end
114
+ end
115
+
116
+ # DRY up file ownership code
117
+ setup_file_owner = lambda do |file|
118
+ File.chmod(0644, file)
119
+ File.chown(owner_uid, owner_gid, file)
120
+ end
121
+
122
+ # Setup unicorn configuration file
123
+ unless File.file?(unicorn_conf_file)
124
+ unicorn_conf_dir = File.dirname(unicorn_conf_file)
125
+ unless File.directory?(unicorn_conf_dir)
126
+ puts "Creating #{unicorn_conf_dir}"
127
+ Dir.mkdir(unicorn_conf_dir)
128
+ File.chmod(0755, unicorn_conf_dir)
129
+ File.chown(owner_uid, owner_gid, unicorn_conf_dir)
130
+ end
131
+ puts "Creating #{unicorn_conf_file}"
132
+ File.binwrite(unicorn_conf_file, <<END)
133
+ require 'unicorn-lockdown'
134
+
135
+ Unicorn.lockdown(self,
136
+ :app=>#{app.inspect},
137
+
138
+ # Update this with correct email
139
+ :email=>'root',
140
+
141
+ # More pledges may be needed depending on application
142
+ :pledge=>'rpath prot_exec inet unix',
143
+ :master_pledge=>'rpath prot_exec cpath wpath inet proc exec',
144
+ :master_execpledge=>'stdio rpath prot_exec inet unix cpath wpath unveil',
145
+
146
+ # More unveils may be needed depending on application
147
+ :unveil=>{
148
+ 'views'=>'r'
149
+ },
150
+ :dev_unveil=>{
151
+ 'models'=>'r'
152
+ },
153
+ )
154
+ END
155
+ setup_file_owner.call(unicorn_conf_file)
156
+ end
157
+
158
+ # Setup /etc/nginx/* file for nginx configuration
159
+ unless File.file?(nginx_file)
160
+ puts "Creating #{nginx_file}"
161
+ File.binwrite(nginx_file, <<END)
162
+ upstream #{app}_unicorn {
163
+ server unix:/sockets/#{app}.sock fail_timeout=0;
164
+ }
165
+ server {
166
+ server_name #{app};
167
+ access_log #{nginx_access_log_file} main;
168
+ error_log #{nginx_error_log_file} warn;
169
+ root #{dir}/public;
170
+ error_page 500 503 /500.html;
171
+ error_page 502 504 /502.html;
172
+ proxy_set_header X-Real-IP $remote_addr;
173
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
174
+ proxy_set_header Host $http_host;
175
+ proxy_redirect off;
176
+ add_header X-Content-Type-Options nosniff;
177
+ add_header X-Frame-Options deny;
178
+ add_header X-XSS-Protection "1; mode=block";
179
+ try_files $uri @#{app}_unicorn;
180
+ location @#{app}_unicorn {
181
+ proxy_pass http://#{app}_unicorn;
182
+ }
183
+ }
184
+ END
185
+
186
+ setup_file_owner.call(nginx_file)
187
+ end
188
+
189
+ # Setup nginx log file
190
+ [nginx_access_log_file, nginx_error_log_file].each do |f|
191
+ unless File.file?(f)
192
+ puts "Creating #{f}"
193
+ File.binwrite(f, '')
194
+ File.chmod(0644, f)
195
+ File.chown(www_id, root_id, f)
196
+ end
197
+ end
198
+
199
+ # Setup unicorn log file
200
+ unless File.file?(unicorn_log_file)
201
+ puts "Creating #{unicorn_log_file}"
202
+ File.binwrite(unicorn_log_file, '')
203
+ File.chmod(0640, unicorn_log_file)
204
+ File.chown(app_uid, Etc.getgrnam('_unicorn').gid, unicorn_log_file) if app_uid
205
+ end
206
+
207
+ # Setup /etc/rc.d/unicorn_* file for daemon management
208
+ unless File.file?(rc_file)
209
+ puts "Creating #{rc_file}"
210
+ File.binwrite(rc_file, <<END)
211
+ #!/bin/ksh
212
+
213
+ daemon_user=#{user}
214
+ unicorn_app=#{app}
215
+ unicorn_dir=#{dir}
216
+ #{unicorn}#{rackup}
217
+ . /etc/rc.d/rc.unicorn
218
+ END
219
+
220
+ File.chmod(0755, rc_file)
221
+ File.chown(root_id, bin_id, rc_file)
222
+ end
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'etc'
4
+
5
+ def sh(*args)
6
+ puts "Running: #{args.join(' ')}"
7
+ system(*args) || raise("Error while running: #{args.join(' ')}")
8
+ end
9
+
10
+ request_dir = '/var/www/request-error-data'
11
+ socket_dir = '/var/www/sockets'
12
+ unicorn_log_dir = '/var/log/unicorn'
13
+ nginx_log_dir = "/var/log/nginx"
14
+ rc_unicorn_file = '/etc/rc.d/rc.unicorn'
15
+
16
+ unicorn_group = '_unicorn'
17
+ root_id = 0
18
+ daemon_id = 1
19
+ www_id = 67
20
+
21
+ # Add _unicorn group if it doesn't exist
22
+ group = begin
23
+ Etc.getgrnam(unicorn_group)
24
+ rescue ArgumentError
25
+ sh('groupadd', unicorn_group)
26
+ Etc.getgrnam(unicorn_group)
27
+ end
28
+ unicorn_group_id = group.gid
29
+
30
+ # Setup requests directory to hold per-request information for crash notifications
31
+ unless File.directory?(request_dir)
32
+ puts "Creating #{request_dir}"
33
+ Dir.mkdir(request_dir)
34
+ File.chmod(0710, request_dir)
35
+ File.chown(root_id, unicorn_group_id, request_dir)
36
+ end
37
+
38
+ # Setup sockets directory to hold nginx connection sockets
39
+ unless File.directory?(socket_dir)
40
+ puts "Creating #{socket_dir}"
41
+ Dir.mkdir(socket_dir)
42
+ File.chmod(0770, socket_dir)
43
+ File.chown(www_id, unicorn_group_id, socket_dir)
44
+ end
45
+
46
+ # Setup log directory to hold unicorn application logs
47
+ unless File.directory?(unicorn_log_dir)
48
+ puts "Creating #{unicorn_log_dir}"
49
+ Dir.mkdir(unicorn_log_dir)
50
+ File.chmod(0755, unicorn_log_dir)
51
+ File.chown(root_id, daemon_id, unicorn_log_dir)
52
+ end
53
+
54
+ # Setup log directory to hold nginx logs
55
+ unless File.directory?(nginx_log_dir)
56
+ puts "Creating #{nginx_log_dir}"
57
+ Dir.mkdir(nginx_log_dir)
58
+ File.chmod(0775, nginx_log_dir)
59
+ File.chown(www_id, root_id, nginx_log_dir)
60
+ end
61
+
62
+ # Setup rc.unicorn file
63
+ unless File.file?(rc_unicorn_file)
64
+ puts "Creating #{rc_unicorn_file}"
65
+ File.binwrite(rc_unicorn_file, File.binread(File.join(File.dirname(__dir__), 'files', 'rc.unicorn')))
66
+ File.chmod(0644, rc_unicorn_file)
67
+ File.chown(root_id, root_id, rc_unicorn_file)
68
+ end
@@ -8,10 +8,9 @@ class Roda
8
8
  # the QUIT signal, allowing Unicorn to finish handling the current request before exiting.
9
9
  #
10
10
  # This is designed to be used with applications that cannot connect to the database
11
- # after application initialization, either because they are using chroot and the database
12
- # connection socket is outside the chroot, or because they are using a firewall and access
13
- # to the database server is not allowed from the application the process is running as after
14
- # privileges are dropped.
11
+ # after application initialization, either because they are restricting access to the database
12
+ # socket using unveil, or because they are using a firewall and access to the database server is
13
+ # not allowed from the application the process is running as after privileges are dropped.
15
14
  #
16
15
  # This plugin must be loaded before the roda error_handler plugin, and it assumes usage of the
17
16
  # Sequel database library with the postgres adapter and pg driver.
@@ -23,13 +22,23 @@ class Roda
23
22
  module InstanceMethods
24
23
  # When database connection is lost, kill the worker process, so a new one will be generated.
25
24
  # This is necessary because the unix socket used by the database connection is no longer available
26
- # once the application is chrooted.
25
+ # once the application is unveiled or pledged.
27
26
  def call
28
27
  super
29
28
  rescue Sequel::DatabaseDisconnectError, Sequel::DatabaseConnectionError, PG::ConnectionBad
30
29
  Process.kill(:QUIT, $$)
31
30
  raise
32
31
  end
32
+
33
+ # When database connection is lost, kill the worker process, so a new one will be generated.
34
+ # This is necessary because the unix socket used by the database connection is no longer available
35
+ # once the application is unveiled or pledged.
36
+ def _roda_handle_main_route
37
+ super
38
+ rescue Sequel::DatabaseDisconnectError, Sequel::DatabaseConnectionError, PG::ConnectionBad
39
+ Process.kill(:QUIT, $$)
40
+ raise
41
+ end
33
42
  end
34
43
  end
35
44
 
@@ -1,25 +1,38 @@
1
- # unicorn-lockdown is designed to be used with Unicorn's chroot support, and
2
- # handles:
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
- # * forcing loading of some common autoloaded constants
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
- # Loading single_byte encoding
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. The
19
- # /var/www/requests folder is currently accessable only
20
- # to root.
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
- "/var/www/requests/#{Unicorn.app_name}.#{pid}.txt"
21
+ "/var/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,19 +62,28 @@ class << Unicorn
49
62
  # to enable programmers to debug and fix the issue.
50
63
  attr_accessor :request_logger
51
64
 
52
- # The user and group name to run as.
53
- attr_accessor :user_name
65
+ # The pledge string to use for the master process's spawned processes by default.
66
+ attr_accessor :master_execpledge
54
67
 
55
- # The pledge string to use.
68
+ # The pledge string to use for the master process.
69
+ attr_accessor :master_pledge
70
+
71
+ # The pledge string to use for worker processes.
56
72
  attr_accessor :pledge
57
73
 
58
- # The address to email for crash and unhandled exception notifications
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.
59
81
  attr_accessor :email
60
82
 
61
83
  # Helper method to write request information to the request logger.
62
84
  # +email_message+ should be an email message including headers and body.
63
85
  # This should be called at the top of the Roda route block for the
64
- # application.
86
+ # application (or at some early point before processing in other web frameworks).
65
87
  def write_request(email_message)
66
88
  request_logger.seek(0, IO::SEEK_SET)
67
89
  request_logger.truncate(0)
@@ -69,21 +91,30 @@ class << Unicorn
69
91
  request_logger.fsync
70
92
  end
71
93
 
72
- # Helper method that sets up all necessary code for chroot/pledge support.
94
+ # Helper method that sets up all necessary code for unveil/pledge support.
73
95
  # This should be called inside the appropriate unicorn.conf file.
74
96
  # The configurator should be self in the top level scope of the
75
97
  # unicorn.conf file, and this takes options:
76
98
  #
77
99
  # Options:
78
- # :app :: The name of the application (required)
100
+ # :app (required) :: The name of the application
79
101
  # :email : The email to notify for worker crashes
80
- # :user :: The user/group to run as (required)
81
- # :pledge :: The string to use when pledging
102
+ # :pledge :: The string to use when pledging worker processes after loading the app
103
+ # :master_pledge :: The string to use when pledging the master process before
104
+ # spawning worker processes
105
+ # :master_execpledge :: The pledge string for processes spawned by the master
106
+ # process (i.e. worker processes before loading the app)
107
+ # :unveil :: A hash of unveil paths, passed to Pledge.unveil.
108
+ # :dev_unveil :: A hash of unveil paths to use in development, in addition
109
+ # to the ones in :unveil.
82
110
  def lockdown(configurator, opts)
83
111
  Unicorn.app_name = opts.fetch(:app)
84
- Unicorn.user_name = opts.fetch(:user)
85
112
  Unicorn.email = opts[:email]
113
+ Unicorn.master_pledge = opts[:master_pledge]
114
+ Unicorn.master_execpledge = opts[:master_execpledge]
86
115
  Unicorn.pledge = opts[:pledge]
116
+ Unicorn.unveil = opts[:unveil]
117
+ Unicorn.dev_unveil = opts[:dev_unveil]
87
118
 
88
119
  configurator.instance_exec do
89
120
  listen "/var/www/sockets/#{Unicorn.app_name}.sock"
@@ -107,13 +138,12 @@ class << Unicorn
107
138
  after_fork do |server, worker|
108
139
  server.logger.info("worker=#{worker.nr} spawned pid=#{$$}")
109
140
 
110
- # Set the request logger for the worker process after forking. The
111
- # process is still root here, so it can open the file in write mode.
141
+ # Set the request logger for the worker process after forking.
112
142
  Unicorn.request_logger = File.open(server.request_filename($$), "wb")
113
143
  Unicorn.request_logger.sync = true
114
144
  end
115
145
 
116
- if wrap_app = Unicorn.email && ENV['RACK_ENV'] == 'production'
146
+ if wrap_app = Unicorn.email
117
147
  require 'rack/email_exceptions'
118
148
  end
119
149
 
@@ -129,54 +159,31 @@ class << Unicorn
129
159
  end
130
160
  end
131
161
 
132
- # Before chrooting, reference all constants that use autoload
133
- # that are probably needed at runtime. This must be done
134
- # before chrooting as attempting to load the constants after
135
- # chrooting will break things.
136
- #
137
- # This part is the most prone to breakage, as new versions
138
- # of the rack or mail libraries (or new libraries that
139
- # use autoload) could break things, as well as existing
140
- # applications using new features at runtime that were
141
- # not loaded at load time.
142
- Rack::Multipart
143
- Rack::Multipart::Parser
144
- Rack::Multipart::Generator
145
- Rack::Multipart::UploadedFile
146
- Rack::CommonLogger
147
- Rack::Mime
148
- Rack::Auth::Digest::Params
149
- if ENV['RACK_ENV'] == 'development'
150
- Rack::Lint
151
- Rack::ShowExceptions
152
- end
153
- if defined?(Mail)
154
- Mail::Address
155
- Mail::AddressList
156
- Mail::Parsers::AddressListsParser
157
- Mail::ContentTransferEncodingElement
158
- Mail::ContentDispositionElement
159
- Mail::MessageIdsElement
160
- Mail::MimeVersionElement
161
- Mail::OptionalField
162
- Mail::ContentTypeElement
163
- Mail::SMTP
162
+ unveil = if Unicorn.dev_unveil && ENV['RACK_ENV'] == 'development'
163
+ Unicorn.unveil.merge(Unicorn.dev_unveil)
164
+ else
165
+ Hash[Unicorn.unveil]
164
166
  end
165
167
 
166
- # Strip path prefixes from the reloader. This is only
167
- # really need in development mode for code reloading to work.
168
- pwd = Dir.pwd
169
- Unreloader.strip_path_prefix(pwd) if defined?(Unreloader)
170
-
171
- # Drop privileges. This must be done after chrooting as
172
- # chrooting requires root privileges.
173
- worker.user(Unicorn.user_name, Unicorn.user_name, pwd)
168
+ # Don't allow loading files in rack and mail gems if not using rubygems
169
+ if defined?(Gem) && Gem.respond_to?(:loaded_specs)
170
+ # Allow read access to the rack gem directory, as rack autoloads constants.
171
+ if defined?(Rack) && Gem.loaded_specs['rack']
172
+ unveil['rack'] = :gem
173
+ end
174
174
 
175
- if Unicorn.pledge
176
- # Pledge after dropping privileges, because dropping
177
- # privileges requires a separate pledge.
178
- Pledge.pledge(Unicorn.pledge)
175
+ # If using the mail library, allow read access to the mail gem directory,
176
+ # as mail autoloads constants.
177
+ if defined?(Mail) && Gem.loaded_specs['mail']
178
+ unveil['mail'] = :gem
179
+ end
179
180
  end
181
+
182
+ # Restrict access to the file system based on the specified unveil.
183
+ Pledge.unveil(unveil)
184
+
185
+ # Pledge after unveiling, because unveiling requires a separate pledge.
186
+ Pledge.pledge(Unicorn.pledge)
180
187
  end
181
188
 
182
189
  # the last time there was a worker crash and the request information
@@ -219,28 +226,13 @@ class << Unicorn
219
226
  # If the request filename exists and the worker process crashed,
220
227
  # send a notification email.
221
228
  Process.waitpid(fork do
222
- # Load net/smtp early, before chrooting.
229
+ # Load net/smtp early
223
230
  require 'net/smtp'
224
231
 
225
232
  # When setting the email, first get the contents of the email
226
233
  # from the request file.
227
234
  body = File.read(file)
228
235
 
229
- # Then get information from /etc and drop group privileges
230
- uid = Etc.getpwnam(Unicorn.user_name).uid
231
- gid = Etc.getgrnam(Unicorn.user_name).gid
232
- if gid && Process.egid != gid
233
- Process.initgroups(Unicorn.user_name, gid)
234
- Process::GID.change_privilege(gid)
235
- end
236
-
237
- # Then chroot
238
- Dir.chroot(Dir.pwd)
239
- Dir.chdir('/')
240
-
241
- # Then drop user privileges
242
- Process.euid != uid and Process::UID.change_privilege(uid)
243
-
244
236
  # Then use a restrictive pledge
245
237
  Pledge.pledge('inet prot_exec')
246
238
 
@@ -263,4 +255,3 @@ class << Unicorn
263
255
  end
264
256
  end
265
257
  end
266
-
@@ -0,0 +1,38 @@
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
+ unless defined?(SimpleCov)
33
+ # If running coverage tests, don't run pledged as coverage
34
+ # testing can require many additional permissions.
35
+ Pledge.pledge(pledge)
36
+ end
37
+ end
38
+ 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: 0.9.0
4
+ version: 1.0.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: 2018-05-02 00:00:00.000000000 Z
11
+ date: 2020-11-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pledge
@@ -40,17 +40,22 @@ dependencies:
40
40
  version: '0'
41
41
  description:
42
42
  email: code@jeremyevans.net
43
- executables: []
43
+ executables:
44
+ - unicorn-lockdown-add
45
+ - unicorn-lockdown-setup
44
46
  extensions: []
45
47
  extra_rdoc_files: []
46
48
  files:
49
+ - CHANGELOG
47
50
  - MIT-LICENSE
48
51
  - README.rdoc
52
+ - bin/unicorn-lockdown-add
53
+ - bin/unicorn-lockdown-setup
49
54
  - files/rc.unicorn
50
- - lib/chrooter.rb
51
55
  - lib/rack/email_exceptions.rb
52
56
  - lib/roda/plugins/pg_disconnect.rb
53
57
  - lib/unicorn-lockdown.rb
58
+ - lib/unveiler.rb
54
59
  homepage: https://github.com/jeremyevans/unicorn-lockdown
55
60
  licenses:
56
61
  - MIT
@@ -70,10 +75,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
70
75
  - !ruby/object:Gem::Version
71
76
  version: '0'
72
77
  requirements: []
73
- rubyforge_project:
74
- rubygems_version: 2.7.6
78
+ rubygems_version: 3.1.4
75
79
  signing_key:
76
80
  specification_version: 4
77
- summary: Helper library for running Unicorn with chroot/privdrop/fork+exec/pledge
78
- on OpenBSD
81
+ summary: Helper library for running Unicorn with fork+exec/unveil/pledge on OpenBSD
79
82
  test_files: []
@@ -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