unicorn-lockdown 1.0.0 → 1.1.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: 931209c4514dbb9f15da327fac6ed81f82fed2a504c500a34aa5f6e2624cde5a
4
- data.tar.gz: 160c9cf285e6c4f8abeb38bce3d1b7006f236607e6f18e606fdcbd8b53f73baa
3
+ metadata.gz: 0f0b84f88c8502c942f5b15b50bb7ac6946de87ef31ee14cf96e9d71b569b8a3
4
+ data.tar.gz: 69d55260a85368464b5dbb0b9b24120eacb3d9f95f07c4be765f8d913b8e7f2d
5
5
  SHA512:
6
- metadata.gz: 9f457aefb1571441815165ef9fb032f2de71547debfa81d9a7d4a441019e0671e00258b77bdb4fdca29dca677a5b5e795c65a7f53483cbf67bd12ef2950e4545
7
- data.tar.gz: de3ee5209b1cb08dd0918448689868cc7b5c423ec07c929df7a4475d222436b32ea271f972f789a7ee13ffcffc8a7bbafd78950741c40392d49b8c69af69473e
6
+ metadata.gz: 2622fa1ea4b31f117037175273420574269b8f976e905d39a76137aefb4da44e94e13e1ec121db5a9d21fcef901dc3d5fec42434c36d5dd5cc21640d64379131
7
+ data.tar.gz: 032dbedbb2e5eab750fb10ae6f9ef0cb108fdedac239bf3adead2faa637bddfb8613a1d9a865730d449e2d4d6d334f35bab5b762f5b97bf4e946872ebdbf1d63
data/CHANGELOG CHANGED
@@ -1,3 +1,13 @@
1
+ = 1.1.0 (2022-07-18)
2
+
3
+ * Make unveiler still pledge if SimpleCov is loaded, but update pledge promises (jeremyevans)
4
+
5
+ * Fix roda pg_disconnect plugin to correctly error if error_handler is already loaded (jeremyevans)
6
+
7
+ * Avoid SSL error in newer versions of net/smtp when notifying about worker crashes (jeremyevans)
8
+
9
+ * Add flock pledge, needed on Ruby 3.1+ (jeremyevans)
10
+
1
11
  = 1.0.0 (2020-11-09)
2
12
 
3
13
  * Require unicorn-lockdown-add -o and -u options, and require options have arguments (jeremyevans)
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2018-2020 Jeremy Evans
1
+ Copyright (c) 2018-2022 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
data/README.rdoc CHANGED
@@ -91,9 +91,9 @@ file for the app if one does not already exist, looking similar to:
91
91
  :email=>'root',
92
92
 
93
93
  # More pledges may be needed depending on application
94
- :pledge=>'rpath prot_exec inet unix',
94
+ :pledge=>'rpath prot_exec inet unix flock',
95
95
  :master_pledge=>'rpath prot_exec cpath wpath inet proc exec',
96
- :master_execpledge=>'stdio rpath prot_exec inet unix cpath wpath unveil',
96
+ :master_execpledge=>'stdio rpath prot_exec inet unix cpath wpath unveil flock',
97
97
 
98
98
  # More unveils may be needed depending on application
