unicorn-lockdown 0.12.0 → 0.13.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 +6 -0
- data/README.rdoc +51 -6
- data/bin/unicorn-lockdown-add +0 -0
- data/bin/unicorn-lockdown-setup +0 -0
- data/lib/chrooter.rb +52 -9
- data/lib/unicorn-lockdown.rb +75 -40
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5246972fac1124b5bad333f7c94163ca00193f6ad621febaef8d833be05bb6b8
|
4
|
+
data.tar.gz: f015a78584e3fbbeecdfc961f49acdbd9b6f967d6e73e5e47300ccf3b3b15552
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d10bca6474b0c6da80c244670e0edd1fd128fa7866285dad4efaf7c5634f48889db95499556330462d23415237bddb537c80e2316e4a2b935878cefc0c2da66
|
7
|
+
data.tar.gz: b325c37291644bc1b06d37ff6184db81c1355668a612a1cf1bb6516aeee199cf664cce13bb4fcaba2c1bfb2252087ced308f2efa6a5c26f2d78a805ab4a271d9
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
= 0.13.0 (2019-07-09)
|
2
|
+
|
3
|
+
* Add Chrooter.unveil for using unveil in tests (jeremyevans)
|
4
|
+
|
5
|
+
* Support Unicorn.lockdown :unveil and :dev_unveil options for use of unveil instead of chroot (jeremyevans)
|
6
|
+
|
1
7
|
= 0.12.0 (2019-04-29)
|
2
8
|
|
3
9
|
* Do not reference the rack middleware unicorn loads by default if unicorn is set to not load default middleware (jeremyevans)
|
data/README.rdoc
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
= unicorn-lockdown
|
2
2
|
|
3
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,
|
4
|
+
that supports security features such as chroot (or unveil), privdrop, fork+exec,
|
5
5
|
and pledge.
|
6
6
|
|
7
7
|
With the configuration unicorn-lockdown uses, the unicorn process executes as root,
|
8
8
|
and the unicorn master process continues to run as root. The master process
|
9
9
|
forks worker processes, which re-exec (fork+exec) so that a new memory layout
|
10
10
|
is used in each worker process. The worker process then loads the application,
|
11
|
-
after which it chroots to the application's directory
|
12
|
-
|
13
|
-
|
11
|
+
after which it chroots to the application's directory (or uses unveil to limit
|
12
|
+
file system permissions), drops root privileges and then runs as the application
|
13
|
+
user (privdrop), then runs pledge to limit the allowed system calls to the minimum
|
14
|
+
required to run the application.
|
14
15
|
|
15
16
|
== Assumptions
|
16
17
|
|
@@ -21,6 +22,11 @@ the PATH to the appropriate unicornXY executable.
|
|
21
22
|
It also assumes you have a SMTP server listening on localhost port 25 to
|
22
23
|
receive notification emails of worker crashes, if you are notifying for those.
|
23
24
|
|
25
|
+
If using unveil instead of chroot to limit file system access, you must be
|
26
|
+
using OpenBSD 6.6+ (or 6.5-current after July 2019), one of the OpenBSD Ruby
|
27
|
+
ports with backported support for unveil to work correctly with +File.realpath+,
|
28
|
+
and version 1.2.0+ of the pledge gem.
|
29
|
+
|
24
30
|
== Usage
|
25
31
|
|
26
32
|
=== unicorn-lockdown-setup
|
@@ -71,7 +77,7 @@ directory and can make modifications to the application.
|
|
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 chroot (or unveil), privdrop, fork+exec, 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:
|
@@ -81,7 +87,7 @@ file for the app if one does not already exist, looking similar to:
|
|
81
87
|
Unicorn.lockdown(self,
|
82
88
|
:app=>"app_name",
|
83
89
|
:user=>"user_name", # Set application user here
|
84
|
-
:pledge=>'rpath prot_exec inet unix' # More may be needed
|
90
|
+
:pledge=>'rpath prot_exec inet unix', # More may be needed
|
85
91
|
:email=>'root' # update this with correct email
|
86
92
|
)
|
87
93
|
|
@@ -96,6 +102,13 @@ Unicorn.lockdown options:
|
|
96
102
|
log files.
|
97
103
|
:pledge :: (optional) a pledge string to limit the allowed system calls
|
98
104
|
after privileges have been dropped
|
105
|
+
:unveil :: (optional) a hash of paths to limit file system access, passed
|
106
|
+
to +Pledge.unveil+. Forces use of unveil instead of chroot.
|
107
|
+
:dev_unveil :: (optional, requires :unveil option) a hash of paths to
|
108
|
+
limit file system, merged into the :unveil option paths
|
109
|
+
if in the development environment. Useful if you are
|
110
|
+
allowing more access in development, such as access needed
|
111
|
+
for file reloading.
|
99
112
|
:email :: (optional) an email address to use for notifications when the
|
100
113
|
worker process crashes or an unhandled exception is raised by
|
101
114
|
the application or middleware.
|
@@ -202,6 +215,22 @@ the current directory after loading the specs, then drop
|
|
202
215
|
privileges to the user given (and optionally pledging using the given
|
203
216
|
pledge string), then run the specs.
|
204
217
|
|
218
|
+
If you want to use unveil instead of chroot, you can use
|
219
|
+
+Chrooter.unveil+:
|
220
|
+
|
221
|
+
require 'minitest/autorun'
|
222
|
+
require 'chrooter'
|
223
|
+
at_exit do
|
224
|
+
Chrooter.unveil('user_name', 'rpath prot_exec inet unix',
|
225
|
+
'views' => 'r',
|
226
|
+
'rack' => :gem,
|
227
|
+
'mail' => :gem,
|
228
|
+
)
|
229
|
+
end
|
230
|
+
|
231
|
+
One advantage of using unveil instead of chroot is that the unveils
|
232
|
+
will take effect even when not starting the tests as root.
|
233
|
+
|
205
234
|
== autoload
|
206
235
|
|
207
236
|
As you'll find out if you try to run your applications with chroot,
|
@@ -211,6 +240,22 @@ If you use other gems that use autoload, you'll have to add code that
|
|
211
240
|
references the autoloaded constants after the application is loaded but
|
212
241
|
before chrooting.
|
213
242
|
|
243
|
+
If you have to use libraries that use autoload, it is recommended that
|
244
|
+
you use the support for unveil instead of using chroot, and allow
|
245
|
+
access to the given gems. unicorn-lockdown will automatically unveil
|
246
|
+
the +rack+ gem, and will unveil the +mail+ gem if the +Mail+ constant
|
247
|
+
is defined.
|
248
|
+
|
249
|
+
Unicorn.lockdown(self,
|
250
|
+
:app=>"app_name",
|
251
|
+
:user=>"user_name", # Set application user here
|
252
|
+
:pledge=>'rpath prot_exec inet unix', # More may be needed
|
253
|
+
:unveil=>{
|
254
|
+
'views' => 'r',
|
255
|
+
'gem-name' => :gem,
|
256
|
+
}
|
257
|
+
)
|
258
|
+
|
214
259
|
== Author
|
215
260
|
|
216
261
|
Jeremy Evans <code@jeremyevans.net>
|
data/bin/unicorn-lockdown-add
CHANGED
File without changes
|
data/bin/unicorn-lockdown-setup
CHANGED
File without changes
|
data/lib/chrooter.rb
CHANGED
@@ -20,9 +20,36 @@ if defined?(SimpleCov) && Process.euid == 0
|
|
20
20
|
raise "cannot run coverage testing in chroot mode"
|
21
21
|
end
|
22
22
|
|
23
|
-
# Chrooter allows for testing programs in both chroot and non
|
23
|
+
# Chrooter allows for testing programs in both chroot/unveil and non
|
24
24
|
# chroot modes.
|
25
25
|
module Chrooter
|
26
|
+
# If the current user is the super user, drop privileges to
|
27
|
+
# the given +user+ first.
|
28
|
+
#
|
29
|
+
# Use Pledge.unveil to limit access to the file system based on the
|
30
|
+
# +unveil+ option. Then pledge the process with the given +pledge+
|
31
|
+
# permissions (if given).
|
32
|
+
def self.unveil(user, pledge=nil, unveil={}, group=user)
|
33
|
+
require 'unveil'
|
34
|
+
|
35
|
+
unveil = Hash[unveil]
|
36
|
+
if defined?(Rack)
|
37
|
+
unveil['rack'] = :gem
|
38
|
+
end
|
39
|
+
if defined?(Mail)
|
40
|
+
unveil['mail'] = :gem
|
41
|
+
end
|
42
|
+
|
43
|
+
if Process.euid == 0
|
44
|
+
_drop_privs(user, group)
|
45
|
+
puts "Unveiled, running as user #{user}"
|
46
|
+
end
|
47
|
+
|
48
|
+
Pledge.unveil(unveil)
|
49
|
+
|
50
|
+
_pledge(pledge)
|
51
|
+
end
|
52
|
+
|
26
53
|
# If the current user is the super user, change to the given
|
27
54
|
# user/group, chroot to the given directory, and pledge
|
28
55
|
# the process with the given permissions (if given).
|
@@ -75,15 +102,10 @@ module Chrooter
|
|
75
102
|
end
|
76
103
|
|
77
104
|
if Process.euid == 0
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
Process.initgroups(user, gid)
|
82
|
-
Process::GID.change_privilege(gid)
|
105
|
+
_drop_privs(user, group) do
|
106
|
+
Dir.chroot(dir)
|
107
|
+
Dir.chdir('/')
|
83
108
|
end
|
84
|
-
Dir.chroot(dir)
|
85
|
-
Dir.chdir('/')
|
86
|
-
Process.euid != uid and Process::UID.change_privilege(uid)
|
87
109
|
puts "Chrooted to #{dir}, running as user #{user}"
|
88
110
|
else
|
89
111
|
# Load minitest plugins before freezing loaded features,
|
@@ -97,6 +119,26 @@ module Chrooter
|
|
97
119
|
$LOADED_FEATURES.freeze
|
98
120
|
end
|
99
121
|
|
122
|
+
_pledge(pledge)
|
123
|
+
end
|
124
|
+
|
125
|
+
def self._drop_privs(user, group)
|
126
|
+
uid = Etc.getpwnam(user).uid
|
127
|
+
gid = Etc.getgrnam(group).gid
|
128
|
+
if Process.egid != gid
|
129
|
+
Process.initgroups(user, gid)
|
130
|
+
Process::GID.change_privilege(gid)
|
131
|
+
end
|
132
|
+
|
133
|
+
yield if block_given?
|
134
|
+
|
135
|
+
Process.euid != uid and Process::UID.change_privilege(uid)
|
136
|
+
|
137
|
+
nil
|
138
|
+
end
|
139
|
+
private_class_method :_drop_privs
|
140
|
+
|
141
|
+
def self._pledge(pledge)
|
100
142
|
unless defined?(SimpleCov)
|
101
143
|
if pledge
|
102
144
|
# If running coverage tests, don't run pledged as coverage
|
@@ -107,4 +149,5 @@ module Chrooter
|
|
107
149
|
|
108
150
|
nil
|
109
151
|
end
|
152
|
+
private_class_method :_pledge
|
110
153
|
end
|
data/lib/unicorn-lockdown.rb
CHANGED
@@ -59,6 +59,12 @@ class << Unicorn
|
|
59
59
|
# The pledge string to use.
|
60
60
|
attr_accessor :pledge
|
61
61
|
|
62
|
+
# The hash of unveil paths to use.
|
63
|
+
attr_accessor :unveil
|
64
|
+
|
65
|
+
# The hash of additional unveil paths to use if in the development environment.
|
66
|
+
attr_accessor :dev_unveil
|
67
|
+
|
62
68
|
# The address to email for crash and unhandled exception notifications
|
63
69
|
attr_accessor :email
|
64
70
|
|
@@ -86,12 +92,16 @@ class << Unicorn
|
|
86
92
|
# Can be an array of two strings, where the first string is the primary
|
87
93
|
# group, and the second string is the group used for the log files.
|
88
94
|
# :pledge :: The string to use when pledging
|
95
|
+
# :unveil :: A hash of unveil paths
|
96
|
+
# :dev_unveil :: A hash of unveil paths to use in development
|
89
97
|
def lockdown(configurator, opts)
|
90
98
|
Unicorn.app_name = opts.fetch(:app)
|
91
99
|
Unicorn.user_name = opts.fetch(:user)
|
92
100
|
Unicorn.group_name = opts[:group] || opts[:user]
|
93
101
|
Unicorn.email = opts[:email]
|
94
102
|
Unicorn.pledge = opts[:pledge]
|
103
|
+
Unicorn.unveil = opts[:unveil]
|
104
|
+
Unicorn.dev_unveil = opts[:dev_unveil]
|
95
105
|
|
96
106
|
configurator.instance_exec do
|
97
107
|
listen "/var/www/sockets/#{Unicorn.app_name}.sock"
|
@@ -137,49 +147,74 @@ class << Unicorn
|
|
137
147
|
end
|
138
148
|
end
|
139
149
|
|
140
|
-
|
141
|
-
|
142
|
-
# before chrooting as attempting to load the constants after
|
143
|
-
# chrooting will break things.
|
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
|
165
|
-
end
|
150
|
+
if unveil = Unicorn.unveil
|
151
|
+
require 'unveil'
|
166
152
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
153
|
+
unveil = if Unicorn.dev_unveil && ENV['RACK_ENV'] == 'development'
|
154
|
+
unveil.merge(Unicorn.dev_unveil)
|
155
|
+
else
|
156
|
+
Hash[unveil]
|
157
|
+
end
|
158
|
+
|
159
|
+
# Allow read access to the rack gem directory, as rack autoloads constants.
|
160
|
+
unveil['rack'] = :gem
|
161
|
+
|
162
|
+
if defined?(Mail)
|
163
|
+
# If using the mail library, allow read access to the mail gem directory,
|
164
|
+
# as mail autoloads constants.
|
165
|
+
unveil['mail'] = :gem
|
166
|
+
end
|
174
167
|
|
175
|
-
|
176
|
-
|
177
|
-
pwd = Dir.pwd
|
178
|
-
Unreloader.strip_path_prefix(pwd) if defined?(Unreloader)
|
168
|
+
# Drop privileges
|
169
|
+
worker.user(Unicorn.user_name, Unicorn.group_name)
|
179
170
|
|
180
|
-
|
181
|
-
|
182
|
-
|
171
|
+
# Restrict access to the file system based on the specified unveil.
|
172
|
+
Pledge.unveil(unveil)
|
173
|
+
else
|
174
|
+
# Before chrooting, reference all constants that use autoload
|
175
|
+
# that are probably needed at runtime. This must be done
|
176
|
+
# before chrooting as attempting to load the constants after
|
177
|
+
# chrooting will break things.
|
178
|
+
|
179
|
+
# Start with rack, which uses autoload for all constants.
|
180
|
+
# Most of rack's constants are not used at runtime, this
|
181
|
+
# lists the ones most commonly needed.
|
182
|
+
Rack::Multipart
|
183
|
+
Rack::Multipart::Parser
|
184
|
+
Rack::Multipart::Generator
|
185
|
+
Rack::Multipart::UploadedFile
|
186
|
+
Rack::Mime
|
187
|
+
Rack::Auth::Digest::Params
|
188
|
+
|
189
|
+
# In the development environment, reference all middleware
|
190
|
+
# the unicorn will load by default, unless unicorn is
|
191
|
+
# set to not load middleware by default.
|
192
|
+
if ENV['RACK_ENV'] == 'development' && (!respond_to?(:set) || set[:default_middleware] != false)
|
193
|
+
Rack::ContentLength
|
194
|
+
Rack::CommonLogger
|
195
|
+
Rack::Chunked
|
196
|
+
Rack::Lint
|
197
|
+
Rack::ShowExceptions
|
198
|
+
Rack::TempfileReaper
|
199
|
+
end
|
200
|
+
|
201
|
+
# If using the mail library, eagerly autoload all constants.
|
202
|
+
# This costs about 9MB of memory, but the mail gem changes
|
203
|
+
# their autoloaded constants on a regular basis, so it's
|
204
|
+
# better to be safe than sorry.
|
205
|
+
if defined?(Mail)
|
206
|
+
Mail.eager_autoload!
|
207
|
+
end
|
208
|
+
|
209
|
+
# Strip path prefixes from the reloader. This is only
|
210
|
+
# really need in development mode for code reloading to work.
|
211
|
+
pwd = Dir.pwd
|
212
|
+
Unreloader.strip_path_prefix(pwd) if defined?(Unreloader)
|
213
|
+
|
214
|
+
# Drop privileges. This must be done after chrooting as
|
215
|
+
# chrooting requires root privileges.
|
216
|
+
worker.user(Unicorn.user_name, Unicorn.group_name, pwd)
|
217
|
+
end
|
183
218
|
|
184
219
|
if Unicorn.pledge
|
185
220
|
# Pledge after dropping privileges, because dropping
|
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.
|
4
|
+
version: 0.13.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: 2019-
|
11
|
+
date: 2019-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pledge
|
@@ -78,6 +78,6 @@ requirements: []
|
|
78
78
|
rubygems_version: 3.0.3
|
79
79
|
signing_key:
|
80
80
|
specification_version: 4
|
81
|
-
summary: Helper library for running Unicorn with chroot/privdrop/fork+exec/pledge
|
81
|
+
summary: Helper library for running Unicorn with (chroot|unveil)/privdrop/fork+exec/pledge
|
82
82
|
on OpenBSD
|
83
83
|
test_files: []
|