Commit 81af3133 authored by Dietmar Maurer's avatar Dietmar Maurer

pveproxy: implement host based access control

parent 72eb5b9f
......@@ -22,6 +22,7 @@ use PVE::INotify;
use PVE::RPCEnvironment;
use PVE::REST;
use Net::IP;
use URI;
use HTTP::Status qw(:constants);
use HTTP::Headers;
......@@ -924,6 +925,41 @@ sub wait_end_loop {
});
}
sub check_host_access {
my ($self, $clientip) = @_;
my $cip = Net::IP->new($clientip);
my $match_allow = 0;
my $match_deny = 0;
if ($self->{allow_from}) {
foreach my $t (@{$self->{allow_from}}) {
if ($t->overlaps($cip)) {
$match_allow = 1;
last;
}
}
}
if ($self->{deny_from}) {
foreach my $t (@{$self->{deny_from}}) {
if ($t->overlaps($cip)) {
$match_deny = 1;
last;
}
}
}
if ($match_allow == $match_deny) {
# match both allow and deny, or no match
return $self->{policy} && $self->{policy} eq 'allow' ? 1 : 0;
}
return $match_allow;
}
sub accept_connections {
my ($self) = @_;
......@@ -943,6 +979,13 @@ sub accept_connections {
($reqstate->{peer_port}, $reqstate->{peer_host}) = ($pport, Socket::inet_ntoa($phost));
}
if (!$self->{trusted_env} && !$self->check_host_access($reqstate->{peer_host})) {
print "$$: ABORT request from $reqstate->{peer_host} - access denied\n" if $self->{debug};
$reqstate->{log}->{code} = 403;
$self->log_request($reqstate);
next;
}
$reqstate->{hdl} = AnyEvent::Handle->new(
fh => $clientfh,
rbuf_max => 64*1024,
......@@ -1047,6 +1090,8 @@ sub new {
$self->{max_conn} = 800 if !$self->{max_conn};
$self->{max_requests} = 8000 if !$self->{max_requests};
$self->{policy} = 'allow' if !$self->{policy};
$self->{end_cond} = AnyEvent->condvar;
if ($self->{ssl}) {
......
......@@ -26,11 +26,35 @@ export LC_ALL="C"
mkdir -p ${RUNDIR} || true
chmod 0700 ${RUNDIR} || true
chown www-data:www-data ${RUNDIR} || true
DAEMON_OPTS=""
# Include defaults if available
if [ -f /etc/default/$NAME ] ; then
ALLOW_FROM=""
DENY_FROM=""
POLICY=""
. /etc/default/$NAME
if [ -n "$ALLOW_FROM" ] ; then
DAEMON_OPTS="${DAEMON_OPTS} --allow-from ${ALLOW_FROM}"
fi
if [ -n "$DENY_FROM" ] ; then
DAEMON_OPTS="${DAEMON_OPTS} --deny-from ${DENY_FROM}"
fi
if [ -n "$POLICY" ] ; then
DAEMON_OPTS="${DAEMON_OPTS} --policy $POLICY"
fi
fi
case "$1" in
start)
log_daemon_msg "Starting $DESC" "$NAME"
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- ${DAEMON_OPTS}
log_end_msg $?
;;
stop)
......@@ -43,7 +67,7 @@ case "$1" in
if ( [ -e $PIDFILE ] && kill -0 `cat $PIDFILE`) then
start-stop-daemon --stop --signal HUP --pidfile $PIDFILE
else
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- ${DAEMON_OPTS}
fi
log_end_msg $?
;;
......@@ -51,7 +75,7 @@ case "$1" in
log_daemon_msg "Restarting $DESC" "$NAME"
start-stop-daemon --stop --quiet --retry TERM/2/TERM/15/KILL/2 --pidfile $PIDFILE
sleep 2
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- ${DAEMON_OPTS}
log_end_msg $?
;;
*)
......
......@@ -123,4 +123,21 @@ All configuration is done using this Server. The Server only
listens to a local address 127.0.0.1 port 85 for security
reasons.
=head1 COPYRIGHT AND DISCLAIMER
Copyright (C) 2007-2013 Proxmox Server Solutions GmbH
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, 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
Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public
License along with this program. If not, see
<http://www.gnu.org/licenses/>.
......@@ -18,17 +18,47 @@ use URI;
use URI::QueryParam;
use File::Find;
use Data::Dumper;
use Net::IP;
my $pidfile = "/var/run/pveproxy/pveproxy.pid";
my $lockfile = "/var/lock/pveproxy.lck";
my $opt_debug;
my $opt_allow_from;
my $opt_deny_from;
my $opt_policy;
initlog ('pveproxy');
if (!GetOptions ('debug' => \$opt_debug)) {
die "usage: $0 [--debug]\n";
if (!GetOptions ('allow-from=s@' => \$opt_allow_from,
'deny-from=s@' => \$opt_deny_from,
'policy=s' => \$opt_policy,
'debug' => \$opt_debug)) {
die "usage: $0 [--allow-from CIDR{,CIRD}] [--deny-from CIDR{,CIRD}] [--policy (allow|deny)] [--debug]\n";
}
$opt_deny_from = [ split(/,/, join(',', @$opt_deny_from)) ] if $opt_deny_from;
$opt_allow_from = [ split(/,/, join(',', @$opt_allow_from)) ] if $opt_allow_from;
die "unknown policy '$opt_policy'\n" if $opt_policy && $opt_policy !~ m/^(allow|deny)$/;
if ($opt_deny_from) {
my $ips = [];
foreach my $ip (@$opt_deny_from) {
$ip = "0/0" if $ip eq 'all';
push @$ips, Net::IP->new($ip) || die Net::IP::Error() . "\n";
}
$opt_deny_from = $ips;
}
if ($opt_allow_from) {
my $ips = [];
foreach my $ip (@$opt_allow_from) {
$ip = "0/0" if $ip eq 'all';
push @$ips, Net::IP->new($ip) || die Net::IP::Error() . "\n";
}
$opt_allow_from = $ips;
}
$SIG{'__WARN__'} = sub {
......@@ -84,6 +114,9 @@ eval {
max_conn => 500,
max_requests => 1000,
debug => $opt_debug,
allow_from => $opt_allow_from,
deny_from => $opt_deny_from,
policy => $opt_policy,
trusted_env => 0, # not trusted, anyone can connect
logfile => '/var/log/pveproxy/access.log',
lockfile => $lockfile,
......@@ -253,10 +286,56 @@ pveproxy - the PVE API proxy server
=head1 SYNOPSIS
pveproxy [--debug]
pveproxy [--allow-from CIDR{,CIRD}] [--deny-from CIDR{,CIRD}] [--policy (allow|deny)] [--debug]
=head1 DESCRIPTION
This is the REST API proxy server, listening on port 8006.
This is the REST API proxy server, listening on port 8006. This is usually started
as service using:
# service pveproxy start
=head1 Host based access control
Options '--allow-from', '--deny-from' and '--policy' can be used to set up
apache2 like access control. If started as service, those values are read
from file /etc/default/pveproxy. For example:
ALLOW_FROM="10.0.0.1-10.0.0.5,192.168.0.0/22"
DENY_FROM="all"
POLICY="allow"
IP addresses can be specified using any syntax understoop by Net::IP. The
name 'all' is an alias for '0/0'.
The default policy is 'allow'.
Match | POLICY=deny | POLICY=allow
---------------------------|-------------|------------
Match Allow only | allow | allow
Match Deny only | deny | deny
No match | deny | allow
Match Both Allow & Deny | deny | allow
=head1 FILES
/etc/default/pveproxy
=head1 COPYRIGHT AND DISCLAIMER
Copyright (C) 2007-2013 Proxmox Server Solutions GmbH
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, 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
Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public
License along with this program. If not, see
<http://www.gnu.org/licenses/>.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment