authorSergey Poznyakoff <>2014-07-29 17:07:44 (GMT)
committer Sergey Poznyakoff <>2014-07-29 17:07:44 (GMT)
commitb73431421025660d28198955c15356c086f5223f (patch) (side-by-side diff)
parentd745c522f462e01aa576c6f93b94cbad6c631f73 (diff)
Separate rules for the same user name coming from different IP addresses.
* Makefile.PL: Add Net::CIDR to the list of prerequisites. * gitaclhook: Document user@CIDRLIST syntax and the hooks.acl.ip-env-var variable. * lib/ Use Net::CIDR (match_host): New sub. (match_user): Check IP against cidr part (if defined). (new): Get remote IP address from environment.
Diffstat (more/less context) (ignore whitespace changes)
3 files changed, 47 insertions, 10 deletions
diff --git a/Makefile.PL b/Makefile.PL
index db3859d..3f18606 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -27,10 +27,11 @@ WriteMakefile(
'LICENSE' => 'gpl',
'FIRST_MAKEFILE' => 'Makefile',
'VERSION' => '1.00',
'PM' => \%pm,
'EXE_FILES' => [ 'gitaclhook' ],
'PREREQ_PM' => { 'Getopt::Long' => 2.34,
- 'File::Spec' => 3.39 }
+ 'File::Spec' => 3.39,
+ 'Net::CIDR' => 0.17 }
diff --git a/gitaclhook b/gitaclhook
index b88adf3..ed6c390 100755
--- a/gitaclhook
+++ b/gitaclhook
@@ -83,20 +83,29 @@ The name of the project. It is obtained by removing the directory
and suffix parts of the repository pathname. Thus, if the repository
is located in B</var/gitroot/foobar.git>, then the corresponding name of
the project is B<foobar>.
An asterisk matches any project name.
-=item I<USER>
+=item I<USER>[B<@>I<CIDRLIST>]
Name of the user. The word B<all> stands for any user, the word B<none>
matches no one at all. Otherwise, if this part begins with a percent
sign (B<%>), the rest of characters are treated as the name of the UNIX
group to check and the rule matches any user in that group. Otherwise,
the literal match is assumed.
+The I<CIDRLIST> part, if present, restricts the rule to users coming from
+IP addresses that match one of the elements in the list. I<CIDRLIST> is a
+comma-separated list of IP addresses, ranges or CIDRs. An IP range is
+defined as two IP addresses separated by a minus sign. A CIDR is defined as
+network address, followed by a slash and length of the netmask in decimal.
+For example:
+ gray@,
The optional parts are:
=over 4
@@ -182,13 +191,13 @@ The project this entry applies to.
=item B<gitAclVerb> [mandatory]
The control verb.
=item B<gitAclUser> [optional]
-The user name or group (B<%>I<GROUPNAME>) specification.
+The user name specification (see description in the B<ACL FILE> section).
=item B<gitAclOp> [optional]
The list of operation codes.
=item B<gitAclRef> [optional]
@@ -218,50 +227,59 @@ all project names.
If no matching entry is found, the update is allowed.
=over 4
-=item B<hooks.acl.type> STRING
+=item B<hooks.acl.type> I<STRING>
Type of the storage engine. Valid values are B<File> (default) and B<LDAP>.
-=item B<hooks.acl.file> STRING
+=item B<hooks.acl.file> I<STRING>
For the B<File> storage engine, name of the ACL file.
-=item B<hooks.acl.ldapconf> STRING
+=item B<hooks.acl.ldapconf> I<STRING>
For the B<LDAP> storage engine, the name of the configuration file to use
instead of B</etc/ldap.conf>.
-=item B<hooks.acl.log> STRING
+=item B<hooks.acl.log> I<STRING>
Send log info to this file.
-=item B<hooks.acl.debug> NUMBER
+=item B<hooks.acl.debug> I<NUMBER>
Enable debugging. The bigger the number, the more debugging info will
be displayed.
-=item B<hooks.acl.quiet> BOOL
+=item B<hooks.acl.quiet> I<BOOL>
Suppress diagnostics on stderr.
=item B<hooks.acl.default> B<allow>|B<deny>
Sets the default rule, i.e. the one that will be executed if no other
rule matched the request. Unless defined, B<deny> is assumed.
-=item B<hooks.acl.httpd-user> STRING
+=item B<hooks.acl.httpd-user> I<STRING>
Name of the user httpd runs as. Define it if the repository can be
accessed via HTTP(S). If B<gitaclhook> is run as this user, it will
get the name of the user on behalf of which the update is performed
from the environment variable B<REMOTE_USER>.
+=item B<hooks.acl.ip-env-var> I<STRING>
+Name of the environment variable from where to retrieve remote IP address.
+Default is B<REMOTE_ADDR>, if B<hooks.acl.httpd-user> is defined and current
+user matches it, and B<SSH_CLIENT> otherwise.
+The part of the string up to the first space character (if any) is taken as
+the IP address.
=head1 TEST MODE
The B<--test> (B<-t>) option provides a mechanism for testing access control
lists from the command line. The syntax is:
diff --git a/lib/ b/lib/
index cde9624..4214b3f 100644
--- a/lib/
+++ b/lib/
@@ -15,12 +15,13 @@
# along with gitaclhook. If not, see <>.
package GitACL;
use strict;
use File::Spec;
+use Net::CIDR qw (cidrlookup);
my %opstr = ('C' => 'create',
'D' => 'delete',
'U' => 'update',
'R' => 'rewind/rebase');
@@ -113,16 +114,26 @@ sub match_primary_group($$) {
my ($name,$passwd,$uid,$gid) = getpwnam($user_name) or return 0;
($name) = getgrgid($gid) or return 0;
return 1 if $name eq $group_name;
return 0;
+sub match_host($$) {
+ my ($ip,$iplist) = @_;
+ return 0 unless defined($ip);
+ return cidrlookup($ip, split /,/, $iplist);
sub match_user($$) {
my ($self, $expr) = @_;
return 1 if ($expr eq 'all');
return 0 if ($expr eq 'none');
+ if ($expr =~ /(.+)@(.+)/) {
+ return 0 unless match_host($self->{ip}, $2);
+ $expr = $1;
+ }
if ($expr =~ /^%(.+)/) {
return 1 if match_primary_group($self->{user_name}, $1);
my ($name,$passwd,$gid,$members) = getgrnam($1) or return 0;
my @a = split(/\s+/,$members);
for (my $i = 0; $i <= $#a; $i++) {
return 1 if $a[$i] eq $self->{user_name};
@@ -217,12 +228,19 @@ sub new {
$obj->deny("no such user") unless $obj->{user_name};
my $httpdusr = git_value('config', 'hooks.acl.httpd-user');
if (defined($httpdusr) and $obj->{user_name} eq $httpdusr) {
$obj->deny("need authenticated user") unless $ENV{AUTH_TYPE};
$obj->{user_name} = $ENV{REMOTE_USER};
+ $obj->{ip} = $ENV{REMOTE_ADDR};
+ } else {
+ my $ipvar = git_value('config', 'hooks.acl.ip-env-var') or 'SSH_CLIENT';
+ if (defined($ENV{$ipvar})) {
+ my @a = split /\S/, $ENV{$ipvar}, 2;
+ $obj->{ip} = $a[0];
+ }
$obj->{project_name} = get_project_name($obj->{git_dir});
$obj->deny("need a ref name") unless defined($args{ref});
$obj->deny("bogus ref $args{ref}") unless $args{ref} =~ s,^refs/,,;
$obj->{ref} = $args{ref};

Return to:

Send suggestions and report system problems to the System administrator.