File Coverage

blib/lib/Mojolicious/Plugin/SetUserGroup.pm
Criterion Covered Total %
statement 35 63 55.5
branch 8 30 26.6
condition 3 6 50.0
subroutine 8 11 72.7
pod 1 1 100.0
total 55 111 49.5


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::SetUserGroup;
2 2     2   2150 use Mojo::Base 'Mojolicious::Plugin';
  2         2  
  2         12  
3              
4 2     2   326 use List::Util 'any';
  2         3  
  2         132  
5 2     2   14 use Mojo::IOLoop;
  2         2  
  2         20  
6 2     2   41 use POSIX qw(setuid setgid);
  2         2  
  2         11  
7 2     2   507 use Unix::Groups 'setgroups';
  2         375  
  2         92  
8 2     2   7 use Carp 'croak';
  2         2  
  2         1285  
9              
10             our $VERSION = '0.004';
11              
12             sub register {
13 3     3 1 1296 my ($self, $app, $conf) = @_;
14 3         4 my $user = $conf->{user};
15 3   100     13 my $group = $conf->{group} // $user;
16            
17 3 100       7 return $self unless defined $user;
18            
19             # Make sure desired user and group exist
20 2         4 $! = 0;
21 2 100       405 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       83 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   3 my ($app, $error) = @_;
36 2         3 chomp $error;
37 2         7 $app->log->fatal($error);
38 2 50       351 Mojo::IOLoop->stop if Mojo::IOLoop->is_running;
39 2         324 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         return _error($app, qq{Failed to retrieve user "$user": $!}) if $!;
50 0           return _error($app, qq{User "$user" does not exist});
51             }
52 0           $! = 0;
53 0 0         unless (defined($gid = getgrnam $group)) {
54 0 0         return _error($app, qq{Failed to retrieve group "$group": $!}) if $!;
55 0           return _error($app, qq{Group "$group" does not exist});
56             }
57            
58             # Secondary groups
59 0           my @gids = ($gid);
60 0           my @groups = ($group);
61 0           $! = 0;
62 0           while (my ($name, undef, $id, $members) = getgrent) {
63 0 0 0 0     if ($id != $gid and any { $_ eq $user } split ' ', $members) {
  0            
64 0           push @gids, $id;
65 0           push @groups, $name;
66             }
67 0           } continue { $! = 0; }
68             # Empty list may indicate getgrent is done, or an error
69 0 0         return _error($app, qq{Failed to read groups: $!}) if $!;
70            
71 0 0         unless (setgid($gid) == 0) { return _error($app, qq{Can't switch to group "$group": $!}); }
  0            
72 0 0         unless (setgroups(@gids)) { return _error($app, qq{Can't set supplemental groups "@groups": $!}); }
  0            
73 0 0         unless (setuid($uid) == 0) { return _error($app, qq{Can't switch to user "$user": $!}); }
  0            
74             }
75              
76             1;
77              
78             =head1 NAME
79              
80             Mojolicious::Plugin::SetUserGroup - Mojolicious plugin to set unprivileged
81             credentials
82              
83             =head1 SYNOPSIS
84              
85             # Mojolicious
86             $self->plugin(SetUserGroup => {user => $user, group => $group});
87            
88             # Mojolicious::Lite
89             plugin SetUserGroup => {user => $user, group => $group};
90            
91             # Production mode only
92             plugin SetUserGroup => {user => $user, group => $group}
93             if $self->mode eq 'production';
94            
95             # Root only
96             plugin SetUserGroup => {user => $user, group => $group}
97             if $< == 0 or $> == 0;
98              
99             =head1 DESCRIPTION
100              
101             This plugin is intended to replace the C functionality of
102             L. It should be loaded in application startup and it will change
103             the user and group credentials of the process when L is started,
104             which occurs in each worker process of a L daemon like
105             L.
106              
107             This allows an application to be started as root so it can bind to privileged
108             ports such as port 80 or 443, but run worker processes as unprivileged users.
109             However, if the application is not started as root, it will most likely fail to
110             change credentials. So, you should only set the user/group when the application
111             is started as root.
112              
113             This module requires L and thus will only work on Unix-like
114             systems like Linux, OS X, and BSD.
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, no credential change will occur. If
127             option C is undefined but C is defined, the group will be set to a
128             group matching the user name. If credential changes fail, an error will be
129             logged and the process will be stopped.
130              
131             =head1 AUTHOR
132              
133             Dan Book, C
134              
135             =head1 CONTRIBUTORS
136              
137             =over
138              
139             =item Jan Henning Thorsen (jhthorsen)
140              
141             =item Lee Johnson (leejo)
142              
143             =back
144              
145             =head1 COPYRIGHT AND LICENSE
146              
147             Copyright 2015, Dan Book.
148              
149             This library is free software; you may redistribute it and/or modify it under
150             the terms of the Artistic License version 2.0.
151              
152             =head1 SEE ALSO
153              
154             L, L, L