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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 24ec67b0fb4c2a47aea2652b30a1b99973226d379492fff1a87c1aef3dba5038
4
- data.tar.gz: 84ecb2a8a3a97929e4fc6c960eaee2cab8df0c9f784297e6a3b11ff00c8044f8
3
+ metadata.gz: 5246972fac1124b5bad333f7c94163ca00193f6ad621febaef8d833be05bb6b8
4
+ data.tar.gz: f015a78584e3fbbeecdfc961f49acdbd9b6f967d6e73e5e47300ccf3b3b15552
5
5
  SHA512:
6
- metadata.gz: 866fcee1a554851025331f856fcb47e3f77f53967a79abda8fc89fd9ead7fd2c390f090338dc3a3047dca22d2b8e7f87595fe5dbc509e5636d825e56c0670544
7
- data.tar.gz: bbec6a5aa7159b05ef7995ca56235f3c08cc001c1fbf6593d2ec1f2c99db457b2d998b01d866f1223b14c58e03e68bef348b0c8e0bbcf213ae38021a3d6da18a
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)
@@ -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, 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.
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>
File without changes
File without changes
@@ -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
- 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)
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
@@ -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
- # Before chrooting, reference all constants that use autoload
141
- # that are probably needed at runtime. This must be done
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
- # If using the mail library, eagerly autoload all constants.
168
- # This costs about 9MB of memory, but the mail gem changes
169
- # their autoloaded constants on a regular basis, so it's
170
- # better to be safe than sorry.
171
- if defined?(Mail)
172
- Mail.eager_autoload!
173
- end
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
- # Strip path prefixes from the reloader. This is only
176
- # really need in development mode for code reloading to work.
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
- # Drop privileges. This must be done after chrooting as
181
- # chrooting requires root privileges.
182
- worker.user(Unicorn.user_name, Unicorn.group_name, pwd)
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.12.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-04-29 00:00:00.000000000 Z
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: []