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
         |