Author: arkurth
Date: Fri Jun  2 21:25:53 2017
New Revision: 1797452

URL: http://svn.apache.org/viewvc?rev=1797452&view=rev
Log:
VCL-1045
Implemented code so that $self->data->get_image_domain_password() returns a 
decrypted password string.

Added subroutines:
* DataStructure.pm::get_image_domain_password
* ManagementNode.pm::get_private_key_file_path
* ManagementNode.pm::_get_private_key_object_from_file
* ManagementNode.pm::extract_public_key_from_private_key_file
* ManagementNode.pm::check_encryption_keys
* ManagementNode.pm::generate_private_key_file
* ManagementNode.pm::decrypt_cryptsecret
* utils.pm::get_management_node_cryptkey_pubkey
* utils.pm::set_management_node_cryptkey_pubkey
* utils.pm::get_management_node_cryptsecret_info
* utils.pm::get_management_node_cryptsecret_value
* utils.pm::delete_management_node_cryptsecret
* utils.pm::update_reservation_cryptsecret

Added cryptkey/crypsecret data to return value from 
utils.pm::get_image_active_directory_domain_info. (This is mainly for 
debugging, the values from this hash aren't used.)

Added perl-Crypt-CBC to install_perl_libs.pl, now used by ManagementNode.pm.

Updated DataStructure.pm::SUBROUTINE_MAPPINGS to match current schema for AD 
tables.

Modified:
    vcl/trunk/managementnode/bin/install_perl_libs.pl
    vcl/trunk/managementnode/lib/VCL/DataStructure.pm
    vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/ManagementNode.pm
    vcl/trunk/managementnode/lib/VCL/utils.pm

Modified: vcl/trunk/managementnode/bin/install_perl_libs.pl
URL: 
http://svn.apache.org/viewvc/vcl/trunk/managementnode/bin/install_perl_libs.pl?rev=1797452&r1=1797451&r2=1797452&view=diff
==============================================================================
--- vcl/trunk/managementnode/bin/install_perl_libs.pl (original)
+++ vcl/trunk/managementnode/bin/install_perl_libs.pl Fri Jun  2 21:25:53 2017
@@ -57,6 +57,7 @@ my @LINUX_PACKAGES = (
        'openssl-devel',
        'perl-Archive-Tar',
        'perl-CPAN',
+       'perl-Crypt-CBC',
        'perl-Crypt-OpenSSL-RSA',
        'perl-DBD-MySQL',
        'perl-DBI',

Modified: vcl/trunk/managementnode/lib/VCL/DataStructure.pm
URL: 
http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/DataStructure.pm?rev=1797452&r1=1797451&r2=1797452&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/DataStructure.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/DataStructure.pm Fri Jun  2 21:25:53 2017
@@ -394,15 +394,17 @@ $SUBROUTINE_MAPPINGS{imagemeta_subimages
 $SUBROUTINE_MAPPINGS{imagemeta_sysprep} = 
'$self->request_data->{reservation}{RESERVATION_ID}{image}{imagemeta}{sysprep}';
 $SUBROUTINE_MAPPINGS{imagemeta_rootaccess} = 
'$self->request_data->{reservation}{RESERVATION_ID}{image}{imagemeta}{rootaccess}';
 $SUBROUTINE_MAPPINGS{imagemeta_sethostname} = 
'$self->request_data->{reservation}{RESERVATION_ID}{image}{imagemeta}{sethostname}';
-
-$SUBROUTINE_MAPPINGS{image_domain_dns_servers} = 
'$self->request_data->{reservation}{RESERVATION_ID}{image}{imagedomain}{dnsServers}';
+  
+#$SUBROUTINE_MAPPINGS{image_domain_dns_servers} = 
'$self->request_data->{reservation}{RESERVATION_ID}{image}{imagedomain}{dnsServers}';
 # Explicit subroutine
 $SUBROUTINE_MAPPINGS{image_domain_dns_name} = 
'$self->request_data->{reservation}{RESERVATION_ID}{image}{imagedomain}{domainDNSName}';
 $SUBROUTINE_MAPPINGS{image_domain_id} = 
'$self->request_data->{reservation}{RESERVATION_ID}{image}{imagedomain}{id}';
-$SUBROUTINE_MAPPINGS{image_domain_login_description} = 
'$self->request_data->{reservation}{RESERVATION_ID}{image}{imagedomain}{logindescription}';
-$SUBROUTINE_MAPPINGS{image_domain_password} = 
'$self->request_data->{reservation}{RESERVATION_ID}{image}{imagedomain}{password}';
-$SUBROUTINE_MAPPINGS{image_domain_prettyname} = 
'$self->request_data->{reservation}{RESERVATION_ID}{image}{imagedomain}{prettyname}';
+$SUBROUTINE_MAPPINGS{image_domain_name} = 
'$self->request_data->{reservation}{RESERVATION_ID}{image}{imagedomain}{name}';
+$SUBROUTINE_MAPPINGS{image_domain_owner_id} = 
'$self->request_data->{reservation}{RESERVATION_ID}{image}{imagedomain}{ownerid}';
+#$SUBROUTINE_MAPPINGS{image_domain_password} = 
'$self->request_data->{reservation}{RESERVATION_ID}{image}{imagedomain}{password}';
 # Explicit subroutine
+$SUBROUTINE_MAPPINGS{image_domain_secret_id} = 
'$self->request_data->{reservation}{RESERVATION_ID}{image}{imagedomain}{secretid}';
 $SUBROUTINE_MAPPINGS{image_domain_username} = 
'$self->request_data->{reservation}{RESERVATION_ID}{image}{imagedomain}{username}';
 $SUBROUTINE_MAPPINGS{image_domain_base_ou} = 
'$self->request_data->{reservation}{RESERVATION_ID}{image}{imagedomain}{imageaddomain}{baseOU}';
+$SUBROUTINE_MAPPINGS{image_domain_cryptsecret} = 
'$self->request_data->{reservation}{RESERVATION_ID}{image}{imagedomain}{cryptsecret}{cryptsecret}';
 
 $SUBROUTINE_MAPPINGS{image_os_name} = 
'$self->request_data->{reservation}{RESERVATION_ID}{image}{OS}{name}';
 $SUBROUTINE_MAPPINGS{image_os_prettyname} = 
'$self->request_data->{reservation}{RESERVATION_ID}{image}{OS}{prettyname}';
@@ -2765,6 +2767,40 @@ sub get_invalid_substitution_identifiers
 }
 
 #//////////////////////////////////////////////////////////////////////////////
+
+=head2 get_image_domain_password
+
+ Parameters  : none
+ Returns     : string
+ Description : Returns the decrypted Active Directory domain password.
+
+=cut
+
+sub get_image_domain_password {
+       my $self = shift;
+       if (ref($self) !~ /VCL::/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return 0;
+       }
+       
+       my $reservation_id = $self->reservation_id();
+       
+       my $secret_id = $self->get_image_domain_secret_id();
+       if (!defined($secret_id)) {
+               notify($ERRORS{'WARNING'}, 0, "failed to retrieve decrypted 
domain password, addomain.secretid is not defined in this DataStructure.pm 
object");
+               return;
+       }
+       
+       my $encrypted_password = 
$self->request_data->{reservation}{$reservation_id}{image}{imagedomain}{password};
+       if (!defined($encrypted_password)) {
+               notify($ERRORS{'WARNING'}, 0, "failed to retrieve decrypted 
domain password, imagedomain.password is not defined in this DataStructure.pm 
object");
+               return;
+       }
+       
+       return $self->mn_os->decrypt_cryptsecret($secret_id, 
$encrypted_password);
+}
+
+#//////////////////////////////////////////////////////////////////////////////
 
 1;
 __END__

Modified: vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/ManagementNode.pm
URL: 
http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/ManagementNode.pm?rev=1797452&r1=1797451&r2=1797452&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/ManagementNode.pm 
(original)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/ManagementNode.pm Fri Jun  
2 21:25:53 2017
@@ -55,6 +55,12 @@ use diagnostics;
 
 use VCL::utils;
 
+use Crypt::CBC;
+use Crypt::OpenSSL::RSA;
+use English;
+use File::Basename;
+use MIME::Base64;
+
 ###############################################################################
 
 =head1 CLASS VARIABLES
@@ -75,6 +81,15 @@ use VCL::utils;
 
 our $MN_STAGE_SCRIPTS_DIRECTORY = "$TOOLS/ManagementNode/Scripts";
 
+=head2 $MN_PRIVATE_ENCRYPTION_KEY_FILE_PATH
+
+ Data type   : String
+ Description : /root/.vcl/<FQDN>.key
+
+=cut
+
+our $MN_PRIVATE_ENCRYPTION_KEY_FILE_PATH = "/root/.vcl/$FQDN.key";
+
 ###############################################################################
 
 =head1 OBJECT METHODS
@@ -261,7 +276,7 @@ sub create_text_file {
 
 =head2 get_file_contents
 
- Parameters  : $file_path
+ Parameters  : $file_path, $display_warnings (optional)
  Returns     : array or string
  Description : Retrieves the contents of a file on the management node.
 
@@ -274,16 +289,17 @@ sub get_file_contents {
                return;
        }
        
-       my ($file_path) = @_;
+       my ($file_path, $display_warnings) = @_;
        if (!defined($file_path)) {
                notify($ERRORS{'WARNING'}, 0, "file path argument was not 
supplied");
                return;
        }
+       $display_warnings = 1 unless defined($display_warnings);
        
        my $computer_node_name = $self->data->get_computer_node_name();
        
        if (!open FILE, '<', $file_path) {
-               notify($ERRORS{'WARNING'}, 0, "failed to retrieve contents of 
file on $computer_node_name, file could not be opened: $file_path");
+               notify($ERRORS{'WARNING'}, 0, "failed to retrieve contents of 
file on $computer_node_name, file could not be opened: $file_path") if 
$display_warnings;
                return;
        }
        my @lines = <FILE>;
@@ -604,6 +620,380 @@ sub setup_get_menu {
 }
 
 #//////////////////////////////////////////////////////////////////////////////
+
+=head2 get_private_key_file_path
+
+ Parameters  : none
+ Returns     : string
+ Description : Returns the location on the management node where the private 
key
+               resides that is used to decrypt secrets: /root/.vcl/<FQDN>.key
+
+=cut
+
+sub get_private_key_file_path {
+       return $MN_PRIVATE_ENCRYPTION_KEY_FILE_PATH;
+}
+
+#//////////////////////////////////////////////////////////////////////////////
+
+=head2 _get_private_key_object_from_file
+
+ Parameters  : none
+ Returns     : string
+ Description : Retrieves the private key string from the file on the management
+               node and creates a Crypt::OpenSSL::RSA object based on it.
+
+=cut
+
+sub _get_private_key_object_from_file {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return;
+       }
+       
+       return $self->{private_key_object_from_file} if 
defined($self->{private_key_object_from_file});
+       
+       my $management_node_short_name = 
$self->data->get_management_node_short_name() || return;
+       
+       my $private_key_file_path = $self->get_private_key_file_path();
+       if (!$self->file_exists($private_key_file_path)) {
+               notify($ERRORS{'OK'}, 0, "unable to retrieve private key from 
file on $management_node_short_name because file does NOT exist: 
$private_key_file_path");
+               return;
+       }
+       
+       my $private_key_file_string = 
$self->get_file_contents($private_key_file_path);
+       if (!$private_key_file_string) {
+               notify($ERRORS{'WARNING'}, 0, "failed to retrieve private key 
from file on $management_node_short_name: $private_key_file_path");
+               return;
+       }
+       
+       # Create an RSA object based on the existing private key contained in 
the file
+       my $rsa_private;
+       eval {
+               $rsa_private = 
Crypt::OpenSSL::RSA->new_private_key($private_key_file_string);
+       };
+       if ($EVAL_ERROR || !$rsa_private) {
+               notify($ERRORS{'WARNING'}, 0, "failed to create private key 
file Crypt::OpenSSL::RSA object from $private_key_file_path on 
$management_node_short_name" . ($EVAL_ERROR ? ", error:\n" . $EVAL_ERROR : ''));
+               return;
+       }
+       
+       $self->{private_key_object_from_file} = $rsa_private;
+       return $rsa_private;
+}
+
+#//////////////////////////////////////////////////////////////////////////////
+
+=head2 extract_public_key_from_private_key_file
+
+ Parameters  : none
+ Returns     : string
+ Description : Retrieves the private key from the file on the management node
+               and extracts the public key from the private key. The public key
+               string is returned.
+
+=cut
+
+sub extract_public_key_from_private_key_file {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return;
+       }
+       
+       my $rsa_private = $self->_get_private_key_object_from_file() || return;
+       
+       my $management_node_short_name = 
$self->data->get_management_node_short_name();
+       my $private_key_file_path = $self->get_private_key_file_path();
+       
+       # Retrieve the public key string from the RSA object
+       my $public_key_string;
+       eval {
+               $public_key_string = $rsa_private->get_public_key_x509_string();
+       };
+       if ($EVAL_ERROR || !$public_key_string) {
+               notify($ERRORS{'WARNING'}, 0, "failed to extract public key 
from private key file $private_key_file_path on $management_node_short_name, 
failed to retrieve public key from private key contained in the file" . 
($EVAL_ERROR ? ", error:\n" . $EVAL_ERROR : ''));
+               return;
+       }
+       
+       return $public_key_string;
+}
+
+#//////////////////////////////////////////////////////////////////////////////
+
+=head2 check_encryption_keys
+
+ Parameters  : none
+ Returns     : string
+ Description : Retrieves the cryptkeys.pubkey value from the database for the
+               management node and extracts the public key from the private key
+               file on the management node. Returns true if they match. Returns
+               false if they differ or if either could not be retrieved.
+
+=cut
+
+sub check_encryption_keys {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return;
+       }
+       
+       my $management_node_id = $self->data->get_management_node_id() || 
return;
+       my $management_node_short_name = 
$self->data->get_management_node_short_name() || return;
+       
+       notify($ERRORS{'DEBUG'}, 0, "*** checking encryption keys on 
$management_node_short_name ***");
+       
+       # Get the cryptkey.pubkey value from the database for the management 
node
+       my $public_key_string_database_value = 
get_management_node_cryptkey_pubkey($management_node_id, 0) || return;
+       
+       # Create an RSA object based on the existing public key stored in the 
database, then extract the (hopefully same) public key from the object
+       # Do this to verify the public key is correctly formatted, the RSA 
module should strip any extraneous space or newlines
+       my $rsa_public;
+       eval {
+               $rsa_public = 
Crypt::OpenSSL::RSA->new_public_key($public_key_string_database_value);
+       };
+       if ($EVAL_ERROR || !$rsa_public) {
+               notify($ERRORS{'WARNING'}, 0, "failed to create RSA object from 
public key stored in database:\n$public_key_string_database_value" . 
($EVAL_ERROR ? ", error:\n" . $EVAL_ERROR : ''));
+               return;
+       }
+       
+       # Retrieve the public key string from the RSA object
+       my $public_key_string_database_extracted;
+       eval {
+               $public_key_string_database_extracted = 
$rsa_public->get_public_key_x509_string();
+       };
+       if ($EVAL_ERROR || !$public_key_string_database_extracted) {
+               notify($ERRORS{'WARNING'}, 0, "retrieved cryptkey.pubkey value 
from database, created RSA object based on this public key, but failed to 
extract the public key from the object, there may be a problem with the public 
key stored in the database:\n$public_key_string_database_value" . ($EVAL_ERROR 
? ", error:\n" . $EVAL_ERROR : ''));
+               return;
+       }
+       
+       # Extract the public key string from the private key file stored on the 
management node
+       my $public_key_string_private_extracted = 
$self->extract_public_key_from_private_key_file() || return;
+       
+       if ($public_key_string_database_extracted eq 
$public_key_string_private_extracted) {
+               notify($ERRORS{'OK'}, 0, "public key extracted from private key 
file on $management_node_short_name matches database cryptkey.pubkey value");
+               return 1;
+       }
+       else {
+               notify($ERRORS{'WARNING'}, 0, "public key extracted from 
private key file on $management_node_short_name does not match database 
cryptkey.pubkey value:\n" .
+                       "public key stored in database (cryptkey.pubkey):\n" . 
string_to_ascii($public_key_string_database_extracted) . "\n" .
+                       "public key extracted from private key file:\n" . 
string_to_ascii($public_key_string_private_extracted)
+               );
+               return 0;
+       }
+}
+
+#//////////////////////////////////////////////////////////////////////////////
+
+=head2 generate_private_key_file
+
+ Parameters  : none
+ Returns     : boolean
+ Description : Creates a 4096 bit RSA private key file on the management node
+               at /root/.vcl/<FQDN>.key.
+
+=cut
+
+sub generate_private_key_file {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return;
+       }
+       
+       my $arguments_hash_ref = shift;
+       if (defined($arguments_hash_ref) && !ref($arguments_hash_ref) || 
ref($arguments_hash_ref) ne 'HASH') {
+               notify($ERRORS{'WARNING'}, 0, "argument was supplied but is not 
a hash reference:\n" . format_data($arguments_hash_ref));
+               return;
+       }
+       
+       my $private_key_file_path = $self->get_private_key_file_path();
+       my $bits = 4096;
+       
+       # If provided and true, existing private key file will be deleted if it 
exists
+       my $force = $arguments_hash_ref->{force};
+       
+       my $management_node_id = $self->data->get_management_node_id() || 
return;
+       my $management_node_short_name = 
$self->data->get_management_node_short_name() || return;
+       my $reservation_id = $self->data->get_reservation_id();
+       
+       notify($ERRORS{'DEBUG'}, 0, "*** attempting to generate a new private 
key file on $management_node_short_name: $private_key_file_path ***");
+       
+       # Make sure the private key file does not already exist
+       if ($self->file_exists($private_key_file_path)) {
+               if ($force) {
+                       notify($ERRORS{'OK'}, 0, "force argument was specified, 
existing private key file will be overwritten: $private_key_file_path");
+               }
+               else {
+                       notify($ERRORS{'WARNING'}, 0, "failed to generate 
encryption keys, private key file already exists: $private_key_file_path");
+                       return;
+               }
+       }
+       
+       # Delete cached RSA object
+       if ($self->{private_key_object_from_file}) {
+               notify($ERRORS{'DEBUG'}, 0, "deleting cached RSA private key 
object");
+               delete $self->{private_key_object_from_file};
+       }
+       
+       # Delete all existing cryptsecret entries for the management node
+       # The website's API won't delete any that may have been created with an 
earlier key
+       delete_management_node_cryptsecret($management_node_id);
+       
+       # Create a new RSA object containing a private/public key pair
+       # Create an RSA object based on the existing private key contained in 
the file
+       my $rsa_generate;
+       eval {
+               $rsa_generate = Crypt::OpenSSL::RSA->generate_key($bits);
+       };
+       if ($EVAL_ERROR || !$rsa_generate) {
+               notify($ERRORS{'WARNING'}, 0, "failed to create private key 
file on management node $management_node_short_name: $private_key_file_path, 
RSA object could not be created" . ($EVAL_ERROR ? ", error:\n" . $EVAL_ERROR : 
''));
+               return;
+       }
+
+       my $private_key_string;
+       eval {
+               $private_key_string = $rsa_generate->get_private_key_string();
+       };
+       if ($EVAL_ERROR || !$private_key_string) {
+               notify($ERRORS{'WARNING'}, 0, "failed to create private key 
file on management node $management_node_short_name: $private_key_file_path, 
private key string could not be retireved from RSA object" . ($EVAL_ERROR ? ", 
error:\n" . $EVAL_ERROR : ''));
+               return;
+       }
+       
+       my $public_key_string;
+       eval {
+               $public_key_string = 
$rsa_generate->get_public_key_x509_string();
+       };
+       if ($EVAL_ERROR ||  !$public_key_string) {
+               notify($ERRORS{'WARNING'}, 0, "failed to create private key 
file on management node $management_node_short_name: $private_key_file_path, 
public key string could not be retireved from RSA object" . ($EVAL_ERROR ? ", 
error:\n" . $EVAL_ERROR : ''));
+               return;
+       }
+       
+       $self->create_text_file($private_key_file_path, $private_key_string) || 
return;
+       
+       # Update cryptkey table with the public key string
+       if (!set_management_node_cryptkey_pubkey($management_node_id, 
$public_key_string)) {
+               notify($ERRORS{'WARNING'}, 0, "created private key file on 
management node $management_node_short_name: $private_key_file_path, failed to 
update cryptkey table in database, attempting to delete private key file just 
created: $private_key_file_path");
+               $self->delete_file($private_key_file_path);
+               return;
+       }
+       
+       # Call the XML-RPC API to create a new cryptsecret entry for this 
reservation
+       update_reservation_cryptsecret($reservation_id);
+       
+       notify($ERRORS{'OK'}, 0, "created private key file on management node 
$management_node_short_name: $private_key_file_path, updated cryptkey.pubkey 
value in database");
+       return 1;
+}
+
+#//////////////////////////////////////////////////////////////////////////////
+
+=head2 decrypt_cryptsecret
+
+ Parameters  : $secret_id, $encrypted_string
+ Returns     : string
+ Description : Decrypts an encrypted string stored in the database such as
+               addomain.password.
+               
+               The $secret_id argument must match a cryptsecret.secretid value
+               in the database. The corresponding cryptsecret.cryptsecret value
+               is a base64-encoded string that is encrypted using the 
management
+               node's public key stored in cryptkey.pubkey. The management
+               node's private key is used to decrypt it.
+
+=cut
+
+sub decrypt_cryptsecret {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return;
+       }
+       
+       my ($secret_id, $encrypted_string, $recreate_key) = @_;
+       if (!defined($secret_id)) {
+               notify($ERRORS{'WARNING'}, 0, "secret ID argument was not 
supplied");
+               return;
+       }
+       if (!defined($encrypted_string)) {
+               notify($ERRORS{'WARNING'}, 0, "encrypted string argument was 
not supplied");
+               return;
+       }
+       
+       my $management_node_id = $self->data->get_management_node_id() || 
return;
+       my $management_node_short_name = 
$self->data->get_management_node_short_name() || return;
+       
+       if ($recreate_key) {
+               notify($ERRORS{'DEBUG'}, 0, "previous attempt to decrypt 
cryptsecret failed, attempting to regenerate private key and cryptsecret 
entries");
+               if (!$self->generate_private_key_file({force => 1})) {
+                       notify($ERRORS{'WARNING'}, 0, "unable to decrypt secret 
ID $secret_id, failed to verify private key stored in file on management node 
is valid and its public key matches the cryptkey.pubkey value in the database");
+                       return;
+               }
+       }
+       elsif (!$self->check_encryption_keys()) {
+               return $self->decrypt_cryptsecret($secret_id, 
$encrypted_string, 1);
+       }
+       
+       my $cryptsecret = 
get_management_node_cryptsecret_value($management_node_id, $secret_id);
+       if (!$cryptsecret) {
+               notify($ERRORS{'WARNING'}, 0, "unable to decrypt secret ID 
$secret_id, failed to retrieve cryptsecret.cryptsecret value for management 
node ID $management_node_id");
+               $recreate_key ? return : return 
$self->decrypt_cryptsecret($secret_id, $encrypted_string, 1);
+       }
+       
+       # The encrypted string (addomain.password, etc) and 
cryptsecret.cryptsecret should ALWAYS be base64 encoded
+       # They must be decoded to binary before being passed to decrypt 
functions
+       my $encrypted_string_decoded = decode_base64($encrypted_string);
+       my $cryptsecret_decoded = decode_base64($cryptsecret);
+       
+       my $rsa_private = $self->_get_private_key_object_from_file();
+       if (!$rsa_private) {
+               notify($ERRORS{'WARNING'}, 0, "unable to decrypt secret ID 
$secret_id, failed to create RSA object based on management node's private 
key");
+               $recreate_key ? return : return 
$self->decrypt_cryptsecret($secret_id, $encrypted_string, 1);
+       }
+       
+       my $key;
+       eval {
+               $key = $rsa_private->decrypt($cryptsecret_decoded);
+       };
+       if ($EVAL_ERROR || !$key) {
+               # Wrong key error:
+               # RSA.xs:202: OpenSSL error: oaep decoding error
+               notify($ERRORS{'WARNING'}, 0, "unable to decrypt secret ID 
$secret_id, failed to decrypt cryptsecret using RSA object based on management 
node's private key" . ($EVAL_ERROR ? ", error:\n" . $EVAL_ERROR : ''));
+               $recreate_key ? return : return 
$self->decrypt_cryptsecret($secret_id, $encrypted_string, 1);
+       }
+       
+       my $encrypted_string_length = length($encrypted_string_decoded);
+       if ($encrypted_string_length < 32) {
+               # This should always be at least 32 bytes
+               # If less than 16, the next 2 substr commands may fail with 
'substr outside of string' errors
+               notify($ERRORS{'WARNING'}, 0, "unable to decrypt secret ID 
$secret_id, encrypted string length: $encrypted_string_length bytes, it must be 
32 bytes or more:\n$encrypted_string\n\n" . decode_base64($encrypted_string));
+               return;
+       }
+       
+       my $iv = substr($encrypted_string_decoded, 0, 16);
+       my $ciphered_string = substr($encrypted_string_decoded, 16);
+       
+       my $cipher = Crypt::CBC->new(
+               {
+                       'key'                           => $key,
+                       'cipher'                        => 
'Crypt::OpenSSL::AES',
+                       'iv'                            => $iv,
+                       'header'                        => 'none',
+                       'literal_key'   => 1,
+               }
+       );
+       my $decrypted_string = $cipher->decrypt($ciphered_string);
+       if (defined($decrypted_string)) {
+               notify($ERRORS{'OK'}, 0, "decrypted secret ID $secret_id");
+               return $decrypted_string;
+       }
+       else {
+               notify($ERRORS{'WARNING'}, 0, "failed to decrypt secret ID 
$secret_id");
+               $recreate_key ? return : return 
$self->decrypt_cryptsecret($secret_id, $encrypted_string, 1);
+       }
+}
+
+#//////////////////////////////////////////////////////////////////////////////
 
 1;
 __END__

Modified: vcl/trunk/managementnode/lib/VCL/utils.pm
URL: 
http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/utils.pm?rev=1797452&r1=1797451&r2=1797452&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/utils.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/utils.pm Fri Jun  2 21:25:53 2017
@@ -107,6 +107,7 @@ our @EXPORT = qw(
        database_execute
        database_select
        delete_computerloadlog_reservation
+       delete_management_node_cryptsecret
        delete_request
        delete_reservation_account
        delete_variable
@@ -165,6 +166,9 @@ our @EXPORT = qw(
        get_management_node_computer_ids
        get_management_node_id
        get_management_node_info
+       get_management_node_cryptkey_pubkey
+       get_management_node_cryptsecret_info
+       get_management_node_cryptsecret_value
        get_management_node_requests
        get_management_node_vmhost_ids
        get_management_node_vmhost_info
@@ -243,6 +247,7 @@ our @EXPORT = qw(
        run_scp_command
        run_ssh_command
        set_logfile_path
+       set_management_node_cryptkey_pubkey
        set_managementnode_state
        set_reservation_lastcheck
        set_variable
@@ -288,6 +293,7 @@ our @EXPORT = qw(
        update_preload_flag
        update_request_checkuser
        update_request_state
+       update_reservation_cryptsecret
        update_reservation_lastcheck
        update_reservation_natlog
        update_reservation_password
@@ -14511,12 +14517,16 @@ sub get_image_active_directory_domain_in
                return;
        }
        
+       my $management_node_id = get_management_node_id();
+       
        # Get a hash ref containing the database column names
        my $database_table_columns = get_database_table_columns();
        
        my @tables = (
                'addomain',
                'imageaddomain',
+               'cryptsecret',
+               #'cryptkey',
        );
        
        # Construct the select statement
@@ -14538,6 +14548,12 @@ sub get_image_active_directory_domain_in
 FROM
 imageaddomain,
 addomain
+LEFT JOIN (cryptsecret, cryptkey) ON (
+       addomain.secretid = cryptsecret.secretid AND
+       cryptsecret.cryptkeyid = cryptkey.id AND
+       cryptkey.hosttype = 'managementnode' AND
+       cryptkey.hostid = $management_node_id
+)
 WHERE
 imageaddomain.imageid = $image_id
 AND imageaddomain.addomainid = addomain.id
@@ -14573,6 +14589,9 @@ EOF
                if ($table eq $tables[0]) {
                        $info->{$column} = $value;
                }
+               elsif ($table eq 'cryptkey') {
+                       $info->{cryptsecret}{$table}{$column} = $value;
+               }
                else {
                        $info->{$table}{$column} = $value;
                }
@@ -14894,6 +14913,270 @@ EOF
        return 1;
 }
 
+#//////////////////////////////////////////////////////////////////////////////
+
+=head2 get_management_node_cryptkey_pubkey
+
+ Parameters  : $management_node_id, $show_warnings (optional)
+ Returns     : string
+ Description : Retrieves the cryptkey.pubkey value for the management node.
+
+=cut
+
+sub get_management_node_cryptkey_pubkey {
+       my ($management_node_id, $show_warnings) = @_;
+       if (!defined($management_node_id)) {
+               notify($ERRORS{'WARNING'}, 0, "management node ID argument was 
not supplied");
+               return;
+       }
+       
+       $show_warnings = 1 unless defined($show_warnings);
+       
+       my $select_statement = <<EOF;
+SELECT
+cryptkey.pubkey
+FROM
+cryptkey
+WHERE
+cryptkey.hosttype = 'managementnode'
+AND cryptkey.hostid = $management_node_id
+EOF
+       
+       my @rows = database_select($select_statement);
+       if (!@rows) {
+               if ($show_warnings) {
+                       notify($ERRORS{'WARNING'}, 0, "cryptkey table does NOT 
contain a row for management node ID $management_node_id");
+               }
+               else {
+                       notify($ERRORS{'DEBUG'}, 0, "cryptkey table does NOT 
contain a row for management node ID $management_node_id");
+               }
+               return;
+       }
+       
+       my $row = $rows[0];
+       my $public_key_string = $row->{pubkey};
+       notify($ERRORS{'DEBUG'}, 0, "retrieved public key from cryptkey table 
for management node ID $management_node_id");
+       return $public_key_string;
+}
+
+#//////////////////////////////////////////////////////////////////////////////
+
+=head2 set_management_node_cryptkey_pubkey
+
+ Parameters  : $host_id, $public_key_string
+ Returns     : $cryptkey_id
+ Description : Set or updates the cryptkey.pubkey value for the management 
node.
+
+=cut
+
+sub set_management_node_cryptkey_pubkey {
+       my ($management_node_id, $public_key_string) = @_;
+       if (!defined($management_node_id)) {
+               notify($ERRORS{'WARNING'}, 0, "management node ID argument was 
not supplied");
+               return;
+       }
+       elsif (!defined($public_key_string)) {
+               notify($ERRORS{'WARNING'}, 0, "public key string argument was 
not supplied");
+               return;
+       }
+       
+       my $insert_statement = <<EOF;
+INSERT INTO cryptkey
+(hostid, hosttype, pubkey)
+VALUES
+(
+       $management_node_id,
+       'managementnode',
+       '$public_key_string'
+)
+ON DUPLICATE KEY UPDATE
+pubkey='$public_key_string'
+EOF
+       
+       my $cryptkey_id = database_execute($insert_statement);
+       if ($cryptkey_id) {
+               notify($ERRORS{'DEBUG'}, 0, "set public key in cryptkey table 
for management node ID $management_node_id, cryptkey ID");
+               return 1;
+       }
+       else {
+               notify($ERRORS{'WARNING'}, 0, "failed to execute insert 
statement to set public key in cryptkey table for management node ID 
$management_node_id");
+               return;
+       }
+}
+
+#//////////////////////////////////////////////////////////////////////////////
+
+=head2 get_management_node_cryptsecret_info
+
+ Parameters  : $management_node_id
+ Returns     : hash reference
+ Description : 
+
+=cut
+
+sub get_management_node_cryptsecret_info {
+       my ($management_node_id) = @_;
+       if (!defined($management_node_id)) {
+               notify($ERRORS{'WARNING'}, 0, "management node ID argument was 
not supplied");
+               return;
+       }
+
+       my $select_statement = <<EOF;
+SELECT
+cryptsecret.*
+FROM
+cryptsecret,
+cryptkey
+WHERE
+cryptkey.hostid = $management_node_id
+AND cryptkey.hosttype = 'managementnode'
+AND cryptsecret.cryptkeyid = cryptkey.id
+EOF
+       
+       my @rows = database_select($select_statement);
+       if (scalar @rows == 0) {
+               notify($ERRORS{'WARNING'}, 0, "failed to retrieve cryptsecret 
info from database for management node $management_node_id");
+               return;
+       }
+
+       print format_data(\@rows) . "\n\n";
+}
+
+#//////////////////////////////////////////////////////////////////////////////
+
+=head2 get_management_node_cryptsecret_value
+
+ Parameters  : $management_node_id, $secret_id
+ Returns     : boolean
+ Description : Retrieves the cryptsecret.cryptsecret value matching the
+               cryptsecret.secretid value from the database for the management
+               node.
+
+=cut
+
+sub get_management_node_cryptsecret_value {
+       my ($management_node_id, $secret_id) = @_;
+       if (!defined($management_node_id)) {
+               notify($ERRORS{'WARNING'}, 0, "management node ID argument was 
not supplied");
+               return;
+       }
+       if (!defined($secret_id)) {
+               notify($ERRORS{'WARNING'}, 0, "secret ID argument was not 
supplied");
+               return;
+       }
+
+       my $select_statement = <<EOF;
+SELECT
+cryptsecret.cryptsecret
+FROM
+cryptsecret,
+cryptkey
+WHERE
+cryptkey.hostid = $management_node_id
+AND cryptkey.hosttype = 'managementnode'
+AND cryptsecret.secretid = $secret_id
+AND cryptsecret.cryptkeyid = cryptkey.id
+EOF
+       
+       my @rows = database_select($select_statement);
+       if (scalar @rows == 0) {
+               notify($ERRORS{'WARNING'}, 0, "failed to retrieve cryptsecret 
from database for management node $management_node_id, secret ID: $secret_id");
+               return;
+       }
+
+       my $cryptsecret = $rows[0]->{cryptsecret};      
+       notify($ERRORS{'DEBUG'}, 0, "retrieved cryptsecret, management node ID: 
$management_node_id, secret ID: $secret_id");
+       return $cryptsecret;
+}
+
+#//////////////////////////////////////////////////////////////////////////////
+
+=head2 delete_management_node_cryptsecret
+
+ Parameters  : $management_node_id, $secret_id (optional)
+ Returns     : boolean
+ Description : 
+
+=cut
+
+sub delete_management_node_cryptsecret {
+       my ($management_node_id, $secret_id) = @_;
+       if (!defined($management_node_id)) {
+               notify($ERRORS{'WARNING'}, 0, "management node ID argument was 
not supplied");
+               return;
+       }
+
+       my $delete_statement = <<EOF;
+DELETE
+cryptsecret.*
+FROM
+cryptsecret,
+cryptkey
+WHERE
+cryptkey.hostid = $management_node_id
+AND cryptkey.hosttype = 'managementnode'
+AND cryptsecret.cryptkeyid = cryptkey.id
+EOF
+       if ($secret_id) {
+               $delete_statement .= "AND cryptsecret.secretid = $secret_id";
+       }
+       
+       if (database_execute($delete_statement)) {
+               notify($ERRORS{'OK'}, 0, "deleted entries from cryptsecret 
table for management node ID $management_node_id" . ($secret_id ? ", secret ID: 
$secret_id" : ''));
+               return 1;
+       }
+       else {
+               notify($ERRORS{'WARNING'}, 0, "failed to delete entries from 
cryptsecret table for management node ID $management_node_id" . ($secret_id ? 
", secret ID: $secret_id" : ''));
+               return 1;
+       }
+}
+
+#//////////////////////////////////////////////////////////////////////////////
+
+=head2 update_reservation_cryptsecret
+
+ Parameters  : none
+ Returns     : boolean
+ Description : Calls the XML-RPC XMLRPCupdateSecrets function to update the
+               cryptsecret table for the reservation.
+
+=cut
+
+sub update_reservation_cryptsecret {
+       my $reservation_id = shift;
+       if (!defined($reservation_id)) {
+               notify($ERRORS{'WARNING'}, 0, "reservation ID argument was not 
supplied");
+               return;
+       }
+       
+       my $xmlrpc_function = 'XMLRPCupdateSecrets';
+       my @xmlrpc_arguments = (
+               $xmlrpc_function,
+               $reservation_id
+       );
+       
+       my $response = xmlrpc_call(@xmlrpc_arguments);
+       if (!defined($response)) {
+               notify($ERRORS{'WARNING'}, 0, "failed to update cryptsecret 
table, $xmlrpc_function returned undefined");
+               return;
+       }
+       elsif ($response->value->{status} =~ /success/) {
+               notify($ERRORS{'OK'}, 0, "called XMLRPCupdateSecrets, 
cryptsecret table successfully updated");
+       }
+       elsif ($response->value->{status} =~ /noupdate/) {
+               notify($ERRORS{'OK'}, 0, "called XMLRPCupdateSecrets, 
cryptsecret table does not need to be updated");
+       }
+       else {
+               notify($ERRORS{'WARNING'}, 0, "failed to update cryptsecret 
table, $xmlrpc_function returned:\n" .
+                       "status        : $response->value->{status}\n" .
+                       "error code    : $response->value->{errorcode}\n" .
+                       "error message : $response->value->{errormsg}"
+               );
+               return;
+       }
+       return 1;
+}
+
 #//////////////////////////////////////////////////////////////////////////////
 
 


Reply via email to