Support for running services as a different user (run-as=...).
authorDavin McCall <davmac@davmac.org>
Sat, 20 Jan 2018 20:42:11 +0000 (20:42 +0000)
committerDavin McCall <davmac@davmac.org>
Sat, 20 Jan 2018 20:42:11 +0000 (20:42 +0000)
README
doc/manpages/dinit.8
src/baseproc-service.cc
src/includes/proc-service.h
src/includes/service.h
src/load_service.cc
src/run-child-proc.cc
src/tests/test-run-child-proc.cc

diff --git a/README b/README
index 9d3fbd1f6481e44c84d40e29c04096235c881165..925d8d97e51bc27d8ec0d092695abbf9e88e4707 100644 (file)
--- a/README
+++ b/README
@@ -135,11 +135,16 @@ Parameter values are interpreted literally, except that:
    non-collapsing whitespace, double-quote marks, and backslashes in the
    parameter value.
 
-Parameters are:
+Some available parameters are:
 
 type = process | bgprocess | scripted | internal
 command = ...
+stop-command = ...
+run-as = (user-id)
+restart = (boolean)
+smooth-recovery = (boolean)
 logfile = ...
+pid-file = ...
 options = ...
 depends-on = (service name)
 depends-ms = (service name)
@@ -152,6 +157,11 @@ command = (external script or executable, and arguments)
 stop-command = (external script or executable, and arguments)
    For a 'scripted' service, this command is run to stop the service.
 
+run-as = (user-id)
+   Specifies which user to run the process(es) for this service as. The group
+   id for the process will also be set to the primary group of the specified
+   user.
+
 restart = yes | true | no | false
    Specifies whether the service should automatically restart if it becomes
    stopped (for any reason, including being explicitly requested to stop).
@@ -165,36 +175,9 @@ smooth-recovery = yes | true | no | false
    the service does not reach the stopped state when the process terminates
    unexpectedly).
 
-restart-delay = XXX.YYYY
-   Specifies the minimum time in seconds between automatic restarts. The
-   default is 0.2 (i.e. 200ms). This prevents Dinit from consuming processor
-   cycles when a process continuously fails immediately after it starts.
-
-restart-limit-interval = XXX.YYYY
-   Specifies the interval, in seconds, over which restarts are limited. If a
-   process automatically restarts more than a certain number of times (default
-   3) in this time interval, it will not restart again. The default value is
-   10 seconds. Use this to prevent broken services from continuously
-   restarting ad infinitum.
-
-restart-limit-count = NNN
-   Specifies the maximum number of times that a service can automatically
-   restart over the interval specified by restart-limit-interval (default of
-   10 seconds). Specify a value of 0 to disable the restart limit.
-
-stop-timeout = XXX.YYYY   (or XXX,YYYY)
-   Specifies the time in seconds allowed for the service to stop. If the
-   service takes longer than this, its process group is sent a SIGKILL signal
-   which should cause it to terminate immediately. The timeout period begins
-   only when all dependent services have already stopped. The default stop
-   timeout is 10 seconds.
-
-start-timeout = XXX.YYYY  (or XXX,YYYY)
-   Specifies the time in seconds allowed for the service to start. IF the
-   service startup takes longer than this, its process group is sent a
-   SIGINT signal and transitions to the "stopping" state. if it fails to stop
-   within the period specified by the stop-timeout setting, it is sent a
-   SIGKILL signal.
+logfile = (log file path)
+   Specifies the log file for the service. Output from the service process
+   will go this file.
 
 pid-file = (path to file)
    For "bgprocess" type services only; specifies the path of the file where
@@ -219,35 +202,6 @@ waits-for = (service name)
    for this service. Starting this service will automatically start
    the named service.
 
-socket-listen = (socket path)
-   Pre-open a socket for the service and pass it to the service using the
-   Systemd activation protocol. This by itself does not give so called
-   "socket activation", but does allow that any process trying to connect
-   to the specified socket will be able to do so, even before the service
-   is properly prepared to accept connections.
-
-socket-permissions = (octal permissions mask)
-   Gives the permissions for the socket specified using socket-listen.
-   Normally this will be 600 (user access only), 660 (user and group
-   access), or 666 (all users).
-
-socket-uid = (numeric user id or username)
-   Specifies the user that should own the activation socket. If socket-uid
-   is specified without also specifying socket-gid, then the socket group
-   is the primary group of the specified user (as found in the system user
-   database, normally /etc/passwd). If the socket owner is not specified,
-   the socket will be owned by the user id of the Dinit process.
-
-socket-gid = (numeric group id or group name)
-   Specifies the group of the activation socket. See discussion of
-   socket-uid.
-
-termsignal = HUP | INT | QUIT | USR1 | USR2
-   Specifies an additional signal to send to the process when requesting it
-   to terminate (applies to 'process' services only). SIGTERM is always
-   sent along with the specified signal, unless the 'nosigterm' setting is
-   set true.
-
 options = ( runs-on-console | nosigterm | starts-rwfs | starts-log ) ...
   Specifies various options for this service:
 