99
99
  :unveil=>{
@@ -111,7 +111,7 @@ Unicorn.lockdown options:
111
111
  :email :: (optional) an email address to use for notifications when the
112
112
  worker process crashes or an unhandled exception is raised by
113
113
  the application or middleware.
114
- :pledge :: (optional) a pledge string to limit the allowed system calls
114
+ :pledge :: (required) a pledge string to limit the allowed system calls
115
115
  after privileges have been dropped
116
116
  :master_pledge :: (optional) The string to use when pledging the master process before
117
117
  spawning worker processes
@@ -134,6 +134,7 @@ With this example pledge:
134
134
  exceptions that occur without process crashes. pf (OpenBSD's firewall)
135
135
  should be used to limit access for the application's operating system
136
136
  user to the minimum necessary access needed.
137
+ * flock is needed in Ruby 3.1+ (not necessarily required in older Ruby versions).
137
138
 
138
139
  With this example master pledge:
139
140
 
@@ -1,222 +1,2 @@
1
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
2
+ require_relative '../files/unicorn_lockdown_add'
@@ -1,68 +1,2 @@
1
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
2
+ require_relative '../files/unicorn_lockdown_setup'
@@ -0,0 +1,227 @@
1
+ require 'etc'
2
+ require 'optparse'
3
+
4
+ unicorn = ''
5
+ rackup = ''
6
+ unicorn_file = 'unicorn.conf'
7
+ dir = nil
8
+ user = nil
9
+ new_user_uid = nil
10
+ owner = nil
11
+ owner_uid = nil
12
+ owner_gid = nil
13
+
14
+ options = OptionParser.new do |opts|
15
+ opts.banner = "Usage: unicorn-lockdown-add -o owner -u user [options] app_name"
16
+ opts.separator "Options:"
17
+
18
+ opts.on_tail("-h", "-?", "--help", "Show this message") do
19
+ puts opts
20
+ exit
21
+ end
22
+
23
+ opts.on("-c RACKUP_FILE", "rackup configuration file") do |v|
24
+ rackup = "rackup_file=#{v}\n"
25
+ end
26
+
27
+ opts.on("-d DIR", "application directory name") do |v|
28
+ dir = v
29
+ end
30
+
31
+ opts.on("-f UNICORN_FILE", "unicorn configuration file relative to application directory") do |v|
32
+ unicorn_file = v
33
+ unicorn = "unicorn_conf=#{v}\n"
34
+ end
35
+
36
+ opts.on("-o OWNER", "operating system application owner") do |v|
37
+ owner = v
38
+ ent = Etc.getpwnam(v)
39
+ owner_uid = ent.uid
40
+ owner_gid = ent.gid
41
+ end
42
+
43
+ opts.on("-u USER", "operating system user to run application") do |v|
44
+ user = v
45
+ end
46
+
47
+ opts.on("--uid UID", "user id to use if creating the user when -U is specified") do |v|
48
+ new_user_uid = Integer(v, 10)
49
+ end
50
+ end
51
+ options.parse!
52
+
53
+ unless user && owner
54
+ $stderr.puts "Must pass -o and -u options when calling unicorn_lockdown_add"
55
+ puts options
56
+ exit(1)
57
+ end
58
+
59
+ app = ARGV.shift
60
+ dir ||= app
61
+
62
+ root_id = 0
63
+ bin_id = 7
64
+ www_id = 67
65
+
66
+ prefix = ENV['UNICORN_LOCKDOWN_BIN_PREFIX']
67
+ www_root = "#{prefix}/var/www"
68
+ dir = "#{www_root}/#{dir}"
69
+ rc_file = "#{prefix}/etc/rc.d/unicorn_#{app.tr('-', '_')}"
70
+ nginx_file = "#{prefix}/etc/nginx/#{app}.conf"
71
+ unicorn_conf_file = "#{dir}/#{unicorn_file}"
72
+ nonroot_dir = "#{prefix}/var/www/request-error-data/#{app}"
73
+ unicorn_log_file = "#{prefix}/var/log/unicorn/#{app}.log"
74
+ nginx_access_log_file = "#{prefix}/var/log/nginx/#{app}.access.log"
75
+ nginx_error_log_file = "#{prefix}/var/log/nginx/#{app}.error.log"
76
+
77
+ if Process.uid == 0
78
+ # :nocov:
79
+ chown = lambda{|*a| File.chown(*a)}
80
+ # :nocov:
81
+ else
82
+ chown = lambda{|*a| }
83
+ end
84
+
85
+ # Add application user if it doesn't exist
86
+ passwd = begin
87
+ Etc.getpwnam(user)
88
+ rescue ArgumentError
89
+ # :nocov:
90
+ args = ['/usr/sbin/useradd', '-d', '/var/empty', '-g', '=uid', '-G', '_unicorn', '-L', 'daemon', '-s', '/sbin/nologin']
91
+ if new_user_uid
92
+ args << '-u' << new_user_uid.to_s
93
+ end
94
+ args << user
95
+ puts "Running: #{args.join(' ')}"
96
+ system(*args) || raise("Error while running: #{args.join(' ')}")
97
+ Etc.getpwnam(user)
98
+ # :nocov:
99
+ end
100
+ app_uid = passwd.uid
101
+
102
+ # Create the subdirectory used for request error info when not running as root
103
+ unless File.directory?(nonroot_dir)
104
+ puts "Creating #{nonroot_dir}"
105
+ Dir.mkdir(nonroot_dir)
106
+ File.chmod(0700, nonroot_dir)
107
+ chown.(app_uid, app_uid, nonroot_dir)
108
+ end
109
+
110
+ # Create application public directory if it doesn't exist (needed by nginx)
111
+ dirs = [dir, "#{dir}/public"]
112
+ dirs.each do |d|
113
+ unless File.directory?(d)
114
+ puts "Creating #{d}"
115
+ Dir.mkdir(d)
116
+ File.chmod(0755, d)
117
+ chown.(owner_uid, owner_gid, d)
118
+ end
119
+ end
120
+
121
+ # DRY up file ownership code
122
+ setup_file_owner = lambda do |file|
123
+ File.chmod(0644, file)
124
+ chown.(owner_uid, owner_gid, file)
125
+ end
126
+
127
+ # Setup unicorn configuration file
128
+ unless File.file?(unicorn_conf_file)
129
+ unicorn_conf_dir = File.dirname(unicorn_conf_file)
130
+ unless File.directory?(unicorn_conf_dir)
131
+ puts "Creating #{unicorn_conf_dir}"
132
+ Dir.mkdir(unicorn_conf_dir)
133
+ File.chmod(0755, unicorn_conf_dir)
134
+ chown.(owner_uid, owner_gid, unicorn_conf_dir)
135
+ end
136
+ puts "Creating #{unicorn_conf_file}"
137
+ File.binwrite(unicorn_conf_file, <<END)
138
+ require 'unicorn-lockdown'
139
+
140
+ Unicorn.lockdown(self,
141
+ :app=>#{app.inspect},
142
+
143
+ # Update this with correct email
144
+ :email=>'root',
145
+
146
+ # More pledges may be needed depending on application
147
+ :pledge=>'rpath prot_exec inet unix flock',
148
+ :master_pledge=>'rpath prot_exec cpath wpath inet proc exec',
149
+ :master_execpledge=>'stdio rpath prot_exec inet unix cpath wpath unveil flock',
150
+
151
+ # More unveils may be needed depending on application
152
+ :unveil=>{
153
+ 'views'=>'r'
154
+ },
155
+ :dev_unveil=>{
156
+ 'models'=>'r'
157
+ },
158
+ )
159
+ END
160
+ setup_file_owner.call(unicorn_conf_file)
161
+ end
162
+
163
+ # Setup /etc/nginx/* file for nginx configuration
164
+ unless File.file?(nginx_file)
165
+ puts "Creating #{nginx_file}"
166
+ File.binwrite(nginx_file, <<END)
167
+ upstream #{app}_unicorn {
168
+ server unix:/sockets/#{app}.sock fail_timeout=0;
169
+ }
170
+ server {
171
+ server_name #{app};
172
+ access_log #{nginx_access_log_file} main;
173
+ error_log #{nginx_error_log_file} warn;
174
+ root #{dir}/public;
175
+ error_page 500 503 /500.html;
176
+ error_page 502 504 /502.html;
177
+ proxy_set_header X-Real-IP $remote_addr;
178
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
179
+ proxy_set_header Host $http_host;
180
+ proxy_redirect off;
181
+ add_header X-Content-Type-Options nosniff;
182
+ add_header X-Frame-Options deny;
183
+ add_header X-XSS-Protection "1; mode=block";
184
+ try_files $uri @#{app}_unicorn;
185
+ location @#{app}_unicorn {
186
+ proxy_pass http://#{app}_unicorn;
187
+ }
188
+ }
189
+ END
190
+
191
+ setup_file_owner.call(nginx_file)
192
+ end
193
+
194
+ # Setup nginx log file
195
+ [nginx_access_log_file, nginx_error_log_file].each do |f|
196
+ unless File.file?(f)
197
+ puts "Creating #{f}"
198
+ File.binwrite(f, '')
199
+ File.chmod(0644, f)
200
+ chown.(www_id, root_id, f)
201
+ end
202
+ end
203
+
204
+ # Setup unicorn log file
205
+ unless File.file?(unicorn_log_file)
206
+ puts "Creating #{unicorn_log_file}"
207
+ File.binwrite(unicorn_log_file, '')
208
+ File.chmod(0640, unicorn_log_file)
209
+ chown.(app_uid, Etc.getgrnam('_unicorn').gid, unicorn_log_file)
210
+ end
211
+
212
+ # Setup /etc/rc.d/unicorn_* file for daemon management
213
+ unless File.file?(rc_file)
214
+ puts "Creating #{rc_file}"
215
+ File.binwrite(rc_file, <<END)
216
+ #!/bin/ksh
217
+
218
+ daemon_user=#{user}
219
+ unicorn_app=#{app}
220
+ unicorn_dir=#{dir}
221
+ #{unicorn}#{rackup}
222
+ . /etc/rc.d/rc.unicorn
223
+ END
224
+
225
+ File.chmod(0755, rc_file)
226
+ chown.(root_id, bin_id, rc_file)
227
+ end
@@ -0,0 +1,74 @@
1
+ require 'etc'
2
+
3
+ prefix = ENV['UNICORN_LOCKDOWN_BIN_PREFIX']
4
+ request_dir = "#{prefix}/var/www/request-error-data"
5
+ socket_dir = "#{prefix}/var/www/sockets"
6
+ unicorn_log_dir = "#{prefix}/var/log/unicorn"
7
+ nginx_log_dir = "#{prefix}/var/log/nginx"
8
+ rc_unicorn_file = "#{prefix}/etc/rc.d/rc.unicorn"
9
+
10
+ unicorn_group = '_unicorn'
11
+ root_id = 0
12
+ daemon_id = 1
13
+ www_id = 67
14
+
15
+ if Process.uid == 0
16
+ # :nocov:
17
+ chown = lambda{|*a| File.chown(*a)}
18
+ # :nocov:
19
+ else
20
+ chown = lambda{|*a| }
21
+ end
22
+
23
+ # Add _unicorn group if it doesn't exist
24
+ group = begin
25
+ Etc.getgrnam(unicorn_group)
26
+ rescue ArgumentError
27
+ # :nocov:
28
+ args = ['groupadd', unicorn_group]
29
+ puts "Running: #{args.join(' ')}"
30
+ system(*args) || raise("Error while running: #{args.join(' ')}")
31
+ Etc.getgrnam(unicorn_group)
32
+ # :nocov:
33
+ end
34
+ unicorn_group_id = group.gid
35
+
36
+ # Setup requests directory to hold per-request information for crash notifications
37
+ unless File.directory?(request_dir)
38
+ puts "Creating #{request_dir}"
39
+ Dir.mkdir(request_dir)
40
+ File.chmod(0710, request_dir)
41
+ chown.(root_id, unicorn_group_id, request_dir)
42
+ end
43
+
44
+ # Setup sockets directory to hold nginx connection sockets
45
+ unless File.directory?(socket_dir)
46
+ puts "Creating #{socket_dir}"
47
+ Dir.mkdir(socket_dir)
48
+ File.chmod(0770, socket_dir)
49
+ chown.(www_id, unicorn_group_id, socket_dir)
50
+ end
51
+
52
+ # Setup log directory to hold unicorn application logs
53
+ unless File.directory?(unicorn_log_dir)
54
+ puts "Creating #{unicorn_log_dir}"
55
+ Dir.mkdir(unicorn_log_dir)
56
+ File.chmod(0755, unicorn_log_dir)
57
+ chown.(root_id, daemon_id, unicorn_log_dir)
58
+ end
59
+
60
+ # Setup log directory to hold nginx logs
61
+ unless File.directory?(nginx_log_dir)
62
+ puts "Creating #{nginx_log_dir}"
63
+ Dir.mkdir(nginx_log_dir)
64
+ File.chmod(0775, nginx_log_dir)
65
+ chown.(www_id, root_id, nginx_log_dir)
66
+ end
67
+
68
+ # Setup rc.unicorn file
69
+ unless File.file?(rc_unicorn_file)
70
+ puts "Creating #{rc_unicorn_file}"
71
+ File.binwrite(rc_unicorn_file, File.binread(File.join(File.dirname(__dir__), 'files', 'rc.unicorn')))
72
+ File.chmod(0644, rc_unicorn_file)
73
+ chown.(root_id, root_id, rc_unicorn_file)
74
+ end
@@ -36,7 +36,12 @@ ENV:
36
36
  #{env.map{|k, v| "#{k.inspect} => #{v.inspect}"}.sort.join("\n")}
