]> git.proxmox.com Git - pmg-api.git/commitdiff
bin/pmgcm: start cluster management toolkit + API
authorDietmar Maurer <dietmar@proxmox.com>
Tue, 4 Apr 2017 09:11:15 +0000 (11:11 +0200)
committerDietmar Maurer <dietmar@proxmox.com>
Wed, 5 Apr 2017 05:44:58 +0000 (07:44 +0200)
Makefile
PMG/API2/ClusterConfig.pm
PMG/CLI/pmgcm.pm [new file with mode: 0644]
PMG/Cluster.pm
PMG/ClusterConfig.pm
bin/pmgcm [new file with mode: 0644]

index d8c5181f3158da6731546097d179d3997f26da50..663b49533bea4165901b1746780bd3cd8948321f 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -18,7 +18,7 @@ BASHCOMPLDIR=${DESTDIR}/usr/share/bash-completion/completions/
 REPOID=`./repoid.pl .git`
 
 SERVICES = pmgdaemon pmgproxy
-CLITOOLS = pmgdb pmgconfig pmgperf
+CLITOOLS = pmgdb pmgconfig pmgperf pmgcm
 CLISCRIPTS = pmg-smtp-filter pmgsh pmgpolicy
 CRONSCRIPTS = pmg-hourly pmg-daily
 
