unicorn-lockdown 1.0.0 → 1.1.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 +10 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -3
- data/bin/unicorn-lockdown-add +1 -221
- data/bin/unicorn-lockdown-setup +1 -67
- data/files/unicorn_lockdown_add.rb +227 -0
- data/files/unicorn_lockdown_setup.rb +74 -0
- data/lib/rack/email_exceptions.rb +6 -1
- data/lib/roda/plugins/pg_disconnect.rb +4 -4
- data/lib/unicorn-lockdown.rb +19 -7
- data/lib/unveiler.rb +8 -4
- metadata +67 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f0b84f88c8502c942f5b15b50bb7ac6946de87ef31ee14cf96e9d71b569b8a3
|
4
|
+
data.tar.gz: 69d55260a85368464b5dbb0b9b24120eacb3d9f95f07c4be765f8d913b8e7f2d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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 :: (
|
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
|
|
data/bin/unicorn-lockdown-add
CHANGED
@@ -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'
|
data/bin/unicorn-lockdown-setup
CHANGED
@@ -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
|
-
|
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
|
-
#
|
24
|
-
#
|
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
|
data/lib/unicorn-lockdown.rb
CHANGED
@@ -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
|
-
"/
|
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 "/
|
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
|
-
|
135
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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.
|
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:
|
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:
|
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.
|
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
|