37
37
  END
38
38
 
39
- Net::SMTP.start('127.0.0.1'){|s| s.send_message(body, @email, @email)}
39
+ # :nocov:
40
+ # Don't verify localhost hostname, to avoid SSL errors raised in newer versions of net/smtp
41
+ smtp_params = Net::SMTP.method(:start).parameters.include?([:key, :tls_verify]) ? {tls_verify: false, tls_hostname: 'localhost'} : {}
42
+ # :nocov:
43
+
44
+ Net::SMTP.start('127.0.0.1', **smtp_params){|s| s.send_message(body, @email, @email)}
40
45
 
41
46
  raise
42
47
  end
@@ -16,19 +16,19 @@ class Roda
16
16
  # Sequel database library with the postgres adapter and pg driver.
17
17
  module PgDisconnect
18
18
  def self.load_dependencies(app)
19
- raise RodaError, "error_handler plugin already loaded" if app.method_defined?(:handle_error)
19
+ raise RodaError, "error_handler plugin already loaded" if app.method_defined?(:handle_error) || app.private_method_defined?(:handle_error)
20
20
  end
21
21
 
22
22
  module InstanceMethods
23
- # When database connection is lost, kill the worker process, so a new one will be generated.
24
- # This is necessary because the unix socket used by the database connection is no longer available
25
- # once the application is unveiled or pledged.
23
+ # :nocov:
24
+ # Handle old Roda dispatch API
26
25
  def call
