vSphere Automation SDKs

This week VMware open sourced their SDKs for vSphere using REST APIs, and Python. The REST API was released with vSphere 6.0, while the Python SDK has been around for nearly four years now. I’m going to summarise the contents of this release below, and where these can help us make more of our vSphere environments.

REST API

The vSphere REST API has been growing since the release of vSphere 6 nearly two years ago, and brings access to the following areas of vSphere with its current release:

  • Session management
  • Tagging
  • Content Library
  • Virtual Machines
  • vCenter Server Appliance management

These cover mainly new features from vSphere 6.0 (formerly known as vCloud Suite SDK), and then some of the new bits put together for modernising the API access in vSphere 6.5. The Virtual Machine management particularly is useful in being able to start using REST based methods to do operations, and report on VMs in your environment, very useful for people looking to write quick integrations with things like vRealize Orchestrator, where the built in plugins do not do what you want.

The new material, available on GitHub, contains two main functions:

Postman Collection

Screen Shot 2017-03-12 at 10.28.54.png

Postman is a REST client used to explore APIs, providing a nice graphical display of the request-response type methods used for REST. This is a great way to get your head round what is happening with requests, and helps to build up an idea of what is going on with the API.

Pre-built packs of requests can be gathered together in Postman ‘Collections’; these can then be distributed (in JSON format) and loaded into another instance of Postman. This can be crucially important in documenting the functionality of APIs, especially when the documentation is lacking.

There are some instructions on how to set this up here; if you are new to REST APIs, or just want a quick way to have a play with the new vSphere REST APIs, you could do far worse than starting here.

Node.js Sample Pack

Node.js has taken over the world of server side web programming, and thanks to the simple syntax of Javascript, is easy to pick up and get started with. This pack (available here) has some samples of Node.js code to interact with the REST API. This is a good place to start with seeing how web requests and responses are dealt with in Node, and how we can programatically carry out administrative tasks.

These could be integrated into a web based portal to do the requests directly, or I can see these being used in the future as part of a serverless administration platform, using something like AWS Lambda along with a monitoring platform to automate the administration of a vSphere environment.

Python SDK

Python has been an incredibly popular language for automation for a number of years. Its very low barrier to getting started makes it ideal to pick up and learn, with a wealth of possibilities for building on solid simple foundations to make highly complex software solutions. VMware released their ‘pyvmomi’ Python SDK back in 2013, and it has received consistent updates since then. While not as popular, or as promoted as their PowerCLI PowerShell module, it has nevertheless had strong usage and support from the community.

The release on offer as part of the vSphere Automation SDKs consists of scripts to spin up a demo environment for developing with the Python SDK, as well as a number of sample scripts demonstrating the functionality of the new APIs released in vSphere 6.0 and 6.5.

The continued growth in popularity of Python, along with leading automation toolsets like Ansible using a Python base, mean that it is a great platform to push this kind of development and publicity in. As with Node.js; serverless platforms are widely supporting Python, so this could be integrated with Lambda, Fission, or other FaaS platforms in the future.

Conclusion

It’s great to see VMware really getting behind developing and pushing their automation toolkits in the open, they are to my mind a leader in the industry in terms of making their products programmable, and I hope they continue at this pace and in this vein. The work shown in this release will help make it easier for people new to automation to get involved and start reaping the benefits that it can bring, and the possibilities for combining these vSphere SDKs with serverless administration will be an interesting area to watch.

AWS REST API Authentication Using Node.js

I’ve been learning as much as I can on Amazon Web Services over the last couple of months; the looming shadow of it over traditional IT finally got too much, and I figured it was time to make the leap. Overall it’s been a great experience, and the biggest takeaway I’ve probably had is how every service, and the way in which we consume them, are application-centric.
Every service is fully API first, with the AWS Management Console basically acting as a front end for the API calls made to the vast multitude of services. I’ve done a fair amount of work with REST APIs over the last 18 months, and it’s always good to fire up Postman (if you don’t know what this is, there is a post here I did about REST APIs, and the use of Postman), and throw a few API calls at a new technology to see how it works.
Now while AWS services are all available via REST APIs; there are a tonne of tools available for both administrators and developers, which abstract away the nitty gritty, we have:
  • AWS CLI – a CLI based tool for Windows/Linux/OSX (available here)
  • AWS Tools for Windows PowerShell – the PowerShell module for consuming AWS services (available here)
  • SDKs (Software Development Kits) for the following (all available here):
    • Android
    • Browsers (basically a JavaScript SDK you can build web services around)
    • iOS
    • Java
    • .NET
    • Node.js
    • PHP
    • Python
    • Ruby
    • GoLang
    • C++
    • AWS IoT
    • AWS Mobile
Combined, these provide a wide variety of ways to use pre-built solutions to speak to AWS based resources, and there should be something that any developer or admin can use to introduce some automation or programability into their work, and I would recommend using one of these if at all possible to abstract away the heavy lifting of working with a very broad and deep API.
I wanted to get stuck in from a REST API side though, which basically means building things from the ground up. This turned out to take a fair amount of time, but I learned a heck of a lot about the authentication and authorisation process for AWS, and how this helps to prevent unauthorised access.
The full authentication process is described in the AWS Documentation available here. There are pages and pages describing the V4 authentication process (the current recommended version), and this gets pretty complicated. I’m going to try and break it down here, showing the bits of code used to create each element; this should hopefully make it a bit clearer.
One post I found really useful on this was by Lukasz Adamczak (@lukasz_adamczak), on how to do the authentication with Curl, which I used as the basis for some of what I did below. I couldn’t find anything where someone was doing this task via the REST API in JavaScript.

