unicorn-lockdown 0.12.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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