Thursday, November 8, 2012

Automate Adding ESXi Hosts to vCenter With SSL Thumbprint

Recently, I was asked how to automate adding ESXi hosts to vCenter with a custom Perl script.  For security purposes, vSphere vCenter requires SSL verification of the ESXi SSL certificate thumbprint.  The automation challenge is generating the SHA1 digest for the ESXi host certificate, so it can then be passed as a parameter to AddHost_Task() method.

There are two solutions for getting the SSL thumbprint before adding an ESXi host to vCenter's inventory.

The first option is to call AddHost_Task() without the SSL thumbprint.  The Task entity object will contain the SSL thumbprint as part of the task fault details.

if ($task_view->info->state->val =~ m/error/gi) {
 $thumbprint = $task_view->info->error->fault->thumbprint;

The second option is to retrieve the ESXi host's SSL certificate from its web based configuration file listing.  With the SSL certificate, the SHA1 digest can be generated in code.

Traditionally, the need to do full certificate chain validation before adding an ESXi host to a production vCenter Inventory would not be important as the hypervisor is typically built by a trusted source.  However, the growing adoption of IaaS architecture models and automated, unattended deployments of Virtual Infrastructure increases the possibility of injecting an unsupported or even compromised ESXi hypervisor build into the production environment.  The use of a Certificate Authority can become a real solution to verifying the validity of an ESXi host.

I generally recommend the second option.  With the certificate, it's possible to do detailed verification and custom processing before adding the ESXi host to vCenter.

So how can you retrieve the ESXi SSL certificate?  Each ESXi host provides a web share containing many of its configuration files, including the SSL certificate in PEM format.  The URI to access the configuration file web share is https://<FQDN|IP>:443/host.  You'll need valid credentials to access the web share, in this example we'll use the root password configured during the ESXi installation.  Once successfully connected, the SSL certificate is listed as ssl_cert.

ESXi Configuration Files Web Share
Note:  If you're curious, there is another web share that points to the ESXi host's datastores.  It's possible to use HTTP GET and POST operations to download and upload files to the datastore folders.  The URI for the folder web share is https://<FQDN|IP>:443/folder.

Of course, the intention here is to automate the download of the ESXi SSL certificate.  Using Perl's LWP::UserAgent, fetching the certificate is straightforward.  The one detail to note is the realm name parameter to the UserAgent credentials() method.  On ESXi systems, the realm name is VMware HTTP Server.  As we'll be using the ESXi root login when adding the host to vCenter, we'll use those same credentials here to connect to the configuration file web share.

sub get_esx_certificate {
 my %args = @_;
 my ($esx_host, $esx_user, $esx_pass, $esx_port, $ua, $res, $certificate, $realm_name);
 $esx_host = delete($args{'esx_host'}) ||
  die "Missing required parameter 'esx_host'\n";
 $esx_user = delete($args{'esx_user'}) ||
  die "Missing required parameter 'esx_user'\n";
 $esx_pass = delete($args{'esx_pass'}) ||
  die "Missing required parameter 'esx_pass'\n";
 $esx_port = delete($args{'esx_port'}) || "443";
    $certificate = undef;
    $ua = LWP::UserAgent->new();
    $realm_name = "VMware HTTP server";
    $ua->credentials( "$esx_host:$esx_port", $realm_name, $esx_user => $esx_pass );
    $res = $ua->get("https://$esx_host:$esx_port/host/ssl_cert");
   die $res->status_line unless $res->is_success;
    $certificate = $res->decoded_content();
    return $certificate;

The certificate's content is in PEM format, so it will have to be converted to DER.  The MIME::Base64 module provides decode_base64() to perform the conversion.  Once the certificate data is available in DER format, the sha1_hex() method of Digest::SHA1 is used to generate a SHA1 digest of the certificate.  Finally, some string manipulation is required to format the SHA1 digest with colon separators to get the final version of the SSL thumbprint.

Note:  The MIME::Base64 and Digest::SHA1 modules are standard in most Perl installations and were selected since they will not require additional module installation before use.

sub generate_ssl_thumbprint {
 my %args = @_;

 my ($pem, $der, $digest, $sslthumbprint);
 $pem = delete($args{pem});
 ## Strip PEM tags to get Base64 encoded certificate data
 $pem =~ s/-{1,}(BEGIN|END) CERTIFICATE-{1,}//g;

 ## Convert PEM to DER (decode Base64)
 $der = decode_base64($pem);
 ## Generate SHA1 hex digest
 $digest = sha1_hex($der);
 ## Format thumbprint
 $sslthumbprint = "";
 for (my $i=0; $i < length($digest); $i+=2) {
  my $substring = substr($digest, $i, 2);
  $sslthumbprint .= uc($substring);
  unless ($i >= 38) {
   $sslthumbprint .= ":";
 return $sslthumbprint;

Now that we have a VMware vCenter API sslThumbprint string, create the HostConnectSpec and call AddHost_Task().

## Create HostConnectSpec
$spec = HostConnectSpec->new(
 force => 'false',
 hostName => $esx_host,
 userName => $esx_user,
 password => $esx_pass,
 sslThumbprint => $thumbprint );
print "Adding HostSystem '$esx_host' to Cluster '$cluster'...\n";       

## Call AddHost_Task()
$task = $cluster_view->AddHost_Task(spec => $spec, asConnected => "true");

A full version of the example script is available on the VMTM Developer Communities.  Here is an example run of Sample Run