File Coverage

blib/lib/Mojolicious/Plugin/SetUserGroup.pm
Criterion Covered Total %
statement 35 66 53.0
branch 8 32 25.0
condition 3 15 20.0
subroutine 8 11 72.7
pod 1 1 100.0
total 55 125 44.0


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