Pre-requisites

The following variables need to be set in the script before we start:
// our variables
var access_key = 'ACCESS_KEY_VALUE'
var secret_key = 'SECRET_KEY_VALUE'
var region = 'eu-west-1';
var url = 'my-bucket-name.s3.amazonaws.com';
var myService = 's3';
var myMethod = 'GET';
var myPath = '/';
In addition to this, we have these package dependencies:
// declare our dependencies
var crypto = require('crypto-js');
var https = require('https');
var xml = require('xml2js');
The crypto-js and https modules were built into the version of Node I was using (v6.9.5), but I had to use NPM to install the xml2js module.

Amazon Date Format

I started with getting the date format used by AWS in authentication, this is based on the ISO 8601 format, but has the punctuation, and milliseconds removed from it. I created the below function, and use it to create the two variables shown below:
// get the various date formats needed to form our request
var amzDate = getAmzDate(new Date().toISOString());
var authDate = amzDate.split("T")[0];

// this function converts the generic JS ISO8601 date format to the specific format the AWS API wants
function getAmzDate(dateStr) {
  var chars = [":","-"];
  for (var i=0;i<chars.length;i++) {
    while (dateStr.indexOf(chars[i]) != -1) {
      dateStr = dateStr.replace(chars[i],"");
    }
  }
  dateStr = dateStr.split(".")[0] + "Z";
  return dateStr;
}
We’ll go into this later, but the reason there are two variables for the date (amzDate, authDate) is that in generating the headers for our REST call we will need both formats at different times. One is in the ‘YYYYMMDDTHHmmssZ’ format, and one is in the ‘YYYYMMDD’ format.

Our payload

The example used in this script is to use a blank payload, for which we calculate the SHA256 hash. This is obviously always the same when calculated against a blank string (e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855, if you were interested :p), but I included the hashing of this in the script so the logic is there later if we wanted to do different payloads.
// we have an empty payload here because it is a GET request
var payload = '';
// get the SHA256 hash value for our payload
var hashedPayload = crypto.SHA256(payload).toString();
This hashed payload is used a bunch in the final request, including in the ‘x-amz-content-sha256’ HTTP header to validate the expected payload.

Canonical Request

This is where things got a bit confusing for me; we need to work out the payload of our message (in AWS’ special format), and work out what the SHA256 hash of this is. First we need to know the formatting for the canonical request. Ultimately this is a multi-line string, consisting of the following attributes:
HTTPRequestMethod
CanonicalURI
CanonicalQueryString
CanonicalHeaders
SignedHeaders
HexEncode(Hash(RequestPayload))
These attributes are described as:
  • HTTPRequestMethod – the HTTP method being used, could be GET, POST, etc. In our example this will be GET
  • CanonicalURI – the relative URI for the resource we are accessing. In our example we access the root namespace of our bucket, so this is set to “/”
  • CanonicalQueryString – we can build a query for our request, more information on this is available here. In our example we don’t need a query so we will leave this as a blank line
  • CanonicalHeaders – a carriage return separated list of the headers we are using in our request
  • SignedHeaders – a semi-colon separated list of the header keys we are including in our request
  • HexEncode(Hash(RequestPayload)) – the hash value as calculated earlier. As we used the ‘toString()’ method on this, it should already be in hexadecimal
We construct this request with the following code:
// create our canonical request
var canonicalReq =  myMethod + '\n' +
                    myPath + '\n' +
                    '\n' +
                    'host:' + url + '\n' +
                    'x-amz-content-sha256:' + hashedPayload + '\n' +
                    'x-amz-date:' + amzDate + '\n' +
                    '\n' +
                    'host;x-amz-content-sha256;x-amz-date' + '\n' +
                    hashedPayload;
This leaves us with the following as an example:
GET
/

host:my-bucket-name.s3.amazonaws.com
x-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-amz-date:20170213T045707Z

host;x-amz-content-sha256;x-amz-date
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Note the blank CanonicalQuery line here, as we are not using that functionality, and the blank like after Canonical Headers, these are required for the string to be accepted when we hash it.
So now we can hash this multi-line string:
// hash the canonical request
var canonicalReqHash = crypto.SHA256(canonicalReq).toString();
This becomes another long hashed value.
c75b55ba2d959baf99f2c4976c7a50c7cd79067a726c21024f4a981ae2a90b50

String to Sign

Now, similar to the Canonical Request above, we create a new multi-line string which is used to generate our authentication header. This time it is in the following format:
Algorithm
RequestDate
CredentialScope
HashedCanonicalRequest

These attributes are completed as:

  • Algorithm – for SHA256, which is what we always use with AWS, this should be set to ‘AWS4-HMAC-SHA256’
  • RequestDate – this is the date/time stamp in the ‘YYYYMMDDTHHmmssZ’ format, so we will use our stored ‘amzDate’ variable here
  • CredentialScope – this takes the format ‘///aws4_request’. We have the date stored in this format already as ‘authDate’, so we can use that here, our region name can be found in this table, and the service name here is ‘s3’, further details of other namespaces can be found here
  • HashedCanonicalRequest – this was calculated above

With this information we can form our string like this:

// form our String-to-Sign
var stringToSign =  'AWS4-HMAC-SHA256\n' +
                    amzDate + '\n' +
                    authDate+'/'+region+'/'+myService+'/aws4_request\n'+
                    canonicalReqHash;
This generates a string like this:
AWS4-HMAC-SHA256
20170213T051343Z
20170213/eu-west-1/s3/aws4_request
c75b55ba2d959baf99f2c4976c7a50c7cd79067a726c21024f4a981ae2a90b50

Signing Key

We need a signing key now; this embeds our secret key, along with some other bits in a hash which is used to sign our ‘String to sign’, giving us our final hashed value which we use in the authentication header. Luckily here AWS provide some sample JS code (amongst other languages) for creating this hash:
// this function gets the Signature Key, see AWS documentation for more details
function getSignatureKey(Crypto, key, dateStamp, regionName, serviceName) {
    var kDate = Crypto.HmacSHA256(dateStamp, "AWS4" + key);
    var kRegion = Crypto.HmacSHA256(regionName, kDate);
    var kService = Crypto.HmacSHA256(serviceName, kRegion);
    var kSigning = Crypto.HmacSHA256("aws4_request", kService);
    return kSigning;
}

This can be found here.So into this function we pass our secret access key, the authDate variable we calculated earlier, our region, and the service namespace.

// get our Signing Key
var signingKey = getSignatureKey(crypto, secret_key, authDate, region, myService);

This will again return a long hash value:

9afc364e2eb6ba46f000721975d32bc2042058f80b5a8fd69efe422e7be5090d

Authentication Key

Nearly there now! So we need to take our String to Sign, and our Signing Key, and hash the string to sign with the signing key, to generate another hash which will be used in our request header. To do this we again use the CryptoJS library, with the order of the inputs being our string to hash (stringToSign), and then the key to hash it with (signingKey).
This returns another hash:
31c6a42a9aec00390317f9c714f38efeba2498fa1996cecb9b4c714b39cbc05a90332f38ef

Creating our headers

Right, no more hashing needed now, we have everything we need. So next we construct our Authentication header value:
// Form our authorization header
var authString  = 'AWS4-HMAC-SHA256 ' +
                  'Credential='+
                  access_key+'/'+
                  authDate+'/'+
                  region+'/'+
                  myService+'/aws4_request,'+
                  'SignedHeaders=host;x-amz-content-sha256;x-amz-date,'+
                  'Signature='+authKey;
This is a single line, multi-part string consisting of the following parts:
  • Algorithm – for SHA256, which is what we always use with AWS, this should be set to ‘AWS4-HMAC-SHA256’
  • CredentialScope – as used in our String To Sign above
  • SignedHeaders – a semi-colon separated list of our signed headers
  • Signature – the authentication key we hand crafted above
When we place all these together, we end up with a string like this:
WS4-HMAC-SHA256 Credential=A123EXAMPLEACCESSKEY/20170213/eu-west-1/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature=31c6a42a9aec00390317f9c714f38efeba2498fa1996cecb9b4c714b39cbc05a90332f38ef
Now we have everything we need to create our headers for our HTTP request:
// throw our headers together
headers = {
  'Authorization' : authString,
  'Host' : url,
  'x-amz-date' : amzDate,
  'x-amz-content-sha256' : hashedPayload
};
Here we use a hash array for simplicity, with our various headers added to the array, to end up with an array like this:
Key
Value
Authorization
WS4-HMAC-SHA256 Credential=A123EXAMPLEACCESSKEY/20170213/eu-west-1/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature=31c6a42a9aec00390317f9c714f38efeba2498fa1996cecb9b4c714b39cbc05a90332f38ef
Host
x-amz-date
20170213T051343Z
x-amz-content-sha256
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
These are now ready to use in our request.

Our request

Now we can send our request. I split this into a function to do the REST call:
// the REST API call using the Node.js 'https' module
function performRequest(endpoint, headers, data, success) {

  var dataString = data;

  var options = {
    host: endpoint,
    port: 443,
    path: '/',
    method: 'GET',
    headers: headers
  };

  var req = https.request(options, function(res) {
    res.setEncoding('utf-8');

    var responseString = '';

    res.on('data', function(data) {
      responseString += data;
    });

    res.on('end', function() {
      //console.log(responseString);
      success(responseString);
    });
  });

  req.write(dataString);
  req.end();
}
And the call to this function, which also processes the results, in the body:
// call our function
performRequest(url, headers, payload, function(response) {
  // parse the response from our function and write the results to the console
  xml.parseString(response, function (err, result) {
    console.log('\n=== \n'+'Bucket is named: ' + result['ListBucketResult']['Name']);
    console.log('=== \n'+'Contents: ');
    for (i=0;i<result['ListBucketResult']['Contents'].length;i++) {
      console.log(
        '=== \n'+
        'Name: '          + result['ListBucketResult']['Contents'][i]['Key'][0]           + '\n' +
        'Last modified: ' + result['ListBucketResult']['Contents'][i]['LastModified'][0]  + '\n' +
        'Size (bytes): '  + result['ListBucketResult']['Contents'][i]['Size'][0]          + '\n' +
        'Storage Class: ' + result['ListBucketResult']['Contents'][i]['StorageClass'][0]
      );
    };
    console.log('=== \n');
  });
});
This essentially passes our headers with the payload to the URL we specified in our variables, and processes the resulting XML into some useful output.
This is what this returns as output:
Tims-MacBook-Pro:GetS3BucketContent tim$ node get_bucket_content.js

