From 869df6202fc924f8e053635557273b15124a5ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Gr=C3=BCnbichler?= Date: Mon, 11 Nov 2019 11:28:33 +0100 Subject: [PATCH] takeover CertCache from pve-cluster MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit same code, same semantics, different file/module Signed-off-by: Fabian Grünbichler --- PVE/CertCache.pm | 92 +++++++++++++++++++++++++++++++++++++++++++++++ PVE/HTTPServer.pm | 5 +-- PVE/Makefile | 1 + 3 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 PVE/CertCache.pm diff --git a/PVE/CertCache.pm b/PVE/CertCache.pm new file mode 100644 index 000000000..4eadab29a --- /dev/null +++ b/PVE/CertCache.pm @@ -0,0 +1,92 @@ +package PVE::CertCache; + +use strict; +use warnings; + +use Net::SSLeay; + +use PVE::Cluster; +use PVE::SafeSyslog; + +# X509 Certificate cache helper + +my $cert_cache_nodes = {}; +my $cert_cache_timestamp = time(); +my $cert_cache_fingerprints = {}; + +sub update_cert_cache { + my ($update_node, $clear) = @_; + + syslog('info', "Clearing outdated entries from certificate cache") + if $clear; + + $cert_cache_timestamp = time() if !defined($update_node); + + my $node_list = defined($update_node) ? + [ $update_node ] : [ keys %$cert_cache_nodes ]; + + foreach my $node (@$node_list) { + my $clear_old = sub { + if (my $old_fp = $cert_cache_nodes->{$node}) { + # distrust old fingerprint + delete $cert_cache_fingerprints->{$old_fp}; + # ensure reload on next proxied request + delete $cert_cache_nodes->{$node}; + } + }; + + my $fp = eval { PVE::Cluster::get_node_fingerprint($node) }; + if (my $err = $@) { + warn "$err\n"; + &$clear_old() if $clear; + next; + } + + my $old_fp = $cert_cache_nodes->{$node}; + $cert_cache_fingerprints->{$fp} = 1; + $cert_cache_nodes->{$node} = $fp; + + if (defined($old_fp) && $fp ne $old_fp) { + delete $cert_cache_fingerprints->{$old_fp}; + } + } +} + +# load and cache cert fingerprint once +sub initialize_cert_cache { + my ($node) = @_; + + update_cert_cache($node) + if defined($node) && !defined($cert_cache_nodes->{$node}); +} + +sub check_cert_fingerprint { + my ($cert) = @_; + + # clear cache every 30 minutes at least + update_cert_cache(undef, 1) if time() - $cert_cache_timestamp >= 60*30; + + # get fingerprint of server certificate + my $fp = Net::SSLeay::X509_get_fingerprint($cert, 'sha256'); + return 0 if !defined($fp) || $fp eq ''; # error + + my $check = sub { + for my $expected (keys %$cert_cache_fingerprints) { + return 1 if $fp eq $expected; + } + return 0; + }; + + return 1 if &$check(); + + # clear cache and retry at most once every minute + if (time() - $cert_cache_timestamp >= 60) { + syslog ('info', "Could not verify remote node certificate '$fp' with list of pinned certificates, refreshing cache"); + update_cert_cache(); + return &$check(); + } + + return 0; +} + +1; diff --git a/PVE/HTTPServer.pm b/PVE/HTTPServer.pm index ce8957258..e9572c71a 100755 --- a/PVE/HTTPServer.pm +++ b/PVE/HTTPServer.pm @@ -11,6 +11,7 @@ use PVE::Exception qw(raise_param_exc raise); use PVE::RPCEnvironment; use PVE::AccessControl; +use PVE::CertCache; use PVE::Cluster; use PVE::API2Tools; @@ -199,13 +200,13 @@ sub rest_handler { sub check_cert_fingerprint { my ($self, $cert) = @_; - return PVE::Cluster::check_cert_fingerprint($cert); + return PVE::CertCache::check_cert_fingerprint($cert); } sub initialize_cert_cache { my ($self, $node) = @_; - PVE::Cluster::initialize_cert_cache($node); + PVE::CertCache::initialize_cert_cache($node); } sub remote_node_ip { diff --git a/PVE/Makefile b/PVE/Makefile index 71c91b9c7..0071fab1b 100644 --- a/PVE/Makefile +++ b/PVE/Makefile @@ -7,6 +7,7 @@ PERLSOURCE = \ API2Tools.pm \ APLInfo.pm \ AutoBalloon.pm \ + CertCache.pm \ CertHelpers.pm \ ExtMetric.pm \ HTTPServer.pm \