summaryrefslogtreecommitdiffabout
path: root/gitaclhook
blob: 5d0d79b5573f19e2d91770a5e357609d87940817 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
#! /usr/bin/perl
# Copyright (C) 2013 Sergey Poznyakoff <gray@gnu.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

use strict;
use GitACL;
use Pod::Man;
use Pod::Usage;
use Getopt::Long qw(:config gnu_getopt no_ignore_case);
    
=head1 NAME

gitaclhook - control access to git repositories

=head1 SYNOPSIS

B<gitaclhook> I<refname> I<old-sha1> I<new-sha1>

B<gitacthook> [B<--debug>] B<--test> I<REPO> I<USER> I<OP> I<REF>
    
B<gitaclhook --help>     

=head1 DESCRIPTION
    
This program is intended to be run as an "update" hook by git.
It is called by B<git-receive-pack> with arguments:
I<refname> I<old-sha1> I<new-sha1>.

The program reads access control lists from the storage engine
specified in the repository's config file and allows or denies
the update depending on their settings.  If no storage engine is
defined update is allowed unconditionally.  If it is defined, but
is not available (e.g. the disk file does not exist or the LDAP
server cannot be reached, the update is denied.

Two storage engines are supported: B<File>, which reads access control
lists from a disk file, and B<LDAP>, which obtains them from LDAP.
The engine to use is defined by the B<hooks.acltype> configuration keyword.    
The default is B<File>.    
    
=head1 ACL FILE

The ACL file is used when the B<File> storage engine is requested.  The
path to the file must be given via the B<hooks.aclfile> configuration
keyword.  If B<hooks.aclfile> is not defined, update is allowed
unconditionally.
    
The ACL file has the usual line-oriented syntax.  Comments are introduced
by the # sign and extend to the end of the physical line.  Comments and
empty lines are ignored.

Non-empty lines introduce ACL rules.  The syntax is:

=over 4
    
I<VERB> I<PROJECT> I<USER> [I<OP> I<REF>]

=back

where brackets denote optional parts.  The parts of an ACL are:

=over 4

=item I<VERB>

Either B<allow> or B<deny>, to allow or deny the operation, correspondingly.

=item I<PROJECT>

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>

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.

=back

The optional parts are:

=over 4

=item I<OP>

Requested operation codes.  It is a string consisting of one or more
of the following letters (case-insensitive):

=over 8

=item B<C>

Create new ref.

=item B<D>

Delete existing ref.

=item B<U>

Fast-forward existing ref (no commit loss).

=item B<R>

Rewind or rebase existing ref (commit loss).

=back    
    
=item I<REF>

Affected ref, relative to the git B<refs/> directory.  If it begins with
a caret (B<^>), it is treated as a Perl regular expression (with the B<^>
being its part).  If it ends with a B</>, it is treated as a prefix match,
so, e.g., B<heads/baz/> matches B<refs/heads/baz> and anything below.
Otherwise, it must match exactly the affected ref.

=back

=head1 RULE MATCHING
    
The rule applies only if its I<PROJECT> and I<USER> parts match the project
which is being updated and the user who requests the update, its I<OP>
contains the opcode of the requested operation and I<REF> matches the affected
ref.  Missing I<REF> and/or I<OP> are treated as a match.

If no rule applies, the operation is allowed.

For example, assume you have the following ACL file:

    allow myprog %devel   U    heads/master
    allow myprog %pm      CDUR heads/
    allow myprog %pm      C    ^heads/tags/v\\d+$
    allow myprog admin    CDUR
    deny myprog all

Then the users from the B<devel> group will be able to push updates to
B<refs/heads/master>, the users from the B<pm> group will be allowed to do
anything with refs under B<refs/heads> and to create tags with names beginning
with B<v> and containing only digits afterwards, and the user B<admin> will
be allowed to do anything he pleases.  No other users will be allowed to
update that repository.

=head1 LDAP

The LDAP storage engine is requested by the following configuration statement:

    [hooks]
            acltype = LDAP

The URI of the LDAP server to use and other data necessary to access it
are read from the file name given in the B<hooks.aclldapconf> variable,
or from B</etc/ldap.conf>, if it is not defined.  LDAP access control
entries are similar to the plaintext file ACLs.  Each entry has the
following attrubutes:

=over 4

=item B<gitAclProject>    [mandatory]

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.
    
=item B<gitAclOp>         [optional]

The list of operation codes.
    
=item B<gitAclRef>        [optional]

Git ref.
    
=item B<gitAclOrder>      [optional]

Sorting order (see below).
    
=back    

The program first reads all entries with the B<gitAclProject> attribute
matching the requested project name.  The obtained entries are sorted
by the value of B<gitAclOrder> attribute.  Entries without this attributes
are assumed to have sorting order B<0>.  Entries with the project name
B<all> are sorted last.  Entries with the same sorting order are sorted
by the count of attributes they carry (in the reverse order).  Thus, the
most specific entries precede the least specific entries in the resulting
list.

Each list entry is then matched against the current tuple (I<PROJECT>, I<USER>,
I<OP>, I<REF>), much the same way as described in B<RULE MATCHING>.  Missing
attributes always match.  The special B<gitAclProject> value B<all> matches
all project names.    

If no matching entry is found, the update is allowed.
    
=head1 CONFIGURATION SETTINGS

=over 4

=item B<hooks.acltype>    STRING

Type of the storage engine.  Valid values are B<File> (default) and B<LDAP>.
    
=item B<hooks.aclfile>    STRING

For the B<File> storage engine, name of the ACL file.

=item B<hooks.aclldapconf> STRING

For the B<LDAP> storage engine, the name of the configuration file to use
instead of B</etc/ldap.conf>.    

=item B<hooks.acllog>     STRING

Send log info to this file.

=item B<hooks.acldebug>   NUMBER

Enable debugging.  The bigger the number, the more debugging info will
be displayed.    

=item B<hooks.aclquiet>   BOOL

Suppress diagnostics on stderr.

=item B<hooks.httpd-user> 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>.

=back

=head1 TEST MODE

The B<--test> (B<-t>) option provides a mechanism for testing access control
lists from the command line.  The syntax is:

=over 4

B<gitacthook> [B<--debug>] [B<-d>] B<--test> I<REPO> I<USER> I<OP> I<REF>

=back

I<REPO> is a pathname of the repository to test, I<USER> is the username,
I<OP> is the operation code and I<REF> is the reference.

Optional B<--debug> (B<-d>) options increment the debugging level.

=head1 ENVIRONMENT

The program uses following environment variables:

=over 4

=item B<GIT_UPDATE_DEBUG>

When set to 1, enables debugging mode.  The B<hooks.acldebug>
configuration variable overrides this setting.
    
=item B<GIT_DIR>

Path to the affected repository.    

=back

If updates are performed via HTTP or HTTPS and the B<hooks.httpd-user>
configuration variable is set, the following two variables are used
to determine the identity of the user:
    
=over 4    

=item B<AUTH_TYPE>

If this variable is not set or set to an empty value, the program will
deny the update.    
    
=item B<REMOTE_USER>

The authenticated name of the user.
    
=back

=head1 SEE ALSO

B<git-receive-pack>(1).     
    
=head1 AUTHOR

Sergey Poznyakoff, <gray@gnu.org>    
    
=cut

my $script;
($script = $0) =~ s/.*\///;

sub abend($) {
    my $msg = shift;
    print STDERR "$script: $msg\n";
    exit 2;
}

my %args;

unless ($ENV{GIT_DIR}) {
    my $debug;
    my $test;
    GetOptions("help|h" => sub { pod2usage(-exitstatus => 0, -verbose => 2); },
	       "debug|d+" => \$debug,
	       "test|t" => \$test)
	or exit (3);
    if ($test) {
	abend("--test requires four arguments") unless ($#ARGV == 3);
	$args{git_dir} = $ENV{GIT_DIR} = $ARGV[0];
	$args{user} = $ARGV[1];
	$args{op} = $ARGV[2];
	$args{ref} = $ARGV[3];
	$args{old} = '0000000000000000000000000000000000000000';
	$args{new} = '0000000000000000000000000000000000000001';
	$args{debug} = $debug;
    } else {
	abend("try \"$script --help\" for fore info")
    }
} else {
    abend("bad number of arguments") unless ($#ARGV == 2);
    $args{git_dir} = $ENV{GIT_DIR};
    $args{ref} = $ARGV[0];
    $args{old} = $ARGV[1];
    $args{new} = $ARGV[2];
}

my $gitacl = GitACL->new(%args);
$gitacl->check;

exit 0;

# Finis






Return to:

Send suggestions and report system problems to the System administrator.