===
Bucket is named: virtualbrakeman
===
Contents:
===
Name: file_a_foo.txt
Last modified: 2017-02-05T11:19:36.000Z
Size (bytes): 10
Storage Class: STANDARD
===
Name: file_b_foo.txt
Last modified: 2017-02-05T11:19:36.000Z
Size (bytes): 10
Storage Class: STANDARD
===
Name: file_c_foo.txt
Last modified: 2017-02-05T11:19:36.000Z
Size (bytes): 10
Storage Class: STANDARD
===
Name: file_d_foo.txt
Last modified: 2017-02-05T11:19:36.000Z
Size (bytes): 10
Storage Class: STANDARD
===
Name: foobar
Last modified: 2017-02-04T22:01:38.000Z
Size (bytes): 0
Storage Class: STANDARD
===

Tims-MacBook-Pro:GetS3BucketContent tim$

Conclusion

This is a fairly trivial example of data to return, but the real point behind this was building the authentication code, which proved to be very laborious. Given the wide (and growing) variety of SDKs available, it seems overly complex to try and construct these requests in this way every time. I have played with the Python and JavaScript SDKs and both take this roughly 150 line script, and achieve the same result in around 20 lines.
Regardless, this was a good learning exercise for me, and it may come in useful for people trying to interact with the AWS API in ways which are not covered by the SDKs, or via other languages where an SDK is not available.
The final script is shown below, and is also available on my GitHub library here: https://github.com/railroadmanuk/awsrestauthentication
// declare our dependencies
var crypto = require('crypto-js');
var https = require('https');
var xml = require('xml2js');

main();

// split the code into a main function
function main() {
  // this serviceList is unused right now, but may be used in future
  const serviceList = [
    'dynamodb',
    'ec2',
    'sqs',
    'sns',
    's3'
  ];

  // our variables
  var access_key = 'ACCESS_KEY_VALUE';
  var secret_key = 'SECRET_KEY_VALUE';
  var region = 'eu-west-1';
  var url = 'my-bucket-name.s3.amazonaws.com';
  var myService = 's3';
  var myMethod = 'GET';
  var myPath = '/';

  // get the various date formats needed to form our request
  var amzDate = getAmzDate(new Date().toISOString());
  var authDate = amzDate.split("T")[0];

  // we have an empty payload here because it is a GET request
  var payload = '';
  // get the SHA256 hash value for our payload
  var hashedPayload = crypto.SHA256(payload).toString();

  // create our canonical request
  var canonicalReq =  myMethod + '\n' +
                      myPath + '\n' +
                      '\n' +
                      'host:' + url + '\n' +
                      'x-amz-content-sha256:' + hashedPayload + '\n' +
                      'x-amz-date:' + amzDate + '\n' +
                      '\n' +
                      'host;x-amz-content-sha256;x-amz-date' + '\n' +
                      hashedPayload;

  // hash the canonical request
  var canonicalReqHash = crypto.SHA256(canonicalReq).toString();

  // form our String-to-Sign
  var stringToSign =  'AWS4-HMAC-SHA256\n' +
                      amzDate + '\n' +
                      authDate+'/'+region+'/'+myService+'/aws4_request\n'+
                      canonicalReqHash;

  // get our Signing Key
  var signingKey = getSignatureKey(crypto, secret_key, authDate, region, myService);

  // Sign our String-to-Sign with our Signing Key
  var authKey = crypto.HmacSHA256(stringToSign, signingKey);

  // Form our authorization header
  var authString  = 'AWS4-HMAC-SHA256 ' +
                    'Credential='+
                    access_key+'/'+
                    authDate+'/'+
                    region+'/'+
                    myService+'/aws4_request,'+
                    'SignedHeaders=host;x-amz-content-sha256;x-amz-date,'+
                    'Signature='+authKey;

  // throw our headers together
  headers = {
    'Authorization' : authString,
    'Host' : url,
    'x-amz-date' : amzDate,
    'x-amz-content-sha256' : hashedPayload
  };

  // call our function
  performRequest(url, headers, payload, function(response) {
    // parse the response from our function and write the results to the console
    xml.parseString(response, function (err, result) {
      console.log('\n=== \n'+'Bucket is named: ' + result['ListBucketResult']['Name']);
      console.log('=== \n'+'Contents: ');
      for (i=0;i<result['ListBucketResult']['Contents'].length;i++) {
        console.log(
          '=== \n'+
          'Name: '          + result['ListBucketResult']['Contents'][i]['Key'][0]           + '\n' +
          'Last modified: ' + result['ListBucketResult']['Contents'][i]['LastModified'][0]  + '\n' +
          'Size (bytes): '  + result['ListBucketResult']['Contents'][i]['Size'][0]          + '\n' +
          'Storage Class: ' + result['ListBucketResult']['Contents'][i]['StorageClass'][0]
        );
      };
      console.log('=== \n');
    });
  });
};

