Group Authentication For Nginx
The access control system of Nginx lacks an equivalent for Apache's group based access control. But it is easy to emulate this Apache feature with a little bit of scripting.
In order to make the browser pop up an ugly username and password dialog before users can enter your site, you have to configure something like this:
location /protected {
auth_basic "My Own Private NSA";
auth_basic_user_file /path/to/auth/htusers;
}
The password file /path/to/auth/htusers
can be created with the Apache htpasswd tool or with the python script htpasswd.py. The result will look like this:
$ cat /path/to/auth/htusers
hihosilver:$apr1$7iHB6eAM$bhrCoWXyXVDHXYo4aXZlf.
tom:$apr1$k03jYOif$hcf8rXosgreIz0v5oAtqD0
dick:$apr1$ZtrDNWP4$1G7sOMKOGZuBYdCu37bSX.
harry:$apr1$Lv1OSEIx$HHNvUurRBs/IRtSuYGXMh/
superuser:$apr1$wh3lz3Zw$TW4BO3mhEcWkFwcx.fZ760
For Apache the same protection can be set up like this:
<Location /protected>
AuthType Basic
AuthName "My Own Private NSA"
AuthUserFile "/path/to/auth/htusers"
Require valid-user
</Location>
The directive Require valid-user
means "any user that can be found in AuthUserFile
".
Group Based Authentication, the Simple Way
But what if there is an area /protected/admin
that should only be accessible for the users hihosilver
and superuser
? In Apache that is a piece of cake:
<Location /protected/admin>
AuthType Basic
AuthName "My Own Private NSA"
AuthUserFile "/path/to/auth/htusers"
AuthGroupFile "/path/to/auth/htgroups"
Require group admin
</Location>
There is now a group file (line 5) and not every user but only users from the group admin
are granted access. The group file can be created with any text editor and looks like this:
$ cat /path/to/auth/htgroups
admin: superuser hihosilver
users: superuser hihosilver tom dick harry
Only superuser
and hihosilver
are members of the group admin
and only they are allowed in.
Unfortunately, nginx does not know about groups. The only thing we can do is create a second password file:
location /protected/admin {
auth_basic "My Own Private NSA";
auth_basic_user_file /path/to/auth/admin.users;
}
Same as above for /protected
but we specify a different password file (line 3). We could create second the password file like this:
$ cd /path/to/auth
$ grep 'hihosilver|superuser:' htusers >admin.users
That copies the password information for the users hihosilver
and superuser
. Unfortunately, it also copies lines that contain one of these strings in the password digest (or the user name). The regular expression has to be less permissive:
$ cd /path/to/auth
$ grep -E '^(hihosilver|superuser):' htusers >admin.users
This will be sufficient for many setups. Be sure though to use the same authentication realm (that is the keyword auth_basic
in the nginx configuration) for all areas. Otherwise, admin users will be prompted again for the password, when they visit the admin area.
Group Based Authentication, the Comfortable Way
The solution with grep will soon become impractical if you have more groups and more areas to protect. An apache-style group file would definitely come in handy and you can have that for nginx as well:
Download the script nginx-groups.pl and save it in the directory /path/to/auth
. Create the group file htgroups
(see above) and run the script:
$ perl nginx-groups.pl htusers htgroups
Writing users file 'admin.users'.
Writing users file 'users.users'.
The script reads the files with the user and group information and generates one file GROUP.users
for each group GROUP.
The script is very basic and limited. For example, it always writes the output files into the current working directory, the naming scheme GROUP.users
is hard-coded but it does the job it is supposed to do.
Feel free to change it to your individual needs. It is easy to understand:
#! /usr/bin/env perl
use strict;
die "Usage: $0 USERSFILE GROUPSFILE\n" unless @ARGV == 2;
my ($users_file, $groups_file) = @ARGV;
my %users;
open my $fh, "<$users_file" or die "cannot open '$users_file': $!\n";
while (my $line = <$fh>) {
chomp $line;
my ($name, $password) = split /:/, $line, 2;
next if !defined $password;
$users{$name} = $line;
}
open my $fh, "<$groups_file" or die "cannot open '$groups_file': $!\n";
while (my $line = <$fh>) {
my ($name, $members) = split /:/, $line, 2 or next;
next if !defined $members;
$name =~ s/[ \t]//g;
next if $name eq '';
my @members = grep { length $_ && exists $users{$_} }
split /[ \t\r\n]+/, $members;
my $groups_users_file = $name . '.users';
print "Writing users file '$groups_users_file'.\n";
open my $wh, ">$groups_users_file"
or die "Cannot open '$groups_users_file' for writing: $!\n";
foreach my $user (@members) {
print $wh "$users{$user}\n"
or die "Cannot write to '$groups_users_file': $!\n";
}
close $wh or die "Cannot close '$groups_users_file': $!\n";
}
In lines 8 to 15, the user file is read and each valid line is stored in a hash (dictionary, associative array or whatever you call it) using the user name as the key.
Then the groups file is read in a similar manner, and (line 30) a dedicated password file for each group is written. It is important that a password file is also (over)written, when the group has no members.
Otherwise a stale password file would be used by nginx granting unwanted access.
Leave a comment