27
26
  super
28
27
  rescue Sequel::DatabaseDisconnectError, Sequel::DatabaseConnectionError, PG::ConnectionBad
29
28
  Process.kill(:QUIT, $$)
30
29
  raise
31
30
  end
31
+ # :nocov:
32
32
 
33
33
  # When database connection is lost, kill the worker process, so a new one will be generated.
34
34
  # This is necessary because the unix socket used by the database connection is no longer available
@@ -18,7 +18,7 @@ class Unicorn::HttpServer
18
18
  # The /var/www/request-error-data/$app_name folder is accessable
19
19
  # only to the user of the application.
20
20
  def request_filename(pid)
21
- "/var/www/request-error-data/#{Unicorn.app_name}/#{pid}.txt"
21
+ "#{Unicorn.unicorn_lockdown_prefix}/www/request-error-data/#{Unicorn.app_name}/#{pid}.txt"
22
22
  end
23
23
 
24
24
  unless ENV['UNICORN_WORKER']
@@ -80,6 +80,10 @@ class << Unicorn
80
80
  # The address to email for crash and unhandled exception notifications.
81
81
  attr_accessor :email
82
82
 
83
+ # The prefix for unicorn lockdown files
84
+ attr_reader :unicorn_lockdown_prefix
85
+ Unicorn.instance_variable_set(:@unicorn_lockdown_prefix, ENV['UNICORN_LOCKDOWN_PREFIX'] || '/var')
86
+
83
87
  # Helper method to write request information to the request logger.