// this function gets the Signature Key, see AWS documentation for more details, this was taken from the AWS samples site
function getSignatureKey(Crypto, key, dateStamp, regionName, serviceName) {
    var kDate = Crypto.HmacSHA256(dateStamp, "AWS4" + key);
    var kRegion = Crypto.HmacSHA256(regionName, kDate);
    var kService = Crypto.HmacSHA256(serviceName, kRegion);
    var kSigning = Crypto.HmacSHA256("aws4_request", kService);
    return kSigning;
}

// this function converts the generic JS ISO8601 date format to the specific format the AWS API wants
function getAmzDate(dateStr) {
  var chars = [":","-"];
  for (var i=0;i<chars.length;i++) {
    while (dateStr.indexOf(chars[i]) != -1) {
      dateStr = dateStr.replace(chars[i],"");
    }
  }
  dateStr = dateStr.split(".")[0] + "Z";
  return dateStr;
}

// the REST API call using the Node.js 'https' module
function performRequest(endpoint, headers, data, success) {

  var dataString = data;

  var options = {
    host: endpoint,
    port: 443,
    path: '/',
    method: 'GET',
    headers: headers
  };

  var req = https.request(options, function(res) {
    res.setEncoding('utf-8');

    var responseString = '';

    res.on('data', function(data) {
      responseString += data;
    });

    res.on('end', function() {
      //console.log(responseString);
      success(responseString);
    });
  });

  req.write(dataString);
  req.end();
}

vRealize Orchestrator and Site Recovery Manager – The Missing Parts (or how to hack SOAP APIs to get what you want)

vRealize Orchestrator (vRO) forms the backbone of vRealize Automation (vRA), and provides the XaaS (Anything-as-a-Service) functionality for this product. vRO has plugins for a number of technologies; both those made by VMware, and those which are not. Having been using vRO to automate various products for the last 6 months or so, I have found that these plugins have varying degrees of quality, and some cover more functionality of the underlying product than others.

Over the last couple of weeks, I have been looking at the Site Recovery Manager (SRM) plugin (specifically version 6.1.1, in association with vRO 7.0.1, and SRM 6.1), and while this provides some of the basic functionality of SRM, it is missing some key features which I needed to expose in order to provide full-featured vRA catalog services. Specifically, the plugin documentation lists the following as being missing:

  • You cannot create, edit, or delete recovery plans.
  • You cannot add or remove test network mapping to a recovery plan.
  • You cannot rescan storage to discover newly added replicated devices.
  • You cannot delete folder, network, and resource pool mappings
  • You cannot delete protection groups
  • The unassociateVms and unrotectVms methods are not available in the plug-in. You can use them by using the Site Recovery Manager public API.

Some of these are annoying, but the last ones, around removing VMs from Protection Groups are pretty crucial for the catalog services I was looking to put together. I had to find another way to do this task, outside of the hamstrung plugin.

I dug out the SRM API Developers Guide (available here), and had a read through it, but whilst describing the API in terms of Java and C# access, it wasn’t particularly of use in helping me to use vRO’s JavaScript based programming to do what I needed to do. So I needed another way to do this, which utilised the native SOAP API presented by SRM.

Another issue I saw when using the vRO SRM plugin was that when trying to add a second SRM server (the Recovery site), the plugin fell apart. It seems that the general idea is you only automate your Protected site with this plugin, and not both sites through a single vRO instance.

I tried adding a SOAP host to vRO using the ‘Add a SOAP host’ workflow, but even after adding the WSDL available on the SRM API interface, this was still not particularly friendly, so this didn’t help too much.

Using PowerCLI, we can do some useful things using the SRM API, see this post, and this GitHub repo, for some help with doing this. Our general approach to using vRO is to avoid using a PowerShell host, as this adds a bunch of complexity around adding a host, and generally we would rather do things using REST hosts with pure JavaScript code. So we need a way to figure out how to use this undocumented SOAP API to do stuff.

Now before we go on, I appreciate that the API is subject to change, and that by using the following method to do what we need to do, the methods of automation may change in a future version of SRM. As you will see, this is a fairly simple method of getting what you need, and it should be easy enough to refactor the payloads we are using if and when the API changes. In addition to this, this method should work for any kind of SOAP or REST based API which you can access through .NET type objects in PowerShell.

So the first thing we need to do is to install Fiddler. This is the easiest tool I found to get what I wanted, and there are probably other products about, but I found and liked this one. Fiddler is a web debugging tool, which I would imagine a lot of web developers are familiar with, it can be obtained here. What I like about it is the simplicity it gives in setting up a man-in-the-middle (MitM) attack to pull the detail of what is going on. This is particularly useful when using it with PowerShell, because your client machine is the endpoint, so the proxy injection is straight forward without too much messing about.

NOTE: Because this is doing MitM attacks on the traffic, it is

I’m not going to go into installing Fiddler here, it’s a standard Windows wizard, once installed, launch the program and you should see something like this:

1

If you click in the bottom right, next to ‘All Processes’, you will see it change to ‘Capturing’:

2

We are now ready to start capturing some API calls. So open PowerShell. Now to limit the amount of junk traffic we capture, we can set to only keep a certain number of sessions (in this case I set it to 1000), and target the process to capture from (by dragging the ‘Any Process’ button to our PowerShell window).

3

9

Run the following to connect to vCenter:

Import-Module VMware.VimAutomation.Core
Connect-VIServer -Server $vcenter -Credential (Get-Credential)

4

You should see some captures appearing in the Fiddler window, we can ignore these for now as it’s just connections to the vCenter server:

