diff options
-rw-r--r-- | Documentation/yahns_config.txt | 6 | ||||
-rw-r--r-- | lib/yahns/config.rb | 4 | ||||
-rw-r--r-- | lib/yahns/server.rb | 17 | ||||
-rw-r--r-- | lib/yahns/server_mp.rb | 2 | ||||
-rw-r--r-- | lib/yahns/worker.rb | 24 | ||||
-rw-r--r-- | test/test_server.rb | 62 |
6 files changed, 86 insertions, 29 deletions
diff --git a/Documentation/yahns_config.txt b/Documentation/yahns_config.txt index 258ca8d..db24cad 100644 --- a/Documentation/yahns_config.txt +++ b/Documentation/yahns_config.txt @@ -384,8 +384,10 @@ Ruby it is running under. Runs application process(es) as the specified USER and GROUP. - The master process always stays running as the user who started it. - This switch will occur after calling the after_fork hook. + If using worker_processes, this only affects the workers and the + master stays running as the user who started it. This switch will + occur before calling the atfork_child hook(s). + GROUP is optional and will not change if unspecified. Default: none (no user switching is done) diff --git a/lib/yahns/config.rb b/lib/yahns/config.rb index 6d8945a..ebc3bda 100644 --- a/lib/yahns/config.rb +++ b/lib/yahns/config.rb @@ -158,7 +158,7 @@ class Yahns::Config # :nodoc: # if the Worker#user method is not called in the after_fork hooks # +group+ is optional and will not change if unspecified. def user(user, group = nil) - var = :user + var = _check_in_block(nil, :user) @block and raise "#{var} is not valid inside #{@block.type}" # raises ArgumentError on invalid user/group Etc.getpwnam(user) @@ -385,7 +385,7 @@ class Yahns::Config # :nodoc: io.sync = true end - [ :logger, :pid, :worker_processes, + [ :logger, :pid, :worker_processes, :user, :worker_atfork_prepare, :worker_atfork_parent, :worker_atfork_child ].each do |var| val = @set[var] diff --git a/lib/yahns/server.rb b/lib/yahns/server.rb index fea310c..a2af745 100644 --- a/lib/yahns/server.rb +++ b/lib/yahns/server.rb @@ -10,6 +10,7 @@ class Yahns::Server # :nodoc: :CHLD ] attr_accessor :daemon_pipe attr_accessor :logger + attr_writer :user attr_writer :worker_processes attr_writer :worker_atfork_prepare attr_writer :worker_atfork_parent @@ -60,10 +61,26 @@ class Yahns::Server # :nodoc: if @worker_processes require 'yahns/server_mp' extend Yahns::ServerMP + else + switch_user(*@user) if @user end self end + def switch_user(user, group = nil) + # we do not protect the caller, checking Process.euid == 0 is + # insufficient because modern systems have fine-grained + # capabilities. Let the caller handle any and all errors. + uid = Etc.getpwnam(user).uid + gid = Etc.getgrnam(group).gid if group + Yahns::Log.chown_all(uid, gid) + if gid && Process.egid != gid + Process.initgroups(user, gid) + Process::GID.change_privilege(gid) + end + Process.euid != uid and Process::UID.change_privilege(uid) + end + def drop_acceptors @listeners.delete_if(&:ac_quit) end diff --git a/lib/yahns/server_mp.rb b/lib/yahns/server_mp.rb index c8e1989..be24152 100644 --- a/lib/yahns/server_mp.rb +++ b/lib/yahns/server_mp.rb @@ -46,7 +46,7 @@ module Yahns::ServerMP # :nodoc: Yahns::START.clear @sev.close @sev = Yahns::Sigevent.new - worker.user(*@user) if @user + switch_user(*@user) if @user @user = @workers = nil @worker_atfork_child.each(&:call) if @worker_atfork_child @worker_atfork_child = @worker_atfork_parent = @worker_atfork_prepare = nil diff --git a/lib/yahns/worker.rb b/lib/yahns/worker.rb index 980f7bd..0d25acc 100644 --- a/lib/yahns/worker.rb +++ b/lib/yahns/worker.rb @@ -31,28 +31,4 @@ class Yahns::Worker # :nodoc: def ==(other_nr) # :nodoc: @nr == other_nr end - - # Changes the worker process to the specified +user+ and +group+ - # This is only intended to be called from within the worker - # process from the +after_fork+ hook. This should be called in - # the +after_fork+ hook after any privileged functions need to be - # run (e.g. to set per-worker CPU affinity, niceness, etc) - # - # Any and all errors raised within this method will be propagated - # directly back to the caller (usually the +after_fork+ hook. - # These errors commonly include ArgumentError for specifying an - # invalid user/group and Errno::EPERM for insufficient privileges - def user(user, group = nil) - # we do not protect the caller, checking Process.euid == 0 is - # insufficient because modern systems have fine-grained - # capabilities. Let the caller handle any and all errors. - uid = Etc.getpwnam(user).uid - gid = Etc.getgrnam(group).gid if group - Yahns::Log.chown_all(uid, gid) - if gid && Process.egid != gid - Process.initgroups(user, gid) - Process::GID.change_privilege(gid) - end - Process.euid != uid and Process::UID.change_privilege(uid) - end end diff --git a/test/test_server.rb b/test/test_server.rb index e49c8ea..96dc546 100644 --- a/test/test_server.rb +++ b/test/test_server.rb @@ -460,4 +460,66 @@ class TestServer < Testcase quit_wait(pid) FileUtils.rm_rf(tmpdir) end + + module MockSwitchUser + def self.included(cls) + cls.__send__(:remove_method, :switch_user) + cls.__send__(:alias_method, :switch_user, :mock_switch_user) + end + + def mock_switch_user(user, group = nil) + $yahns_user = [ $$, user, group ] + end + end + + def test_user_no_workers + refute defined?($yahns_user), "$yahns_user global should be undefined" + err = @err + cfg = Yahns::Config.new + host, port = @srv.addr[3], @srv.addr[1] + cfg.instance_eval do + ru = lambda do |_| + b = $yahns_user.inspect + [ 200, {'Content-Length'=>b.size.to_s }, [b] ] + end + GTL.synchronize { app(:rack, ru) { listen "#{host}:#{port}" } } + user "nobody" + stderr_path err.path + end + pid = mkserver(cfg) { Yahns::Server.__send__(:include, MockSwitchUser) } + expect = [ pid, "nobody", nil ].inspect + run_client(host, port) { |res| assert_equal expect, res.body } + refute defined?($yahns_user), "$yahns_user global should be undefined" + ensure + quit_wait(pid) + end + + def test_user_workers + refute defined?($yahns_user), "$yahns_user global should be undefined" + err = @err + cfg = Yahns::Config.new + host, port = @srv.addr[3], @srv.addr[1] + cfg.instance_eval do + ru = lambda do |_| + b = $yahns_user.inspect + [ 200, {'Content-Length'=>b.size.to_s, 'X-Pid' => "#$$" }, [b] ] + end + GTL.synchronize { app(:rack, ru) { listen "#{host}:#{port}" } } + user "nobody" + stderr_path err.path + worker_processes 1 + end + pid = mkserver(cfg) { Yahns::Server.__send__(:include, MockSwitchUser) } + run_client(host, port) do |res| + worker_pid = res["X-Pid"].to_i + assert_operator worker_pid, :>, 0 + refute_equal pid, worker_pid + refute_equal $$, worker_pid + expect = [ worker_pid, "nobody", nil ].inspect + assert_equal expect, res.body + end + refute defined?($yahns_user), "$yahns_user global should be undefined" + ensure + quit_wait(pid) + end end |