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