You can inspect this traffic in any case, by selecting a session, and selecting the ‘Raw’ tab in the right hand pane:

5

Here we can see the URI (https://<redacted>/sdk), the SOAP method (POST), the Headers (User-Agent, Content-Type, SOAPAction, Host, Cookie etc), and the body (<?xml version….), this shows us exactly what the PowerShell client is doing to talk to the API.

Now we can connect to our local and remote SRM sites using the following command:

$srm = connect-srmserver -RemoteCredential (Get-Credential -Message 'Remote Site Credential') -Credential (Get-Credential -Message 'Local Site Credential')

If you examine the sessions in your Fiddler window now, you should see a session which looks like this:

6

This shows the URI as our SRM server, on HTTPS port 9086, with suffix ‘/vcdr/extapi/sdk’, this is the URI we use for all the SRM SOAP calls, it shows the body we use (which contains usernames and passwords for both sites), and the response with a ‘Set-Cookie’ header with a session ticket in it. This session ticket will be added as a header to each of our following calls to the SOAP API.

Let’s try and do something with the API through PowerShell now, and see what the response looks like, run the following in your PowerShell window:

$srmApi = $srm.ExtensionData
$protectionGroups= $srmApi.Protection.ListProtectionGroups()

This session will show us the following:

7

Here we can see the URI is the same as earlier, that there is a header with the name ‘Cookie’ and value of ‘vmware_soap_session=”d8ba0e7de00ae1831b253341685201b2f3b29a66″’, which ties in with the cookie returned by the last call, which has returned us some ManagedObjectReference (MoRef) names of ‘srm-vm-protection-group-1172’ and ‘srm-vm-protection-group-1823’, which represent our Protection Groups. This is great, but how do we tie these into the Protection Group names we set in SRM? Well if we run the following commands in our PowerShell window, and look at the output:

Write-Output $($pg.MoRef.Value+" is equal to "+$pg.GetInfo().Name)

The responses in Fiddler look like this:

8

This shows us a query being sent, with the Protection Group MoRef, and the returned Protection Group name.

We can repeat this process for any of the methods available through the SRM API exposed in PowerCLI, and build up a list of the bodies we have for querying, and retrieving data, and use this to build up a library of actions. As an example we have the following methods already:

Query for Protection Groups:

<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ListProtectionGroups xmlns="urn:srm0"><_this type="SrmProtection">SrmProtection</_this></ListProtectionGroups></soap:Body></soap:Envelope>

Get the name of a Protection Group from it’s MoRef:

<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><GetInfo xmlns="urn:srm0"><_this type="SrmProtectionGroup">MOREFNAME</_this></GetInfo></soap:Body></soap:Envelope>

So how do we take these, and turn them into actions in vRO? Well we first need to add a REST host to vRO using the ‘Add a REST host’ built in workflow, pointing to ‘https://<SRM_Server_IP>:9086’, and then write actions to do calls against this, there is more detail on doing this around on the web, this site has a good example. For the authentication method we can do:

// let's set up our variables first, these could be pushed in through parameters on the action, which would make more sense, but keep it simple for now

var localUsername = "administrator@vsphere.local"

var localPassword = "VMware1!"

var remoteUsername = "administrator@vsphere.local"

var remotePassword = "VMware1!"

 

// We need our XML body to send

var content = '<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><SrmLoginSites xmlns="urn:srm0"><_this type="SrmServiceInstance">SrmServiceInstance</_this><username>'+localUsername+'</username><password>'+localPassword+'</password><remoteUsername>'+remoteUsername+'</remoteUsername><remotePassword>'+remotePassword+'</remotePassword></SrmLoginSites></soap:Body></soap:Envelope>';

 

// create the session request

var SessionRequest = RestHost.createRequest("POST", "/vcdr/extapi/sdk", content);

// set the headers we saw on the request through Fiddler

SessionRequest.setHeader("SOAPAction","urn:srm0/4.0");

SessionRequest.setHeader("Content-Type","text/xml; charset=utf-8");

var SessionResponse = SessionRequest.execute();

 

// show the content

System.log("Session Response: " + SessionResponse.contentAsString);

 

// take the response and turn it into a string

var XmlContent = SessionResponse.contentAsString;

 

// get the headers

var responseHeaders = SessionResponse.getAllHeaders();

 

// and just the one we want

var token = responseHeaders.get("Set-Cookie");

 

// log the token we got

System.log("Token: " + token);

 

// return our token

return token

This will return us the token we can use for doing calls against the API. Now how do we use that to return a list of Protection Groups:

// We need our XML body to send, this just queries for the Protection Group MoRefs

var content = '<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ListProtectionGroups xmlns="urn:srm0"><_this type="SrmProtection">SrmProtection</_this></ListProtectionGroups></soap:Body></soap:Envelope>';

 

// create the session request

var SessionRequest = RestHost.createRequest("POST", "/vcdr/extapi/sdk", content);

// set the headers we saw on the request through Fiddler

SessionRequest.setHeader("SOAPAction","urn:srm0/4.0");

SessionRequest.setHeader("Content-Type","text/xml; charset=utf-8");

SessionRequest.setHeader("Cookie",token);

var SessionResponse = SessionRequest.execute();

 

// show the content

System.log("Session Response: " + SessionResponse.contentAsString);

 

// take the response and turn it into a string

var XmlContent = SessionResponse.contentAsString;

 

// lets get the Protection Group MoRefs from the response

var PGMoRefs = XmlContent.getElementsByTagName("returnval");

 

// declare an array of Protection Groups to return

var returnedPGs = [];

 

// iterate through each Protection Group MoRef

for each (var index=0; index<PGMoRefs.getLength(); index++) {

// extract the actual MoRef value

var thisMoRef = PGMoRefs.item(index).textContent;

// and insert it into the body of the new call

var content = '<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><GetInfo xmlns="urn:srm0"><_this type="SrmProtectionGroup">'+thisMoRef+'</_this></GetInfo></soap:Body></soap:Envelope>';

// do another call to the API to get the Protection Group name

SessionRequest = RestHost.createRequest("POST", "/vcdr/extapi/sdk", content);

SessionRequest.setHeader("SOAPAction","urn:srm0/4.0");

SessionRequest.setHeader("Content-Type","text/xml; charset=utf-8");

SessionRequest.setHeader("Cookie",token);

SessionResponse = SessionRequest.execute();

XmlContent = XMLManager.fromString(SessionResponse.contentAsString);

returnedPGs += myxmlobj.getElementsByTagName("name").item(0).textContent;

};

 

// return our token

return returnedPGs;

Through building actions like this, we can build up a library to call the API directly. This should be a good starting point for building your own libraries for vRO to interact with SRM via the API, rather than the plugin. As stated earlier, using Fiddler, or something like it, you should be able to use this to capture anything being done through PowerShell, and I have even had some success with capturing browser clicks through this method, depending on how the web interface is configured. This method certainly made creating some integration with SRM through vRO less painful than trying to use the plugin.

PowerShell – Could not create SSL/TLS secure channel

I have spent a considerable amount of time in my life battling with the above error message when running PowerShell scripts. Long and short of it is that this can be caused by a few things, but most of the times I have experienced it, the reason is that the endpoint you are trying to connect to is using self-signed certificates, which causes the Invoke-WebRequest, and Invoke-RestMethod commands to throw an error stating:

The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.

If you hit this, you will know as your web request via standard REST methods will simply refuse to give you anything back.

I had a bunch of scripts written to do automation of the configuration of vRealize Orchestrator, and vRealize Automation 7.0, and these had been heavily tested, and confirmed as working. The way of avoiding the above error is to use the following PowerShell function:

function Ignore-SelfSignedCerts
{
try
{
Write-Host "Adding TrustAllCertsPolicy type." -ForegroundColor White
Add-Type -TypeDefinition @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy
{
public bool CheckValidationResult(
ServicePoint srvPoint, X509Certificate certificate,
WebRequest request, int certificateProblem)
{
return true;
}
}
"@
Write-Host "TrustAllCertsPolicy type added." -ForegroundColor White
}
catch
{
Write-Host $_ -ForegroundColor "Yellow"
}
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
}
Ignore-SelfSignedCerts;

So not a great start to my Sunday when I found that my scripts no longer worked after a fresh install of the recently released vRealize Orchestrator and vRealize Automation 7.0.1.

After much messing about, I worked out the cause of this, which is that SSLv3 and TLSv1.0 were both disabled in the new releases, as a result we need to either:

a) Enable SSLv3 or TLSv1.0 – probably not the best idea, these have been disabled due to the growing number of security risks in these protocols, and will (presumably) continue to be disabled for every new version of the products going forward

