]> git.proxmox.com Git - pmg-api.git/blob - PMG/Cluster.pm
use /etc/pmg/ instead of /etc/proxmox
[pmg-api.git] / PMG / Cluster.pm
1 package PMG::Cluster;
2
3 use strict;
4 use warnings;
5 use Data::Dumper;
6 use Socket;
7
8 use PVE::Tools;
9 use PVE::INotify;
10
11 use PMG::ClusterConfig;
12
13 sub remote_node_ip {
14 my ($nodename, $noerr) = @_;
15
16 my $cinfo = PVE::INotify::read_file("cluster.conf");
17
18 foreach my $entry (values %{$cinfo->{ids}}) {
19 if ($entry->{name} eq $nodename) {
20 my $ip = $entry->{ip};
21 return $ip if !wantarray;
22 my $family = PVE::Tools::get_host_address_family($ip);
23 return ($ip, $family);
24 }
25 }
26
27 # fallback: try to get IP by other means
28 return PMG::Utils::lookup_node_ip($nodename, $noerr);
29 }
30
31 sub get_master_node {
32 my ($cinfo) = @_;
33
34 $cinfo = PVE::INotify::read_file("cluster.conf");
35
36 return $cinfo->{master}->{name} if defined($cinfo->{master});
37
38 return 'localhost';
39 }
40
41 # X509 Certificate cache helper
42
43 my $cert_cache_nodes = {};
44 my $cert_cache_timestamp = time();
45 my $cert_cache_fingerprints = {};
46
47 sub update_cert_cache {
48 my ($update_node, $clear) = @_;
49
50 syslog('info', "Clearing outdated entries from certificate cache")
51 if $clear;
52
53 $cert_cache_timestamp = time() if !defined($update_node);
54
55 my $node_list = defined($update_node) ?
56 [ $update_node ] : [ keys %$cert_cache_nodes ];
57
58 my $clear_node = sub {
59 my ($node) = @_;
60 if (my $old_fp = $cert_cache_nodes->{$node}) {
61 # distrust old fingerprint
62 delete $cert_cache_fingerprints->{$old_fp};
63 # ensure reload on next proxied request
64 delete $cert_cache_nodes->{$node};
65 }
66 };
67
68 my $nodename = PVE::INotify::nodename();
69
70 foreach my $node (@$node_list) {
71
72 if ($node ne $nodename) {
73 &$clear_node($node) if $clear;
74 next;
75 }
76
77 my $cert_path = "/etc/pmg/pmg-api.pem";
78
79 my $cert;
80 eval {
81 my $bio = Net::SSLeay::BIO_new_file($cert_path, 'r');
82 $cert = Net::SSLeay::PEM_read_bio_X509($bio);
83 Net::SSLeay::BIO_free($bio);
84 };
85 my $err = $@;
86 if ($err || !defined($cert)) {
87 &$clear_node($node) if $clear;
88 next;
89 }
90
91 my $fp;
92 eval {
93 $fp = Net::SSLeay::X509_get_fingerprint($cert, 'sha256');
94 };
95 $err = $@;
96 if ($err || !defined($fp) || $fp eq '') {
97 &$clear_node($node) if $clear;
98 next;
99 }
100
101 my $old_fp = $cert_cache_nodes->{$node};
102 $cert_cache_fingerprints->{$fp} = 1;
103 $cert_cache_nodes->{$node} = $fp;
104
105 if (defined($old_fp) && $fp ne $old_fp) {
106 delete $cert_cache_fingerprints->{$old_fp};
107 }
108 }
109 }
110
111 # load and cache cert fingerprint once
112 sub initialize_cert_cache {
113 my ($node) = @_;
114
115 update_cert_cache($node)
116 if defined($node) && !defined($cert_cache_nodes->{$node});
117 }
118
119 sub check_cert_fingerprint {
120 my ($cert) = @_;
121
122 # clear cache every 30 minutes at least
123 update_cert_cache(undef, 1) if time() - $cert_cache_timestamp >= 60*30;
124
125 # get fingerprint of server certificate
126 my $fp;
127 eval {
128 $fp = Net::SSLeay::X509_get_fingerprint($cert, 'sha256');
129 };
130 return 0 if $@ || !defined($fp) || $fp eq ''; # error
131
132 my $check = sub {
133 for my $expected (keys %$cert_cache_fingerprints) {
134 return 1 if $fp eq $expected;
135 }
136 return 0;
137 };
138
139 return 1 if &$check();
140
141 # clear cache and retry at most once every minute
142 if (time() - $cert_cache_timestamp >= 60) {
143 syslog ('info', "Could not verify remote node certificate '$fp' with list of pinned certificates, refreshing cache");
144 update_cert_cache();
145 return &$check();
146 }
147
148 return 0;
149 }
150
151 1;