84
88
  # +email_message+ should be an email message including headers and body.
85
89
  # This should be called at the top of the Roda route block for the
@@ -117,7 +121,7 @@ class << Unicorn
117
121
  Unicorn.dev_unveil = opts[:dev_unveil]
118
122
 
119
123
  configurator.instance_exec do
120
- listen "/var/www/sockets/#{Unicorn.app_name}.sock"
124
+ listen "#{Unicorn.unicorn_lockdown_prefix}/www/sockets/#{Unicorn.app_name}.sock"
121
125
 
122
126
  # Buffer all client bodies in memory. This assumes an Nginx limit of 10MB,
123
127
  # by using 11MB this ensures that client bodies are always buffered in
@@ -128,12 +132,15 @@ class << Unicorn
128
132
  # Run all worker processes with unique memory layouts
129
133
  worker_exec true
130
134
 
135
+ # :nocov:
131
136
  # Only change the log path if daemonizing.
132
137
  # Otherwise, continue to log to stdout/stderr.
133
138
  if Unicorn::Configurator::RACKUP[:daemonize]
134
- stdout_path "/var/log/unicorn/#{Unicorn.app_name}.log"
135
- stderr_path "/var/log/unicorn/#{Unicorn.app_name}.log"
139
+ log_path = "#{Unicorn.unicorn_lockdown_prefix}/log/unicorn/#{Unicorn.app_name}.log"
140
+ stdout_path log_path
141
+ stderr_path log_path
136
142
  end
143
+ # :nocov:
137
144
 
138
145
  after_fork do |server, worker|
139
146
  server.logger.info("worker=#{worker.nr} spawned pid=#{$$}")
@@ -225,7 +232,7 @@ class << Unicorn
225
232
  unless skip_email
226
233
  # If the request filename exists and the worker process crashed,
227
234
  # send a notification email.
