File Coverage

blib/lib/Mojolicious/Plugin/SetUserGroup.pm
Criterion Covered Total %
statement 41 60 68.3
branch 12 30 40.0
condition 4 12 33.3
subroutine 9 9 100.0
pod 1 1 100.0
total 67 112 59.8


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::SetUserGroup;
2 3     3   65964 use Mojo::Base 'Mojolicious::Plugin';
  3         6  
  3         15  
3              
4 3     3   431 use Mojo::IOLoop;
  3         6  
  3         18  
5 3     3   72 use POSIX qw(getuid getgid setuid setgid);
  3         12  
  3         17  
6 3     3   923 use Unix::Groups::FFI 'initgroups';
  3         1832184  
  3         194  
7 3     3   24 use Carp 'croak';
  3         6  
  3         1958  
8              
9             our $VERSION = '1.000';
10              
11             sub register {
12 4     4 1 1585 my ($self, $app, $conf) = @_;
13 4         9 my $user = $conf->{user};
14 4   100     19 my $group = $conf->{group} // $user;
15            
16 4 100       13 return $self unless defined $user;
17            
18             # Make sure desired user and group exist
19 3         7 $! = 0;
20 3 100       573 unless (defined(getpwnam $user)) {
21 1 50       6 croak _error($app, qq{Failed to retrieve user "$user": $!}) if $!;
22 1         5 croak _error($app, qq{User "$user" does not exist});
23             }
24 2         8 $! = 0;
25 2 100       154 unless (defined(getgrnam $group)) {
26 1 50       8 croak _error($app, qq{Failed to retrieve group "$group": $!}) if $!;
27 1         5 croak _error($app, qq{Group "$group" does not exist});
28             }
29            
30 1     1   13 Mojo::IOLoop->next_tick(sub { _setusergroup($app, $user, $group) });
  1         2099  
31             }
32              
33             sub _error {
34 2     2   5 my ($app, $error) = @_;
35 2         4 chomp $error;
36 2         8 $app->log->fatal($error);
37 2 50       300 Mojo::IOLoop->stop if Mojo::IOLoop->is_running;
38 2         342 return $error;
39             }
40              
41             sub _setusergroup {
42 1     1   3 my ($app, $user, $group) = @_;
43            
44             # User and group IDs
45 1         2 my ($uid, $gid);
46 1         2 $! = 0;
47 1 50       44 unless (defined($uid = getpwnam $user)) {
48 0 0       0 die _error($app, qq{Failed to retrieve user "$user": $!}) if $!;
49 0         0 die _error($app, qq{User "$user" does not exist});
50             }
51 1         3 $! = 0;
52 1 50       33 unless (defined($gid = getgrnam $group)) {
53 0 0       0 die _error($app, qq{Failed to retrieve group "$group": $!}) if $!;
54 0         0 die _error($app, qq{Group "$group" does not exist});
55             }
56            
57             # Check if user and group are already correct
58 1 50 33     28 return if getuid() == $uid and getgid() == $gid;
59            
60 0           my $rc = setgid($gid);
61 0 0 0       unless (defined $rc and $rc == 0) { die _error($app, qq{Can't switch to group "$group": $!}); }
  0            
62 0           my $error;
63 0 0         { local $@; unless (eval { initgroups($user, $gid); 1 }) { $error = "$!" } }
  0            
  0            
  0            
  0            
  0            
64 0 0         if (defined $error) { die _error($app, qq{Can't set supplemental groups for user "$user": $error}); }
  0            
65 0           $rc = setuid($uid);
66 0 0 0       unless (defined $rc and $rc == 0) { die _error($app, qq{Can't switch to user "$user": $!}); }
  0            
67             }
68              
69             1;
70              
71             =head1 NAME
72              
73             Mojolicious::Plugin::SetUserGroup - Mojolicious plugin to set unprivileged
74             credentials
75              
76             =head1 SYNOPSIS
77              
78             # Mojolicious
79             $self->plugin(SetUserGroup => {user => $user, group => $group});
80            
81             # Mojolicious::Lite
82             plugin SetUserGroup => {user => $user, group => $group};
83            
84             # Production mode only
85             plugin SetUserGroup => {user => $user, group => $group}
86             if $self->mode eq 'production';
87            
88             # Root only
89             plugin SetUserGroup => {user => $user, group => $group}
90             if $> == 0;
91              
92             =head1 DESCRIPTION
93              
94             This plugin is intended to replace the C functionality of
95             L. It should be loaded in application startup and it will change
96             the user and group credentials of the process when L is started,
97             which occurs in each worker process of a L daemon like
98             L.
99              
100             This allows an application to be started as root so it can bind to privileged
101             ports such as port 80 or 443, but run worker processes as unprivileged users.
102             However, if the application is not started as root, it will most likely fail to
103             change credentials. So, you should only set the user/group when the application
104             is started as root or a user with the C and C
105             L.
106              
107             This plugin only works on systems with a concept of Unix users and groups, such
108             as Linux, OS X, or BSD.
109              
110             The L development server is currently incompatible with this plugin as
111             the lowered credentials causes the application worker to shut down. Make sure
112             credential changes do not occur when running your application under morbo,
113             either by not registering the plugin under that condition, or starting morbo
114             under the target user and group so no change occurs.
115              
116             =head1 METHODS
117              
118             L inherits all methods from
119             L and implements the following new ones.
120              
121             =head2 register
122              
123             $plugin->register(Mojolicious->new, {user => $user, group => $group});
124              
125             Install callback to change process credentials on the next L
126             tick. If option C is undefined or the current user and group are already
127             correct, no credential change will occur. If option C is undefined but
128             C is defined, the group will be set to a group matching the user name. If
129             credential changes fail, an error will be logged and the process will be
130             stopped.
131              
132             =head1 AUTHOR
133              
134             Dan Book, C
135              
136             =head1 CONTRIBUTORS
137              
138             =over
139              
140             =item Jan Henning Thorsen (jhthorsen)
141              
142             =item Lee Johnson (leejo)
143              
144             =back
145              
146             =head1 COPYRIGHT AND LICENSE
147              
148             Copyright 2015, Dan Book.
149              
150             This library is free software; you may redistribute it and/or modify it under
151             the terms of the Artistic License version 2.0.
152              
153             =head1 SEE ALSO
154              
155             L, L, L