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