index 7f315009af1a10814525d0db6478a7a31b222409..e115a6a3759d06d69abc070bf42459ab1d3d750e 100644 (file)
@@ -26,6 +26,7 @@ __PACKAGE__->register_method({
        additionalProperties => 0,
        properties => {},
     },
+    permissions => { check => [ 'admin' ] },
     returns => {
        type => 'array',
        items => {
@@ -44,4 +45,39 @@ __PACKAGE__->register_method({
        return PVE::RESTHandler::hash_to_array($cfg->{ids}, 'cid');
     }});
 
+__PACKAGE__->register_method({
+    name => 'create_master',
+    path => '',
+    method => 'POST',
+    description => "Create initial cluster config with current node as master.",
+    # alway read local file
+    parameters => {
+       additionalProperties => 0,
+       properties => {},
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       my $code = sub {
+           my $cfg = PMG::ClusterConfig->new();
+
+           die "cluster alreayd defined\n" if scalar(keys %{$cfg->{ids}});
+
+           my $info = PMG::Cluster::read_local_cluster_info();
+
+           $info->{type} = 'master';
+           $info->{maxcid} = 1,
+
+           $cfg->{ids}->{$info->{maxcid}} = $info;
+
+           $cfg->write();
+       };
+
+       PMG::ClusterConfig::lock_config($code, "create cluster failed");
+
+       return undef;
+    }});
+
+
 1;
diff --git a/PMG/CLI/pmgcm.pm b/PMG/CLI/pmgcm.pm
new file mode 100644 (file)
index 0000000..bd4edce
--- /dev/null
@@ -0,0 +1,37 @@
+package PMG::CLI::pmgcm;
+
+use strict;
+use warnings;
+use Data::Dumper;
+
+use PVE::SafeSyslog;
+use PVE::Tools qw(extract_param);
+use PVE::INotify;
+use PVE::CLIHandler;
+
+use PMG::DBTools;
+use PMG::Cluster;
+use PMG::ClusterConfig;
+use PMG::API2::ClusterConfig;
+
+use base qw(PVE::CLIHandler);
+
+my $format_nodelist = sub {
+    my $res = shift;
+
+    print "NAME(CID)--------------IPADDRESS----ROLE-STATE---------UPTIME---LOAD----MEM---DISK\n";
+    foreach my $ni (@$res) {
+       my $state = '?';
+       
+       printf "%-20s %-15s %-6s %1s %15s %6s %5s%% %5s%%\n", 
+       "$ni->{name}($ni->{cid})", $ni->{ip}, $ni->{type}, 
+       $state, '-', '-', '-', '-';
+    }
+};
+
+our $cmddef = {
+    nodes => [ 'PMG::API2::ClusterConfig', 'index', [], {}, $format_nodelist],
+    create => [ 'PMG::API2::ClusterConfig', 'create_master', []],
+};
+
+1;
index 40d1d88ff7777c74125a6ec597d4faa2bb51a34a..ca5de979955070444034334443f9777a3550c6d7 100644 (file)
@@ -31,79 +31,110 @@ sub remote_node_ip {
 sub get_master_node {
     my ($cinfo) = @_;
 
-    $cinfo = PVE::INotify::read_file("cluster.conf");
+    $cinfo = PVE::INotify::read_file("cluster.conf") if !$cinfo;
 
     return $cinfo->{master}->{name} if defined($cinfo->{master});
 
     return 'localhost';
 }
 
-# X509 Certificate cache helper
+sub read_local_ssl_cert_fingerprint {
+    my $cert_path = "/etc/pmg/pmg-api.pem";
 
-my $cert_cache_nodes = {};
-my $cert_cache_timestamp = time();
-my $cert_cache_fingerprints = {};
+    my $cert;
+    eval {
+       my $bio = Net::SSLeay::BIO_new_file($cert_path, 'r');
+       $cert = Net::SSLeay::PEM_read_bio_X509($bio);
+       Net::SSLeay::BIO_free($bio);
+    };
+    if (my $err = $@) {
+       die "unable to read certificate '$cert_path' - $err\n";
+    }
 
-sub update_cert_cache {
-    my ($update_node, $clear) = @_;
+    if (!defined($cert)) {
+       die "unable to read certificate '$cert_path' - got empty value\n";
+    }
+
+    my $fp;
+    eval {
+       $fp = Net::SSLeay::X509_get_fingerprint($cert, 'sha256');
+    };
+    if (my $err = $@) {
+       die "unable to get fingerprint for '$cert_path' - $err\n";
+    }
 
-    syslog('info', "Clearing outdated entries from certificate cache")
-       if $clear;
+    if (!defined($fp) || $fp eq '') {
+       die "unable to get fingerprint for '$cert_path' - got empty value\n";
+    }
 
-    $cert_cache_timestamp = time() if !defined($update_node);
+    return $fp;
+}
 
-    my $node_list = defined($update_node) ?
-       [ $update_node ] : [ keys %$cert_cache_nodes ];
+my $hostrsapubkey_fn = '/etc/ssh/ssh_host_rsa_key.pub';
+my $rootrsakey_fn = '/root/.ssh/id_rsa';
+my $rootrsapubkey_fn = '/root/.ssh/id_rsa.pub';
 
-    my $clear_node = sub {
-       my ($node) = @_;
-       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};
-       }
-    };
+sub read_local_cluster_info {
+
+    my $res = {};
+
+    my $hostrsapubkey = PVE::Tools::file_read_firstline($hostrsapubkey_fn);
+    $hostrsapubkey =~ s/^.*ssh-rsa\s+//i;
+    $hostrsapubkey =~ s/\s+root\@\S+\s*$//i;
+
+    die "unable to parse ${hostrsapubkey_fn}\n"
+       if $hostrsapubkey !~ m/^[A-Za-z0-9\.\/\+]{200,}$/;
 
     my $nodename = PVE::INotify::nodename();
 
-    foreach my $node (@$node_list) {
+    $res->{name} = $nodename;
 
-       if ($node ne $nodename) {
-           &$clear_node($node) if $clear;
-           next;
-       }
+    $res->{ip} = PMG::Utils::lookup_node_ip($nodename);
 
-       my $cert_path = "/etc/pmg/pmg-api.pem";
-
-       my $cert;
-       eval {
-           my $bio = Net::SSLeay::BIO_new_file($cert_path, 'r');
-           $cert = Net::SSLeay::PEM_read_bio_X509($bio);
-           Net::SSLeay::BIO_free($bio);
-       };
-       my $err = $@;
-       if ($err || !defined($cert)) {
-           &$clear_node($node) if $clear;
-           next;
-       }
+    $res->{hostrsapubkey} = $hostrsapubkey;
 
-       my $fp;
-       eval {
-           $fp = Net::SSLeay::X509_get_fingerprint($cert, 'sha256');
-       };
-       $err = $@;
-       if ($err || !defined($fp) || $fp eq '') {
-           &$clear_node($node) if $clear;
-           next;
-       }
+    if (! -f $rootrsapubkey_fn) {
+       unlink $rootrsakey_fn;
+       my $cmd = ['ssh-keygen', '-t', 'rsa', '-N', '', '-b', '2048',
+                  '-f', $rootrsakey_fn];
+       PVE::Tools::run_command($cmd);
+    }
+
+    my $rootrsapubkey = PVE::Tools::file_read_firstline($rootrsapubkey_fn);
+    $rootrsapubkey =~ s/^.*ssh-rsa\s+//i;
+    $rootrsapubkey =~ s/\s+root\@\S+\s*$//i;
+
+    die "unable to parse ${rootrsapubkey_fn}\n"
+       if $rootrsapubkey !~ m/^[A-Za-z0-9\.\/\+]{200,}$/;
+
+    $res->{rootrsapubkey} = $rootrsapubkey;
+
+    $res->{fingerprint} = read_local_ssl_cert_fingerprint();
+
+    return $res;
+}
+
+# X509 Certificate cache helper
+
+my $cert_cache_nodes = {};
+my $cert_cache_timestamp = time();
+my $cert_cache_fingerprints = {};
 
-       my $old_fp = $cert_cache_nodes->{$node};
-       $cert_cache_fingerprints->{$fp} = 1;
-       $cert_cache_nodes->{$node} = $fp;
+sub update_cert_cache {
 
-       if (defined($old_fp) && $fp ne $old_fp) {
-           delete $cert_cache_fingerprints->{$old_fp};
+    $cert_cache_timestamp = time();
+
+    $cert_cache_fingerprints = {};
+    $cert_cache_nodes = {};
+
+    my $cinfo = PVE::INotify::read_file("cluster.conf");
+
+    foreach my $entry (values %{$cinfo->{ids}}) {
+       my $node = $entry->{name};
+       my $fp = $entry->{fingerprint};
+       if ($node && $fp) {
+           $cert_cache_fingerprints->{$fp} = 1;
+           $cert_cache_nodes->{$node} = $fp;
        }
     }
 }
@@ -112,7 +143,7 @@ sub update_cert_cache {
 sub initialize_cert_cache {
     my ($node) = @_;
 
-    update_cert_cache($node)
+    update_cert_cache()
        if defined($node) && !defined($cert_cache_nodes->{$node});
 }
 
@@ -120,7 +151,7 @@ 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;
+    update_cert_cache() if time() - $cert_cache_timestamp >= 60*30;
 
     # get fingerprint of server certificate
     my $fp;
@@ -136,13 +167,13 @@ sub check_cert_fingerprint {
        return 0;
     };
 
-    return 1 if &$check();
+    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 $check->();
     }
 
     return 0;
index 27d04a8a0e62244a2b56eac141f0d38a5c670e65..3440118c512e77f065c3ed66ce9f4de588f53865 100644 (file)
@@ -60,10 +60,17 @@ sub properties {
        hostrsapubkey => {
            description => "Public SSH RSA key for the host.",
            type => 'string',
+           pattern => '^[A-Za-z0-9\.\/\+]{200,}$',
        },
        rootrsapubkey => {
            description => "Public SSH RSA key for the root user.",
            type => 'string',
+           pattern => '^[A-Za-z0-9\.\/\+]{200,}$',
+       },
+       fingerprint => {
+           description => "SSL certificate fingerprint.",
+           type => 'string',
+           pattern => '^(:?[A-Z0-9][A-Z0-9]:){31}[A-Z0-9][A-Z0-9]$',
        },
     };
 }
@@ -74,6 +81,7 @@ sub options {
        name => { fixed => 1 },
        hostrsapubkey => {},
        rootrsapubkey => {},
+       fingerprint => {},
     };
 }
 
@@ -105,6 +113,7 @@ sub options {
        name => { fixed => 1 },
        hostrsapubkey => {},
        rootrsapubkey => {},
+       fingerprint => {},
     };
 }
 
diff --git a/bin/pmgcm b/bin/pmgcm
new file mode 100644 (file)
index 0000000..55528d8
--- /dev/null
+++ b/bin/pmgcm
@@ -0,0 +1,12 @@
+#!/usr/bin/perl -T
+
+$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
+
+delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
+
+use strict;
+use warnings;
+
+use PMG::CLI::pmgcm;
+
+PMG::CLI::pmgcm->run_cli_handler();