b) Change the way we issue requests, to use TLSv1.2 – this is the way to do it in my opinion, and the code to do this is a simple one-liner:

[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12;

So if you  hit this problem (and if you are a PowerShell scripter, and interacting with REST APIs with your scripts, then you probably will!), then this is how to fix the issue.

Getting started with vRealize APIs

I have been doing automation with VMware products for a while, this has been mostly using PowerCLI, which I have blogged about in the past. In the last few weeks I have started to work with configuring and working with vRealize Orchestrator and vRealize Automation, and the hands off automation in these products is carried out using their respective REST APIs.

REST is something I’ve pretty much had zero experience of, so I have had to pick this up from nothing and figure out how to work in this fashion. My plan is to do a series of blog posts around how to get started with working with these interfaces, configuring and working with these products, and with tips on where to start. I’ve had some great support from my fellow automation people at work, but think I have a relatively good grip on how to do things now.

REST stands for ‘Representational State Transfer’ and uses the HTTP protocol as its basic interface, with this being a standard way to do things now, and with ports 80 and 443 (and the security around using them) generally being well understood, this basically means you should not have any issues with applications requiring complex numbers of ports being open to your application servers. REST is becoming somewhat ubiquitous now, with seemingly every new of hardware and software being released coming with its own REST interface.

This is great for people wanting to automate because we do not need any special software to do REST calls, and we can access the API from pretty much any OS and from anywhere with HTTP/HTTPS access to the endpoint. A REST call basically comprises of the following components:

  • URI (Uniform Resource Indicator) – basically a URL which exposes a function of the API, if you send a correctly crafted message to this address then you will get a standard HTTP response which should indicate whether your call was successful.
  • Body – this is also known as the payload. This contains instructions on what we want to do, and will usually (in my experience) be in either JSON (JavaScript Object Notation), or XML (eXtensible Markup Language). These are both markup languages which allow you to define the parameters for what you want to do. If you are sending file type data then the body may also be in multipart MIME format.
  • Headers – these carry information about the message you are sending such as data type, authentication information, desired response type, and are carried as a hash table of names and values.
  • Method – REST uses standard HTTP methods and which method you use will depend on the API and function you are accessing. Primarily this will probably be one of the following:
    • GET – this makes no changes, and is used for retrieving information. When you access a web page, the HTTP request uses the GET method, and by using this method with an API, you can generally expect to be returned an XML/JSON response with information about how a component is configured.
    • POST – this is used for sending information to an API to be processed. Again, you should expect a return code for this, and even if this is an error, there should (depending on the call, and the API) be something meaningful telling you what went wrong
    • PUT – often interchangeable with POST, this is generally used for replacing configuration in its entirety. Again you can reasonably respect some feedback when using this method.
    • DELETE – removes configuration from a specific element or component.

The exact way in which the call is sent depends on the exact API being used, and this is where the API being well documented is of crucial importance, because if documentation is lacking, or just wrong (as is the case much more than it should be), then getting the call to be successful can be challenging.

To get started you will need a tool which can craft calls for you, and show the response received. When starting out, it is easiest for this to be a graphical tool which gives instant and meaningful feedback on the response, although this is not great for doing things in an automated and programmable fashion, this does make things simpler, and makes the learning experience easier.

If you use Chrome or Firefox then you should be able to easily find some REST clients, and it may well be worth trying a few different ones until you find one which works best for you. Postman was recommended to me, and this has a nice graphical UI which will do code highlighting on the response, and will help you to build headers and the like.

Ultimately, if you are looking to automate, then you will be using a built in way of sending REST calls from your chosen automation language. My experience of doing this is either from BASH scripts (I know, I know) where I use curl, or through PowerShell, where you have the Invoke-RestMethod or Invoke-WebRequest cmdlets.

When using these kind of tools (or indeed, doing any kind of programmatic REST call), you will need to form the header yourself. As I said above, this is basically a hash table of key-value pairs.

So let’s do an example REST call. For the example below, I will be using the vRealize Orchestrator 7.0 API. This is a pretty new release, but it can be downloaded from the VMware website, and is deployed from OVA, so should be quick to download and spin up. The new version has two distinct APIs: one for the new Orchestrator Control Center, which is used to configure the appliance, and one for the vRO application itself, used for orchestration of a vSphere (and beyond!) environment. I will show this using both PowerShell code, and the Postman GUI.

vRealize Orchestrator has a fairly straight forward API, you can access the documentation by opening your browser and going to ‘https://<vro_ip&gt;:8281/vco/api/docs’, you will be presented with this screen:

1

From here we can explore any area exposed by the API, for the sake of this example we’re going to do something simple, we are going to return a list of the available workflows in the vRO system. So select ‘Workflow Service’, and click ‘GET /workflows’, and we can see a bit of information about the REST call to list workflows. This is what it shows:

1

This being a ‘GET’ call, we don’t see a lot here, but we will run the call anyway, and see what we get back, in later articles we will go through changing configuration. First we will make the call using PowerShell, the script is as follows, this is pretty simple:

# Ignore SSL certificates
# NOTE: This should not really be required, if you are using
# proper certificates, and have working DNS
Add-type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
public bool CheckValidationResult(
ServicePoint srvPoint, X509Certificate certificate,
WebRequest request, int certificateProblem) {
return true;
}
}
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
# Define our credentials
$user_name = "vcoadmin";
$password = "vcoadmin";
$vro_server_name = "192.168.1.201"
# Convert the credentials to a format we can use in the
# header.
$auth = $user_name+":"+$password;
$Encoded = [System.Text.Encoding]::UTF8.GetBytes($auth);
$EncodedPassword = [System.Convert]::ToBase64String($Encoded)
$header = @{"Authorization"="Basic $($EncodedPassword)"};
# Define the URI for our REST call
$vro_getplugin_uri = "https://"+$vro_server_name+":8281/vco/api/plugins"
# Run the call and store the result in a variable
$get_plugins = Invoke-WebRequest -Uri $vro_getplugin_uri -Method Get -Headers $header -ContentType "application/json"

This returns the result of the call to a variable, unfortunately, although this is in JSON format, it will be horribly formatted if you try to output it:

1.png

If we run:

$get_plugins.content | ConvertFrom-Json | ConvertTo-Json

Then the built in JSON formatting will reformat it to make it readable:

1

This gives us a readable list that we can refer to, filter on, and do all the cool stuff that PowerShell makes simple.

We will now look at doing the same thing with Postman, so first you will need to install the Postman plugin from the Chrome App Store, then open it, you will see this:

1

We know our credentials and URI, so we’re just going to go ahead and step through making the request. First, enter the URI in the box:

1

Now we need to add our authentication method, so change ‘No Auth’ to ‘Basic Auth’, enter the credentials for the ‘vcoadmin’ user, and click ‘Update request’:

1

You will notice that ‘Headers (0)’ changed to ‘Headers(1)’, if you click on that you can see the header for the request, with the encoded credential:

1

Now click ‘Send’ and our REST call will be submitted, the same JSON we got earlier will be nicely formatted and displayed in the lower pane:

1

And that’s a REST call completed, with the result returned, and we know how to do it using PowerShell, to make programmatic calls, and with Postman, to explore and understand APIs.