]> git.proxmox.com Git - pve-manager.git/blob - bin/pveproxy
firewall: expose configuration option for new nftables firewall
[pve-manager.git] / bin / pveproxy
1 #!/usr/bin/perl -T
2
3 $ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
4
5 delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
6
7 use strict;
8 use warnings;
9 use English;
10 use Getopt::Long;
11 use POSIX ":sys_wait_h";
12 use Socket;
13 use IO::Socket::INET;
14 use PVE::SafeSyslog;
15 use PVE::APIDaemon;
16 use HTTP::Response;
17 use Encode;
18 use URI;
19 use URI::QueryParam;
20 use File::Find;
21 use Data::Dumper;
22
23 my $pidfile = "/var/run/pveproxy/pveproxy.pid";
24 my $lockfile = "/var/lock/pveproxy.lck";
25
26 my $opt_debug;
27
28 initlog ('pveproxy');
29
30 if (!GetOptions ('debug' => \$opt_debug)) {
31 die "usage: $0 [--debug]\n";
32 }
33
34 $SIG{'__WARN__'} = sub {
35 my $err = $@;
36 my $t = $_[0];
37 chomp $t;
38 syslog('warning', "WARNING: %s", $t);
39 $@ = $err;
40 };
41
42 $0 = "pveproxy";
43
44 # run as www-data
45 my $gid = getgrnam('www-data') || die "getgrnam failed - $!\n";
46 POSIX::setgid($gid) || die "setgid $gid failed - $!\n";
47 $EGID = "$gid $gid"; # this calls setgroups
48 my $uid = getpwnam('www-data') || die "getpwnam failed - $!\n";
49 POSIX::setuid($uid) || die "setuid $uid failed - $!\n";
50
51 # just to be sure
52 die "detected strange uid/gid\n" if !($UID == $uid && $EUID == $uid && $GID eq "$gid $gid" && $EGID eq "$gid $gid");
53
54 my $proxyconf = PVE::APIDaemon::read_proxy_config();
55
56 sub add_dirs {
57 my ($result_hash, $alias, $subdir) = @_;
58
59 $result_hash->{$alias} = $subdir;
60
61 my $wanted = sub {
62 my $dir = $File::Find::dir;
63 if ($dir =~m!^$subdir(.*)$!) {
64 my $name = "$alias$1/";
65 $result_hash->{$name} = "$dir/";
66 }
67 };
68
69 find({wanted => $wanted, follow => 0, no_chdir => 1}, $subdir);
70 }
71
72 my $cpid;
73 my $daemon;
74 eval {
75
76 my $dirs = {};
77
78 add_dirs($dirs, '/pve2/ext4/', '/usr/share/pve-manager/ext4/');
79 add_dirs($dirs, '/pve2/images/' => '/usr/share/pve-manager/images/');
80 add_dirs($dirs, '/pve2/css/' => '/usr/share/pve-manager/css/');
81 add_dirs($dirs, '/vncterm/' => '/usr/share/vncterm/');
82
83 $daemon = PVE::APIDaemon->new(
84 port => 8006,
85 keep_alive => 100,
86 max_conn => 500,
87 max_requests => 1000,
88 debug => $opt_debug,
89 allow_from => $proxyconf->{ALLOW_FROM},
90 deny_from => $proxyconf->{DENY_FROM},
91 policy => $proxyconf->{POLICY},
92 trusted_env => 0, # not trusted, anyone can connect
93 logfile => '/var/log/pveproxy/access.log',
94 lockfile => $lockfile,
95 ssl => {
96 cipher_list => $proxyconf->{CIPHERS} || 'HIGH:MEDIUM:!aNULL:!MD5',
97 key_file => '/etc/pve/local/pve-ssl.key',
98 cert_file => '/etc/pve/local/pve-ssl.pem',
99 },
100 # Note: there is no authentication for those pages and dirs!
101 pages => {
102 '/' => \&get_index,
103 # avoid authentication when accessing favicon
104 '/favicon.ico' => {
105 file => '/usr/share/pve-manager/images/favicon.ico',
106 },
107 },
108 dirs => $dirs,
109 );
110 };
111
112 my $err = $@;
113
114 if ($err) {
115 syslog ('err' , "unable to start server: $err");
116 print STDERR $err;
117 exit (-1);
118 }
119
120
121 if ($opt_debug || !($cpid = fork ())) {
122
123 $SIG{PIPE} = 'IGNORE';
124 $SIG{INT} = 'IGNORE' if !$opt_debug;
125
126 $SIG{TERM} = $SIG{QUIT} = sub {
127 syslog ('info' , "server closing");
128
129 $SIG{INT} = 'DEFAULT';
130
131 unlink "$pidfile" if !$opt_debug;
132
133 exit (0);
134 };
135
136 syslog ('info' , "starting server");
137
138 if (!$opt_debug) {
139 # redirect STDIN/STDOUT/SDTERR to /dev/null
140 open STDIN, '</dev/null' || die "can't read /dev/null [$!]";
141 open STDOUT, '>/dev/null' || die "can't write /dev/null [$!]";
142 open STDERR, '>&STDOUT' || die "can't open STDERR to STDOUT [$!]";
143 }
144
145 POSIX::setsid();
146
147 eval {
148 $daemon->start_server();
149 };
150 my $err = $@;
151
152 if ($err) {
153 syslog ('err' , "unexpected server error: $err");
154 print STDERR $err if $opt_debug;
155 exit (-1);
156 }
157
158 } else {
159
160 open (PIDFILE, ">$pidfile") ||
161 die "cant write '$pidfile' - $! :ERROR";
162 print PIDFILE "$cpid\n";
163 close (PIDFILE) ||
164 die "cant write '$pidfile' - $! :ERROR";
165 }
166
167 exit (0);
168
169 # NOTE: Requests to those pages are not authenticated
170 # so we must be very careful here
171
172 sub get_index {
173 my ($server, $r, $args) = @_;
174
175 my $lang = 'en';
176 my $username;
177 my $token = 'null';
178
179 if (my $cookie = $r->header('Cookie')) {
180 if (my $newlang = ($cookie =~ /(?:^|\s)PVELangCookie=([^;]*)/)[0]) {
181 if ($newlang =~ m/^[a-z]{2,3}(_[A-Z]{2,3})?$/) {
182 $lang = $newlang;
183 }
184 }
185 my $ticket = PVE::REST::extract_auth_cookie($cookie);
186 if (($username = PVE::AccessControl::verify_ticket($ticket, 1))) {
187 $token = PVE::AccessControl::assemble_csrf_prevention_token($username);
188 }
189 }
190
191 my $workspace = defined($args->{console}) ?
192 "PVE.ConsoleWorkspace" : "PVE.StdWorkspace";
193
194 $username = '' if !$username;
195
196 my $jssrc = <<_EOJS;
197 if (!PVE) PVE = {};
198 PVE.UserName = '$username';
199 PVE.CSRFPreventionToken = '$token';
200 _EOJS
201
202 my $langfile = "/usr/share/pve-manager/ext4/locale/ext-lang-${lang}.js";
203 $jssrc .= PVE::Tools::file_get_contents($langfile) if -f $langfile;
204
205 my $i18nsrc;
206 $langfile = "/usr/share/pve-manager/root/pve-lang-${lang}.js";
207 if (-f $langfile) {
208 $i18nsrc = PVE::Tools::file_get_contents($langfile);
209 } else {
210 $i18nsrc = 'function gettext(buf) { return buf; }';
211 }
212
213 $jssrc .= <<_EOJS;
214
215 // we need this (the java applet ignores the zindex)
216 Ext.useShims = true;
217
218 Ext.History.fieldid = 'x-history-field';
219
220 Ext.onReady(function() { Ext.create('$workspace');});
221
222 _EOJS
223
224 my $page = <<_EOD;
225 <html>
226 <head>
227 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
228
229 <title>Proxmox Virtual Environment</title>
230
231 <link rel="stylesheet" type="text/css" href="/pve2/ext4/resources/css/ext-all.css" />
232 <link rel="stylesheet" type="text/css" href="/pve2/css/ext-pve.css" />
233
234 <script type="text/javascript">$i18nsrc</script>
235 <script type="text/javascript" src="/pve2/ext4/ext-all-debug.js"></script>
236 <script type="text/javascript" src="/pve2/ext4/pvemanagerlib.js"></script>
237 <script type="text/javascript">$jssrc</script>
238
239 </head>
240 <body>
241 <!-- Fields required for history management -->
242 <form id="history-form" class="x-hidden">
243 <input type="hidden" id="x-history-field"/>
244 </form>
245 </body>
246 </html>
247 _EOD
248
249 my $resp = HTTP::Response->new(200, "OK", undef, $page);
250
251 return $resp;
252 }
253
254 __END__
255
256 =head1 NAME
257
258 pveproxy - the PVE API proxy server
259
260 =head1 SYNOPSIS
261
262 pveproxy [--debug]
263
264 =head1 DESCRIPTION
265
266 This is the REST API proxy server, listening on port 8006. This is usually started
267 as service using:
268
269 # service pveproxy start
270
271 =head1 Host based access control
272
273 It is possible to configure apache2 like access control lists. Values are read
274 from file /etc/default/pveproxy. For example:
275
276 ALLOW_FROM="10.0.0.1-10.0.0.5,192.168.0.0/22"
277 DENY_FROM="all"
278 POLICY="allow"
279
280 IP addresses can be specified using any syntax understoop by Net::IP. The
281 name 'all' is an alias for '0/0'.
282
283 The default policy is 'allow'.
284
285 Match | POLICY=deny | POLICY=allow
286 ---------------------------|-------------|------------
287 Match Allow only | allow | allow
288 Match Deny only | deny | deny
289 No match | deny | allow
290 Match Both Allow & Deny | deny | allow
291
292 =head1 SSL Cipher Suite
293
294 You can define the chiper list in /etc/default/pveproxy, for example
295
296 CIPHERS="HIGH:MEDIUM:!aNULL:!MD5"
297
298 Above is the default. See the ciphers(1) man page from the openssl
299 package for list of all available options.
300
301 =head1 FILES
302
303 /etc/default/pveproxy
304
305 =head1 COPYRIGHT AND DISCLAIMER
306
307 Copyright (C) 2007-2013 Proxmox Server Solutions GmbH
308
309 This program is free software: you can redistribute it and/or modify it
310 under the terms of the GNU Affero General Public License as published
311 by the Free Software Foundation, either version 3 of the License, or
312 (at your option) any later version.
313
314 This program is distributed in the hope that it will be useful, but
315 WITHOUT ANY WARRANTY; without even the implied warranty of
316 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
317 Affero General Public License for more details.
318
319 You should have received a copy of the GNU Affero General Public
320 License along with this program. If not, see
321 <http://www.gnu.org/licenses/>.
322