228
- Process.waitpid(fork do
235
+ Process.waitpid(Process.fork do
229
236
  # Load net/smtp early
230
237
  require 'net/smtp'
231
238
 
@@ -234,7 +241,7 @@ class << Unicorn
234
241
  body = File.read(file)
235
242
 
236
243
  # Then use a restrictive pledge
237
- Pledge.pledge('inet prot_exec')
244
+ Pledge.pledge(ENV['UNICORN_LOCKDOWN_WORKER_CRASH_PLEDGE'] || 'inet prot_exec')
238
245
 
239
246
  # If body empty, crash happened before a request was received,
240
247
  # try to at least provide the application name in this case.
@@ -242,8 +249,13 @@ class << Unicorn
242
249
  body = "Subject: [#{Unicorn.app_name}] Unicorn Worker Process Crash\r\n\r\nNo email content provided for app: #{Unicorn.app_name}"
243
250
  end
244
251
 
252
+ # :nocov:
253
+ # Don't verify localhost hostname, to avoid SSL errors raised in newer versions of net/smtp
254
+ smtp_params = Net::SMTP.method(:start).parameters.include?([:key, :tls_verify]) ? {tls_verify: false, tls_hostname: 'localhost'} : {}
255
+ # :nocov:
256
+
245
257
  # Finally send an email to localhost via SMTP.
246
- Net::SMTP.start('127.0.0.1'){|s| s.send_message(body, Unicorn.email, Unicorn.email)}
258
+ Net::SMTP.start('127.0.0.1', **smtp_params){|s| s.send_message(body, Unicorn.email, Unicorn.email)}
247
259
  end)
248
260
  end
249
261
  end
data/lib/unveiler.rb CHANGED
@@ -29,10 +29,14 @@ module Unveiler
29
29
 
30
30
  Pledge.unveil(unveil)
31
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)
32
+ # :nocov:
33
+ if defined?(SimpleCov)
34
+ # :nocov:
35
+ # If running coverage tests, add necessary pledges for
36
+ # coverage testing to work.
37
+ pledge = (pledge.split + %w'rpath wpath cpath flock').uniq.join(' ')
36
38
  end
39
+
40
+ Pledge.pledge(pledge)
37
41
  end
38
42
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unicorn-lockdown
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-09 00:00:00.000000000 Z
11
+ date: 2022-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pledge
@@ -38,6 +38,62 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: mail
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: roda
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest-global_expectations
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
41
97
  description:
42
98
  email: code@jeremyevans.net
43
99
  executables:
@@ -52,6 +108,8 @@ files:
52
108
  - bin/unicorn-lockdown-add
53
109
  - bin/unicorn-lockdown-setup
54
110
  - files/rc.unicorn
111
+ - files/unicorn_lockdown_add.rb
112
+ - files/unicorn_lockdown_setup.rb
55
113
  - lib/rack/email_exceptions.rb
56
114
  - lib/roda/plugins/pg_disconnect.rb
57
115
  - lib/unicorn-lockdown.rb
@@ -59,7 +117,11 @@ files:
59
117
  homepage: https://github.com/jeremyevans/unicorn-lockdown
60
118
  licenses:
61
119
  - MIT
62
- metadata: {}
120
+ metadata:
121
+ bug_tracker_uri: https://github.com/jeremyevans/unicorn-lockdown/issues
122
+ changelog_uri: https://github.com/jeremyevans/unicorn-lockdown/blob/master/CHANGELOG
123
+ mailing_list_uri: https://github.com/jeremyevans/unicorn-lockdown/discussions
124
+ source_code_uri: https://github.com/jeremyevans/unicorn-lockdown
63
125
  post_install_message:
64
126
  rdoc_options: []
65
127
  require_paths:
@@ -68,14 +130,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
68
130
  requirements:
69
131
  - - ">="
70
132
  - !ruby/object:Gem::Version
71
- version: '0'
133
+ version: 2.0.0
72
134
  required_rubygems_version: !ruby/object:Gem::Requirement
73
135
  requirements:
74
136
  - - ">="
75
137
  - !ruby/object:Gem::Version
76
138
  version: '0'
77
139
  requirements: []
78
- rubygems_version: 3.1.4
140
+ rubygems_version: 3.3.7
79
141
  signing_key:
80
142
  specification_version: 4
81
143
  summary: Helper library for running Unicorn with fork+exec/unveil/pledge on OpenBSD