@@ -302,9 +256,7 @@ options = ( runs-on-console | nosigterm | starts-rwfs | starts-log ) ...
               startup. This is meaningful only for scripted and bgprocess
               services. 
 
-logfile = (log file path)
-   Specifies the log file for the service. Output from the service process
-   will go this file.
+Please see the manual page for a full list of service parameters and options.
 
 
 Controlling services
index 4a477a86cf5030c73631fd7e865e14cfa69c1b7f..099cb4a428b4252aac6f152736c186e9aa551207 100644 (file)
@@ -118,6 +118,10 @@ services.
 Specifies the command to stop the service. Applicable only to \fBscripted\fR
 services.
 .TP
+\fBrun-as\fR = \fIuser-id\fR
+Specifies which user to run the process(es) for this service as. The group id
+for the process will also be set to the primary group of the specified user.
+.TP
 \fBrestart\fR = {yes | true | no | false}
 Indicates whether the service should automatically restart if it stops for
 any reason (including unexpected process termination, service dependency
index 14933a5cc99b11c65aea862d087816f863a515f7..481e9f61bc20fbcdeb22bf418031fe9926f68067 100644 (file)
@@ -121,7 +121,8 @@ bool base_process_service::start_ps_process(const std::vector<const char *> &cmd
     }
 
     if (forkpid == 0) {
-        run_child_proc(cmd.data(), logfile, on_console, pipefd[1], control_socket[1], socket_fd);
+        run_child_proc(cmd.data(), logfile, on_console, pipefd[1], control_socket[1], socket_fd,
+                run_as_uid, run_as_gid);
     }
     else {
         // Parent process
index 8074e8a31fc1f1461baefcb627267581510879ab..606c5b15e28d809cd8373c7b544403018590b7ff 100644 (file)
@@ -1,3 +1,5 @@
+#include <sys/types.h>
+
 #include "service.h"
 
 // Given a string and a list of pairs of (start,end) indices for each argument in that string,
@@ -22,6 +24,7 @@ class process_restart_timer : public eventloop_t::timer_impl<process_restart_tim
     dasynq::rearm timer_expiry(eventloop_t &, int expiry_count);
 };
 
+// Base class for process-based services.
 class base_process_service : public service_record
 {
     friend class service_child_watcher;
@@ -60,6 +63,9 @@ class base_process_service : public service_record
     // <stop_timeout>). 0 to disable.
     time_val start_timeout = {60, 0}; // default of 1 minute
 
+    uid_t run_as_uid = -1;
+    gid_t run_as_gid = -1;
+
     pid_t pid = -1;  // PID of the process. If state is STARTING or STOPPING,
                      //   this is PID of the service script; otherwise it is the
                      //   PID of the process itself (process service).
@@ -172,10 +178,18 @@ class base_process_service : public service_record
         this->term_signal = signo;
     }
 
+    // Set the uid/gid that the service process will be run as
+    void set_run_as_uid_gid(uid_t uid, gid_t gid) noexcept
+    {
+        run_as_uid = uid;
+        run_as_gid = gid;
+    }
+
     // The restart/stop timer expired.
     void timer_expired() noexcept;
 };
 
+// Standard process service.
 class process_service : public base_process_service
 {
     virtual void handle_exit_status(int exit_status) noexcept override;
@@ -197,6 +211,7 @@ class process_service : public base_process_service
     }
 };
 
+// Bgproc (self-"backgrounding", i.e. double-forking) process service
 class bgproc_service : public base_process_service
 {
     virtual void handle_exit_status(int exit_status) noexcept override;
@@ -226,6 +241,7 @@ class bgproc_service : public base_process_service
     }
 };
 
+// Service which is started and stopped via separate commands
 class scripted_service : public base_process_service
 {
     virtual void handle_exit_status(int exit_status) noexcept override;
index 1b2bd4efd7c15df4518cce27a07ce97ca905da8c..64da4bca3434ad6a0b17990dc85ed2c3a4bdbf67 100644 (file)
@@ -330,8 +330,17 @@ class service_record
     //   dep_failed: whether failure is recorded due to a dependency failing
     void failed_to_start(bool dep_failed = false) noexcept;
 
+    // Run a child process (call after forking).
+    // - args specifies the program arguments including the executable (argv[0])
+    // - logfile specifies the logfile
+    // - on_console: if true, process is run with access to console
+    // - wpipefd: if the exec is unsuccessful, or another error occurs beforehand, the
+    //   error number (errno) is written to this file descriptor
+    // - csfd: the control socket fd; may be -1 to inhibit passing of control socket
+    // - socket_fd: the pre-opened socket file descriptor (may be -1)
+    // - uid/gid: the identity to run the process as (may be both -1, otherwise both must be valid)
     void run_child_proc(const char * const *args, const char *logfile, bool on_console, int wpipefd,
-            int csfd, int socket_fd) noexcept;
+            int csfd, int socket_fd, uid_t uid, gid_t gid) noexcept;
     
     // A dependency has reached STARTED state
     void dependency_started() noexcept;
