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; ...@@ -22,6 +22,7 @@ use PVE::INotify;
use PVE::RPCEnvironment; use PVE::RPCEnvironment;
use PVE::REST; use PVE::REST;
use Net::IP;
use URI; use URI;
use HTTP::Status qw(:constants); use HTTP::Status qw(:constants);
use HTTP::Headers; use HTTP::Headers;
...@@ -924,6 +925,41 @@ sub wait_end_loop { ...@@ -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 { sub accept_connections {
my ($self) = @_; my ($self) = @_;
...@@ -943,6 +979,13 @@ sub accept_connections { ...@@ -943,6 +979,13 @@ sub accept_connections {
($reqstate->{peer_port}, $reqstate->{peer_host}) = ($pport, Socket::inet_ntoa($phost)); ($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( $reqstate->{hdl} = AnyEvent::Handle->new(
fh => $clientfh, fh => $clientfh,
rbuf_max => 64*1024, rbuf_max => 64*1024,
...@@ -1047,6 +1090,8 @@ sub new { ...@@ -1047,6 +1090,8 @@ sub new {
$self->{max_conn} = 800 if !$self->{max_conn}; $self->{max_conn} = 800 if !$self->{max_conn};
$self->{max_requests} = 8000 if !$self->{max_requests}; $self->{max_requests} = 8000 if !$self->{max_requests};
$self->{policy} = 'allow' if !$self->{policy};
$self->{end_cond} = AnyEvent->condvar; $self->{end_cond} = AnyEvent->condvar;
if ($self->{ssl}) { if ($self->{ssl}) {
......
...@@ -26,11 +26,35 @@ export LC_ALL="C" ...@@ -26,11 +26,35 @@ export LC_ALL="C"
mkdir -p ${RUNDIR} || true mkdir -p ${RUNDIR} || true
chmod 0700 ${RUNDIR} || true chmod 0700 ${RUNDIR} || true
chown www-data:www-data ${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 case "$1" in
start) start)
log_daemon_msg "Starting $DESC" "$NAME" 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 $? log_end_msg $?
;; ;;
stop) stop)
...@@ -43,7 +67,7 @@ case "$1" in ...@@ -43,7 +67,7 @@ case "$1" in
if ( [ -e $PIDFILE ] && kill -0 `cat $PIDFILE`) then if ( [ -e $PIDFILE ] && kill -0 `cat $PIDFILE`) then
start-stop-daemon --stop --signal HUP --pidfile $PIDFILE start-stop-daemon --stop --signal HUP --pidfile $PIDFILE
else else
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- ${DAEMON_OPTS}
fi fi
log_end_msg $? log_end_msg $?
;; ;;
...@@ -51,7 +75,7 @@ case "$1" in ...@@ -51,7 +75,7 @@ case "$1" in
log_daemon_msg "Restarting $DESC" "$NAME" log_daemon_msg "Restarting $DESC" "$NAME"
start-stop-daemon --stop --quiet --retry TERM/2/TERM/15/KILL/2 --pidfile $PIDFILE start-stop-daemon --stop --quiet --retry TERM/2/TERM/15/KILL/2 --pidfile $PIDFILE
sleep 2 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 $? log_end_msg $?
;; ;;
*) *)
......
...@@ -123,4 +123,21 @@ All configuration is done using this Server. The Server only ...@@ -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 listens to a local address 127.0.0.1 port 85 for security
reasons. 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; ...@@ -18,17 +18,47 @@ use URI;
use URI::QueryParam; use URI::QueryParam;
use File::Find; use File::Find;
use Data::Dumper; use Data::Dumper;
use Net::IP;
my $pidfile = "/var/run/pveproxy/pveproxy.pid"; my $pidfile = "/var/run/pveproxy/pveproxy.pid";
my $lockfile = "/var/lock/pveproxy.lck"; my $lockfile = "/var/lock/pveproxy.lck";
my $opt_debug; my $opt_debug;
my $opt_allow_from;
my $opt_deny_from;
my $opt_policy;
initlog ('pveproxy'); initlog ('pveproxy');
if (!GetOptions ('debug' => \$opt_debug)) { if (!GetOptions ('allow-from=s@' => \$opt_allow_from,
die "usage: $0 [--debug]\n"; '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 { $SIG{'__WARN__'} = sub {
...@@ -84,6 +114,9 @@ eval { ...@@ -84,6 +114,9 @@ eval {
max_conn => 500, max_conn => 500,
max_requests => 1000, max_requests => 1000,
debug => $opt_debug, debug => $opt_debug,
allow_from => $opt_allow_from,
deny_from => $opt_deny_from,
policy => $opt_policy,
trusted_env => 0, # not trusted, anyone can connect trusted_env => 0, # not trusted, anyone can connect
logfile => '/var/log/pveproxy/access.log', logfile => '/var/log/pveproxy/access.log',
lockfile => $lockfile, lockfile => $lockfile,
...@@ -253,10 +286,56 @@ pveproxy - the PVE API proxy server ...@@ -253,10 +286,56 @@ pveproxy - the PVE API proxy server
=head1 SYNOPSIS =head1 SYNOPSIS
pveproxy [--debug] pveproxy [--allow-from CIDR{,CIRD}] [--deny-from CIDR{,CIRD}] [--policy (allow|deny)] [--debug]
=head1 DESCRIPTION =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