unicorn-lockdown 0.12.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.
@@ -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/requests'
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(0700, request_dir)
35
- File.chown(root_id, daemon_id, request_dir)
36
- end
37
-
38
- # Setup sockets directory to hold PostgreSQL database 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
@@ -8,32 +8,31 @@ class Roda
8
8
  # the QUIT signal, allowing Unicorn to finish handling the current request before exiting.
9
9
  #
10
10
  # This is designed to be used with applications that cannot connect to the database
11
- # after application initialization, either because they are using chroot and the database
12
- # connection socket is outside the chroot, or because they are using a firewall and access
13
- # to the database server is not allowed from the application the process is running as after
14
- # privileges are dropped.
11
+ # after application initialization, either because they are restricting access to the database
12
+ # socket using unveil, or because they are using a firewall and access to the database server is
13
+ # not allowed from the application the process is running as after privileges are dropped.
15
14
  #
16
15
  # This plugin must be loaded before the roda error_handler plugin, and it assumes usage of the
17
16
  # Sequel database library with the postgres adapter and pg driver.
18
17
  module PgDisconnect
19
18
  def self.load_dependencies(app)
20
- 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)
21
20
  end
22
21
 
23
22
  module InstanceMethods
24
- # When database connection is lost, kill the worker process, so a new one will be generated.
25
- # This is necessary because the unix socket used by the database connection is no longer available
26
- # once the application is chrooted.
23
+ # :nocov:
24
+ # Handle old Roda dispatch API
27
25
  def call
28
26
  super
29
27
  rescue Sequel::DatabaseDisconnectError, Sequel::DatabaseConnectionError, PG::ConnectionBad
30
28
  Process.kill(:QUIT, $$)
31
29
  raise
32
30
  end
31
+ # :nocov:
33
32
 
34
33
  # When database connection is lost, kill the worker process, so a new one will be generated.
35
34
  # This is necessary because the unix socket used by the database connection is no longer available
36
- # once the application is chrooted.
35
+ # once the application is unveiled or pledged.
37
36
  def _roda_handle_main_route
38
37
  super
39
38
  rescue Sequel::DatabaseDisconnectError, Sequel::DatabaseConnectionError, PG::ConnectionBad