index 82dae1a701f5db640649fdf392ad0417bede28a3..59322ea6160d691ee50663d9975054b3fe2c78e5 100644 (file)
@@ -411,6 +411,9 @@ service_record * dirload_service_set::load_service(const char * name)
     timespec stop_timeout = { .tv_sec = 10, .tv_nsec = 0 };
     timespec start_timeout = { .tv_sec = 60, .tv_nsec = 0 };
     
+    uid_t run_as_uid = -1;
+    gid_t run_as_gid = -1;
+
     string line;
     ifstream service_file;
     service_file.exceptions(ios::badbit | ios::failbit);
@@ -586,6 +589,10 @@ service_record * dirload_service_set::load_service(const char * name)
                     string starttimeout_str = read_setting_value(i, end, nullptr);
                     parse_timespec(starttimeout_str, name, "start-timeout", start_timeout);
                 }
+                else if (setting == "run-as") {
+                    string run_as_str = read_setting_value(i, end, nullptr);
+                    run_as_uid = parse_uid_param("run-as", name, &run_as_gid);
+                }
                 else {
                     throw service_description_exc(name, "Unknown setting: " + setting);
                 }
@@ -614,6 +621,7 @@ service_record * dirload_service_set::load_service(const char * name)
                     rvalps->set_start_timeout(start_timeout);
                     rvalps->set_start_interruptible(start_is_interruptible);
                     rvalps->set_extra_termination_signal(term_signal);
+                    rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid);
                     rval = rvalps;
                 }
                 else if (service_type == service_type_t::BGPROCESS) {
@@ -626,6 +634,7 @@ service_record * dirload_service_set::load_service(const char * name)
                     rvalps->set_start_timeout(start_timeout);
                     rvalps->set_start_interruptible(start_is_interruptible);
                     rvalps->set_extra_termination_signal(term_signal);
+                    rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid);
                     rval = rvalps;
                 }
                 else if (service_type == service_type_t::SCRIPTED) {
@@ -636,6 +645,7 @@ service_record * dirload_service_set::load_service(const char * name)
                     rvalps->set_start_timeout(start_timeout);
                     rvalps->set_start_interruptible(start_is_interruptible);
                     rvalps->set_extra_termination_signal(term_signal);
+                    rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid);
                     rval = rvalps;
                 }
                 else {
index 66f51994f45663d0633e5479f9c6e64bc6806deb..c1c43bb88056f2517aa94f500a5f77c3ee22ecad 100644 (file)
@@ -10,7 +10,7 @@
 #include "service.h"
 
 void service_record::run_child_proc(const char * const *args, const char *logfile, bool on_console,
-        int wpipefd, int csfd, int socket_fd) noexcept
+        int wpipefd, int csfd, int socket_fd, uid_t uid, gid_t gid) noexcept
 {
     // Child process. Must not allocate memory (or otherwise risk throwing any exception)
     // from here until exit().
@@ -55,7 +55,7 @@ void service_record::run_child_proc(const char * const *args, const char *logfil
     }
 
     if (socket_fd != -1) {
-
+        // If we passing a pre-opened socket, it has to be fd number 3. (Thanks, systemd).
         if (dup2(socket_fd, 3) == -1) goto failure_out;
         if (socket_fd != 3) {
             close(socket_fd);
@@ -118,6 +118,11 @@ void service_record::run_child_proc(const char * const *args, const char *logfil
         tcsetpgrp(0, getpgrp());
     }
 
+    if (uid != -1) {
+        if (setreuid(uid, uid) != 0) goto failure_out;
+        if (setregid(gid, gid) != 0) goto failure_out;
+    }
+
     sigprocmask(SIG_SETMASK, &sigwait_set, nullptr);
 
     execvp(args[0], const_cast<char **>(args));
index db4dc32fd8dc301751e86c406a2591ccf04bd386..2817cc64986072218a7d9174846e7f8f05f4ba62 100644 (file)
@@ -3,7 +3,7 @@
 // Stub out run_child_proc function, for testing purposes.
 
 void service_record::run_child_proc(const char * const *args, const char *logfile, bool on_console,
-        int wpipefd, int csfd, int socket_fd) noexcept
+        int wpipefd, int csfd, int socket_fd, uid_t uid, gid_t gid) noexcept
 {
 
 }