unicorn-lockdown 0.12.0 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|