]> git.proxmox.com Git - pve-manager.git/blob - bin/pveproxy
87369f45ec73545f5033a3a84ad818b789652e97
[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 $headers = HTTP::Headers->new(Content_Type => "text/html; charset=utf-8");
250 my $resp = HTTP::Response->new(200, "OK", $headers, $page);
251
252 return $resp;
253 }
254
255 __END__
256
257 =head1 NAME
258
259 pveproxy - the PVE API proxy server
260
261 =head1 SYNOPSIS
262
263 pveproxy [--debug]
264
265 =head1 DESCRIPTION
266
267 This is the REST API proxy server, listening on port 8006. This is usually started
268 as service using:
269
270 # service pveproxy start
271
272 =head1 Host based access control
273
274 It is possible to configure apache2 like access control lists. Values are read
275 from file /etc/default/pveproxy. For example:
276
277 ALLOW_FROM="10.0.0.1-10.0.0.5,192.168.0.0/22"
278 DENY_FROM="all"
279 POLICY="allow"
280
281 IP addresses can be specified using any syntax understoop by Net::IP. The
282 name 'all' is an alias for '0/0'.
283
284 The default policy is 'allow'.
285
286 Match | POLICY=deny | POLICY=allow
287 ---------------------------|-------------|------------
288 Match Allow only | allow | allow
289 Match Deny only | deny | deny
290 No match | deny | allow
291 Match Both Allow & Deny | deny | allow
292
293 =head1 SSL Cipher Suite
294
295 You can define the chiper list in /etc/default/pveproxy, for example
296
297 CIPHERS="HIGH:MEDIUM:!aNULL:!MD5"
298
299 Above is the default. See the ciphers(1) man page from the openssl
300 package for list of all available options.
301
302 =head1 FILES
303
304 /etc/default/pveproxy
305
306 =head1 COPYRIGHT AND DISCLAIMER
307
308 Copyright (C) 2007-2013 Proxmox Server Solutions GmbH
309
310 This program is free software: you can redistribute it and/or modify it
311 under the terms of the GNU Affero General Public License as published
312 by the Free Software Foundation, either version 3 of the License, or
313 (at your option) any later version.
314
315 This program is distributed in the hope that it will be useful, but
316 WITHOUT ANY WARRANTY; without even the implied warranty of
317 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
318 Affero General Public License for more details.
319
320 You should have received a copy of the GNU Affero General Public
321 License along with this program. If not, see
322 <http://www.gnu.org/licenses/>.
323