]> git.proxmox.com Git - pmg-api.git/blame - PMG/Ticket.pm
test-server.pl: removed - no need
[pmg-api.git] / PMG / Ticket.pm
CommitLineData
1360e6f0
DM
1package PMG::Ticket;
2
3use strict;
4use warnings;
5use Net::SSLeay;
6use Digest::SHA;
7
9d7f54a3 8use PVE::Tools;
1360e6f0
DM
9use PVE::Ticket;
10
11use Crypt::OpenSSL::RSA;
12
13my $min_ticket_lifetime = -60*5; # allow 5 minutes time drift
14my $max_ticket_lifetime = 60*60*2; # 2 hours
15
d883fac2
DM
16my $basedir = "/etc/proxmox";
17
18my $pmg_api_cert_fn = "$basedir/pmg-api.pem";
19
20# this is just a secret accessable by all API servers
21# and is used for CSRF prevention
22my $pmg_csrf_key_fn = "$basedir/pmg-csrf.key";
9d7f54a3 23
c151b711
DM
24my $authprivkeyfn = "$basedir/pmg-authkey.key";
25my $authpubkeyfn = "$basedir/pmg-authkey.pub";
1360e6f0 26
d883fac2
DM
27# only write output if something fails
28sub run_silent_cmd {
29 my ($cmd) = @_;
30
31 my $outbuf = '';
32
33 my $record_output = sub {
34 $outbuf .= shift;
35 $outbuf .= "\n";
36 };
37
38 eval {
39 PVE::Tools::run_command($cmd, outfunc => $record_output,
40 errfunc => $record_output);
41 };
42
43 my $err = $@;
44
45 if ($err) {
46 print STDERR $outbuf;
47 die $err;
48 }
49}
50
9d7f54a3
DM
51sub generate_api_cert {
52 my ($nodename, $force) = @_;
53
54 if (-f $pmg_api_cert_fn) {
55 return $pmg_api_cert_fn if !$force;
56 unlink $pmg_api_cert_fn;
57 }
58
c151b711
DM
59 my $gid = getgrnam('www-data') ||
60 die "user www-data not in group file\n";
61
62 my $tmp_fn = "$pmg_api_cert_fn.tmp$$";
63
9d7f54a3 64 my $cmd = ['openssl', 'req', '-batch', '-x509', '-newkey', 'rsa:4096',
c151b711 65 '-nodes', '-keyout', $tmp_fn, '-out', $tmp_fn,
9d7f54a3
DM
66 '-subj', "/CN=$nodename/",
67 '-days', '3650'];
68
c151b711
DM
69 eval {
70 run_silent_cmd($cmd);
71 chown(0, $gid, $tmp_fn) || die "chown failed - $!\n";
72 chmod(0640, $tmp_fn) || die "chmod failed - $!\n";
73 rename($tmp_fn, $pmg_api_cert_fn) || die "rename failed - $!\n";
74 };
75 if (my $err = $@) {
76 unlink $tmp_fn;
77 die "unable to generate pmg api cert '$pmg_api_cert_fn':\n$err";
78 }
9d7f54a3
DM
79
80 return $pmg_api_cert_fn;
81}
82
d883fac2
DM
83sub generate_csrf_key {
84
85 return if -f $pmg_csrf_key_fn;
86
c151b711
DM
87 my $gid = getgrnam('www-data') ||
88 die "user www-data not in group file\n";
d883fac2 89
c151b711
DM
90 my $tmp_fn = "$pmg_csrf_key_fn.tmp$$";
91 my $cmd = ['openssl', 'genrsa', '-out', $tmp_fn, '2048'];
92
93 eval {
94 run_silent_cmd($cmd);
95 chown(0, $gid, $tmp_fn) || die "chown failed - $!\n";
96 chmod(0640, $tmp_fn) || die "chmod failed - $!\n";
97 rename($tmp_fn, $pmg_csrf_key_fn) || die "rename failed - $!\n";
98 };
99 if (my $err = $@) {
100 unlink $tmp_fn;
101 die "unable to generate pmg csrf key '$pmg_csrf_key_fn':\n$@";
102 }
d883fac2 103
c151b711
DM
104 return $pmg_csrf_key_fn;
105}
106
107sub generate_auth_key {
108
109 return if -f "$authprivkeyfn";
110
111 eval {
112 run_silent_cmd(['openssl', 'genrsa', '-out', $authprivkeyfn, '2048']);
113
114 run_silent_cmd(['openssl', 'rsa', '-in', $authprivkeyfn, '-pubout', '-out', $authpubkeyfn]);
115 };
116
117 die "unable to generate pmg auth key:\n$@" if $@;
118}
119
120my $pve_auth_priv_key;
121sub get_privkey {
122
123 return $pve_auth_priv_key if $pve_auth_priv_key;
124
125 my $input = PVE::Tools::file_get_contents($authprivkeyfn);
126
127 $pve_auth_priv_key = Crypt::OpenSSL::RSA->new_private_key($input);
128
129 return $pve_auth_priv_key;
130}
131
132my $pve_auth_pub_key;
133sub get_pubkey {
134
135 return $pve_auth_pub_key if $pve_auth_pub_key;
136
137 my $input = PVE::Tools::file_get_contents($authpubkeyfn);
138
139 $pve_auth_pub_key = Crypt::OpenSSL::RSA->new_public_key($input);
140
141 return $pve_auth_pub_key;
d883fac2
DM
142}
143
1360e6f0
DM
144my $csrf_prevention_secret;
145my $get_csrfr_secret = sub {
146 if (!$csrf_prevention_secret) {
d883fac2 147 my $input = PVE::Tools::file_get_contents($pmg_csrf_key_fn);
1360e6f0 148 $csrf_prevention_secret = Digest::SHA::sha1_base64($input);
d883fac2 149 print "SECRET:$csrf_prevention_secret\n";
1360e6f0
DM
150 }
151 return $csrf_prevention_secret;
152};
153
154
155sub verify_csrf_prevention_token {
156 my ($username, $token, $noerr) = @_;
157
158 my $secret = &$get_csrfr_secret();
159
160 return PVE::Ticket::verify_csrf_prevention_token(
d883fac2 161 $secret, $username, $token, $min_ticket_lifetime,
1360e6f0
DM
162 $max_ticket_lifetime, $noerr);
163}
164
165sub assemble_csrf_prevention_token {
166 my ($username) = @_;
167
168 my $secret = &$get_csrfr_secret();
169
170 return PVE::Ticket::assemble_csrf_prevention_token ($secret, $username);
171}
172
173sub assemble_ticket {
174 my ($username) = @_;
175
c151b711
DM
176 my $rsa_priv = get_privkey();
177
178 return PVE::Ticket::assemble_rsa_ticket($rsa_priv, 'PMG', $username);
1360e6f0
DM
179}
180
181sub verify_ticket {
182 my ($ticket, $noerr) = @_;
183
c151b711
DM
184 my $rsa_pub = get_pubkey();
185
1360e6f0 186 return PVE::Ticket::verify_rsa_ticket(
c151b711 187 $rsa_pub, 'PMG', $ticket, undef,
1360e6f0
DM
188 $min_ticket_lifetime, $max_ticket_lifetime, $noerr);
189}
190
191# VNC tickets
192# - they do not contain the username in plain text
193# - they are restricted to a specific resource path (example: '/vms/100')
194sub assemble_vnc_ticket {
195 my ($username, $path) = @_;
196
c151b711
DM
197 my $rsa_priv = get_privkey();
198
1360e6f0
DM
199 my $secret_data = "$username:$path";
200
201 return PVE::Ticket::assemble_rsa_ticket(
c151b711 202 $rsa_priv, 'PMGVNC', undef, $secret_data);
1360e6f0
DM
203}
204
205sub verify_vnc_ticket {
206 my ($ticket, $username, $path, $noerr) = @_;
207
c151b711
DM
208 my $rsa_pub = get_pubkey();
209
1360e6f0
DM
210 my $secret_data = "$username:$path";
211
212 return PVE::Ticket::verify_rsa_ticket(
c151b711 213 $rsa_pub, 'PMGVNC', $ticket, $secret_data, -20, 40, $noerr);
1360e6f0
DM
214}
215
2161;