root/trunk/lib/Perlbal/Plugin/AccessControl.pm

Revision 825, 5.6 kB (checked in by hachi, 2 months ago)

Make AccessControl plugin capable of dumping config.

This reverts commit b71936415bce1e20b2c402c3fd290500c6bd0083.

(and another double-revert, but this is the last one)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1package Perlbal::Plugin::AccessControl;
2
3use Perlbal;
4use strict;
5use warnings;
6no  warnings qw(deprecated);
7
8# commands like:
9#
10# what to do if we fall off the rule chain:
11#     ACCESS POLICY {ALLOW,DENY}
12#
13# adding things to the rule chain.  processing stops once any rule is matched.
14#
15#     ACCESS {ALLOW,DENY} netmask 127.0.0.1/8
16#     ACCESS {ALLOW,DENY} ip 127.0.0.1
17# also can make a match set the request to go into the low-priority perlbal queue:
18#     ACCESS QUEUE_LOW ip 127.0.0.1
19
20# reset the rule chain and policy:  (policy is allow by default)
21#     ACCESS RESET
22
23# Future:
24#  access {allow,deny} forwarded_ip 127.0.0.1
25#  access {allow,deny} method <method>[,<method>]*
26#  access {allow,deny} forwarded_netmask 127.0.0.1/24
27
28sub load {
29    my $class = shift;
30
31    Perlbal::register_global_hook('manage_command.access', sub {
32        my $mc = shift->parse(qr/^access\s+
33                              (policy|allow|deny|reset|queue_low)      # cmd
34                              (?:\s+(\S+))?                  # arg1
35                              (?:\s+(\S+))?                  # optional arg2
36                              $/x,
37                              "usage: ACCESS <cmd> <arg1> [<arg2>]");
38        my ($cmd, $arg1, $arg2) = $mc->args;
39
40        my $svcname;
41        unless ($svcname ||= $mc->{ctx}{last_created}) {
42            return $mc->err("No service name in context from CREATE SERVICE <name> or USE <service_name>");
43        }
44
45        my $ss = Perlbal->service($svcname);
46        return $mc->err("Non-existent service '$svcname'") unless $ss;
47
48        my $cfg = $ss->{extra_config}->{_access} ||= {};
49
50        if ($cmd eq "reset") {
51            $ss->{extra_config}->{_access} = {};
52            return $mc->ok;
53        }
54
55        if ($cmd eq "policy") {
56            return $mc->err("policy must be 'allow' or 'deny'") unless
57                $arg1 =~ /^allow|deny$/;
58            $cfg->{deny_default} = $arg1 eq "deny";
59            return $mc->ok;
60        }
61
62        if ($cmd eq "allow" || $cmd eq "deny" || $cmd eq "queue_low") {
63            my ($what, $val) = ($arg1, $arg2);
64            return $mc->err("Unknown item to $cmd: '$what'") unless
65                $what && ($what eq "ip" || $what eq "netmask");
66
67            if ($what eq "netmask") {
68                return $mc->err("Net::Netmask not installed")
69                    unless eval { require Net::Netmask; 1; };
70
71                $val = eval { Net::Netmask->new2($val) };
72                return $mc->err("Error parsing netmask") unless $val;
73            }
74
75            my $rules = $cfg->{rules} ||= [];
76            push @$rules, [ $cmd, $what, $val ];
77            return $mc->ok;
78        }
79
80        return $mc->err("can't get here");
81    });
82
83    return 1;
84}
85
86# unload our global commands, clear our service object
87sub unload {
88    my $class = shift;
89    Perlbal::unregister_global_hook('manage_command.access');
90    return 1;
91}
92
93# called when we're being added to a service
94sub register {
95    my ($class, $svc) = @_;
96
97    my $use_observed_ip;
98
99    $svc->register_hook('AccessControl', 'start_http_request', sub {
100        my Perlbal::ClientHTTPBase $client = shift;
101        my Perlbal::HTTPHeaders $hds = $client->{req_headers};
102        return 0 unless $hds;
103        my $uri = $hds->request_uri;
104
105        my $allow = sub { 0; };
106        my $deny = sub {
107            $client->send_response(403, "Access denied.");
108            return 1;
109        };
110
111        my $queue_low = sub {
112            $client->set_queue_low;
113            return 0;
114        };
115
116        my $rule_action = sub {
117            my $rule = shift;
118            if ($rule->[0] eq "deny") {
119                return $deny->();
120            } elsif ($rule->[0] eq "allow") {
121                return $allow->();
122            } elsif ($rule->[0] eq "queue_low") {
123                return $queue_low->();
124            }
125        };
126
127        my $match = sub {
128            my $rule = shift;
129            if ($rule->[1] eq "ip") {
130                my $peer_ip;
131                $peer_ip = $client->observed_ip_string if $use_observed_ip;
132                $peer_ip ||= $client->peer_ip_string;
133
134                return $peer_ip eq $rule->[2];
135            }
136
137            if ($rule->[1] eq "netmask") {
138                my $peer_ip;
139                $peer_ip = $client->observed_ip_string if $use_observed_ip;
140                $peer_ip ||= $client->peer_ip_string;
141
142                return eval { $rule->[2]->match($peer_ip); };
143            }
144        };
145
146        my $cfg = $svc->{extra_config}->{_access} ||= {};
147        my $rules = $cfg->{rules} || [];
148        foreach my $rule (@$rules) {
149            next unless $match->($rule);
150            return $rule_action->($rule)
151        }
152
153        return $deny->() if $cfg->{deny_default};
154        return $allow->();
155    });
156
157    # Allow AccessControl users to specify that they would like to use the observed IP as
158    # opposed to the real IP for ACL checking.
159    $svc->register_setter('AccessControl', 'use_observed_ip', sub {
160        my ($out, $what, $val) = @_;
161        return 0 unless $what;
162
163        $use_observed_ip = $val;
164
165        $out->("OK") if $out;
166
167        return 1;
168    });
169
170
171    return 1;
172}
173
174# called when we're no longer active on a service
175sub unregister {
176    my ($class, $svc) = @_;
177    return 1;
178}
179
180sub dumpconfig {
181    my ($class, $svc) = @_;
182
183    my @return;
184
185    my $cfg = $svc->{extra_config}->{_access} ||= {};
186    my $rules = $cfg->{rules} || [];
187
188    foreach my $rule (@$rules) {
189        my $action = uc $rule->[0];
190        my $type   = uc $rule->[1];
191        my $value  = $rule->[2];
192        push @return, "ACCESS $action $type $value";
193    }
194
195    my $default_action = $cfg->{deny_default} ? "DENY" : "ALLOW";
196    push @return, "ACCESS POLICY $default_action";
197
198    return @return;
199}
200
2011;
Note: See TracBrowser for help on using the browser.