06 Oct

AWS Step Functions for networkers – Lambda functions

Now it is time to create Lambda functions for our small state machine. The first one will fetch version stored in S3 bucket object; the second function will get it from firewall using REST API. Then we will pass the collected values to the third function. With AWS Step Functions it is very easy to achieve!

Reading object from S3 bucket

This python script will be straightforward

import boto3
from botocore.exceptions import ClientError

def main(event, context):
    s3 = boto3.resource('s3')

    try:
        bucketObject = s3.Object('stepfunctiondemobucket','ASAVersion.txt')
    except ClientError as e:
        print(e.message)

    print(bucketObject.get()['Body'].read().decode('utf-8'))

    return bucketObject.get()['Body'].read().decode('utf-8')

All we do here is accessing the AWS S3 bucket and reading the object content. We use the boto3 library for accessing AWS services. I wrote more about this in my post AWS Lambda guide part II – Access to S3 service from Lambda function.

Reading from ASAv

The second script will get firmware version directly from firewall via REST API. If you do not know how to enable REST API on Cisco ASA, please read my old post: Cisco ASA REST API – Part I: Getting started.

This script is a modified version of what you can find in online Cisco ASA REST API documentation as an example. I changed it by putting it inside of main() function so we can run it in Lambda, I removed unnecessary variables, so everything is static, I enabled the support of self-signed certificates, and instead of printing the whole JSON structure the function just print and return value of the asaVersion key.

import base64
import json
import urllib2
import ssl


def main(event, context):
    ssl._create_default_https_context = ssl._create_unverified_context
    server = "https://asav-1.virl.lan"
    username = "cisco"
    password = "cisco"
    headers = {'Content-Type': 'application/json'}
    api_path = "/api/monitoring/device/components/version"
    url = server + api_path
    f = None

    req = urllib2.Request(url, None, headers)
    base64string = base64.encodestring('%s:%s' % (username, password)).replace('\n', '')
    req.add_header("Authorization", "Basic %s" % base64string)
    try:
        f = urllib2.urlopen(req)
        status_code = f.getcode()
        if (status_code != 200):
            print 'Error in get. Got status code: ' + status_code
        resp = f.read()
        json_resp = json.loads(resp)
        print json_resp['asaVersion']
    finally:
        if f:  f.close()

    return json_resp['asaVersion']

Passing data between states in Step Functions machine

In previous examples the steps in state machines pretty much did nothing. What we want now is to run the Lambda instead of Wait timer. However, first, let’s add the third function that will now just print the parameters provided during the execution. Save it as CompareFirmwareVersion function with the following content

def main(event, context):
 print event

Now it is time to modify the state machine definition. If we want to run Lambda function as a state, we need to change state type to Task and provide ARN of the Lambda function as Resource. The new state machine definition:

{
  "Comment": "Example of simple AWS Step Function from Piotr Wojciechowski",
  "StartAt": "GetConfiguration",
  "States": {
    "GetConfiguration": {
      "Type": "Parallel",
      "Next": "CompareFirmwareVersion",
      "Branches": [
        {
          "StartAt": "GetVersionFromS3",
          "States": {
            "GetVersionFromS3": {
              "Type": "Task",
              "Resource": "arn:aws:lambda:eu-west-1:0000000000000:function:GetVersionFromS3",
              "End": true
            }
          }
        },
        {
          "StartAt": "GetVersionFromASA",
          "States": {
            "GetVersionFromASA": {
              "Type": "Task",
              "Resource": "arn:aws:lambda:eu-west-1:0000000000000:function:GetVersionFromASA",
              "End": true
            }
          }
        }
      ]
    },
    "CompareFirmwareVersion": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-1:0000000000000:function:CompareFirmwareVersion",
      "End": true
    }
  }
}

Ok, so we defined state machine with three states. What about the parameters? How we pass output from first two functions to the third one? The answer is easy – it will be passed automatically! The AWS Step Function engine is responsible for sending returned values from the previous step to the next step. If, like in our example, preceding phase consists of two independent steps then results from both of them are merged.

It is easy to track the input and output values using the web interface. When you execute the state machine for each step you can see its details in Step Details window. In the Input tab, we will find the value provided to this step (if this is Lambda function this value is accessible via event variable), the Output tab contains value returned by the step.

The input of CompareFirmwareVersion is combined output of two Lambdas executed in previous steps

The input of CompareFirmwareVersion is combined output of two Lambdas executed in previous steps

In this example, each Lambda returns “raw” value – it is not embedded into JSON object. In real Step Functions machines, you should always use JSON even if you are not returning multiple values.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: