03 Jan

Cisco ASA REST API – Part III: Checking if prefix is directly connected

First published: 03/Jan/2017
Last update: 03/Jan/2017

It’s time to do some programming and really use REST API for something good. The first script will be used to check if specified prefix is directly connected to any of firewall interfaces. Script requires two arguiments: checked IP address and IP address of firewall. The execution of script will be as below

$ ./IfDirectlyConnected.pl
Usage: IfDirectlyConnected.pl [Checked IP Address] [Firewall Management IP]

At this demo script require IP addresses to be used and is not checking if arguments are IP addresses, just simply validating if two it’s executed with two arguments.

Please take a moment to look back to my post Cisco ASA REST API – Lab topology and programming language where I explained the topology of simulated network and presented IP addresses assigned to each device. Routing is configured and all subnets are reachable.

We will test te script on asav-1 firewall. The expected results are as follow:

$ ./IfDirectlyConnected.pl 10.0.12.5 172.16.1.51
Checking address 10.0.12.5 on firewall 172.16.1.51
RESULT: Destination route is directly connected

$ ./IfDirectlyConnected.pl 10.0.24.5 172.16.1.51
Checking address 10.0.24.5 on firewall 172.16.1.51
RESULT: Route in routing table but not directly connected

$ ./IfDirectlyConnected.pl 10.0.99.5 172.16.1.51
Checking address 10.0.99.5 on firewall 172.16.1.51
RESULT: Subnet not in routing table

Script is available on my GitHub

Exceptions and error messages

Because I like to do some things my way I need you to get familiar with simple subroutine that I use for displaying error messages on console

sub dprint {
    print "\n\nD ==>\nLine: "
        . ( caller(0) )[2] . "\n"
        . $_[0]
        . "\n<== D\n\n";
}

This dprint subroutine is just a normal print but adding two thing = nice brackets by arrows so we can easily find it on console and information about line from which it was executed. The second feature is done using caller function which is part of base Perl pack.

Programming POST method

We will start with creating subroutine that will execute POST method on specific API object defined by URL. If you look carefully you will notice that I modified code that is provided by Cisco in REST API Documentation. It’s only implemented as subroutine with some additions I need for flexibility of my programs.

My subroutine requires three attributes – Management IP address of device on which it will be executed, URL of API object and JSON object that will contain structure specific to called URL. It returns two values – a scalar object that contain the response obtained from ASA and integer with response code.so we can handle an errors. Response codes has been discussed in Cisco ASA REST API – Part II: How it’s really working?

# Function:	ExecutePOSTMethod
# Argument:	Destination IP, URL, Data
# Return: 	scalar, integer
# Get management IP of device based on interface assigned address using %ManagementIPArray
sub ExecutePOSTMethod {
    my $DestinationIP = $_[0];
    my $url           = $_[1];
    my $data          = $_[2];

    # Configurables
    my $endpoint = "https://" . $DestinationIP;
    my $userpass = "cisco:cisco"
        ;  #default username and password... can be reset by command line args

    # Older implementations of LWP check this to disable server verification
    $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;

    # Set up the connection
    my $client = REST::Client->new();

# disable server verification
# Try SSL_verify_mode => SSL_VERIFY_NONE.  0 is more compatible, but may be deprecated
    $client->getUseragent()->ssl_opts( SSL_verify_mode => 0 );

    $client->setHost($endpoint);
    $client->addHeader( "Authorization",
        "Basic " . encode_base64($userpass) );
    $client->addHeader( "Content-Type", "application/json" );

    $client->POST( $url, $data );

    return ( $client->responseContent(), $client->responseCode() );
}

Because login and password is the same on all devices it’s hardcoded in line 12 of this subroutine. Endpoint parameters are merged in my code in line 11.

Without modification from documentation example we open SSL connection to specified fireall, authenticate and inform that application/json object will be sent. Finally we call POST method on opened connection and return received response and respose code

My subroutine should be extended by some additional exceptions handling but it will fit for our purposes.

 How to check prefix in routing table?

As mentioned at the beginning execution of the script require two arguments:

$ ./IfDirectlyConnected.pl
Usage: IfDirectlyConnected.pl [Checked IP Address] [Firewall Management IP]

In this exercise we want to check if IP address given as first argument to our program (here 10.100.15.100) is in network directly connected to any of firewall interfaces. We pass management IP addresses as second parameter.

We may easily think about two possible approaches to this problem:

  1. We can call URL /api/interfaces/physical that will return information about all physical interfaces available on firewall with associated IP addresses and netmasks. Then we can analyze those attributes received for each interface and calculate if given IP address fits into subnet defined on any of the interface
  2. We can execute command show route 10.100.15.100 and parse output checking for information if route is directly connected. This will be like programming what we exactly do while using CLI.

In this example we will implement CLI approach. Why? The problem with REST API is that you won’t be able to do everything using it. There are many limitations that we may find problematic. But we can execute any CLI command via REST API and parse output if we get stuck so we need to know how to handle those situations.

If we think how script should work and visualize it as flow diagram it will look as below

CheckIfRouteDirectlyConnected Flow Diagram.png

From flow diagram we see that we need to create few more subroutines so our program is modular

Executing CLI command via REST API

Before we execute any method we need to understand what JSON object may be required as an argument while using specified URL. We will find this in REST API documentation along with nice examples.

Because we want to execute one command we need to include following JSON object in POST method body

{
  "commands": [
    "show route 10.100.15.100"
  ]
}

When this data is used in POST method call – we need to handle response and response code. Full function may look like below

# Function:	GetASARoutingTableEntry
# Argument:	Management IP, Checked IP
# Return: 	boolean
# Check if NetworkObject object exist
sub GetASARoutingTableEntry {
    my $ManagementIP     = $_[0];
    my $CheckedIPAddress = $_[1];

    my $data = '{
	  "commands": [
		"show route ' . $CheckedIPAddress . '"
	  ]
	}';

    my ( $NetworkObjectResponse, $NetworkObjectResponseCode )
        = ExecutePOSTMethod( $ManagementIP, "/api/cli", $data );

    if ( $NetworkObjectResponseCode == 404 ) {
        return false;
    }
    else {
        return $NetworkObjectResponse;
    }

}

For this function we pass two arguments = Management IP of the firewall and checked IP address. JSON object is scalar variable in Perl so can be filled with proper concatenation of text and variables. Then we use function we created previously to execute POST method. If return code is differen than 200 we return with error, otherwise we return JSON object containing output of execution the command. I like the way we handle Response Codes in function where ExecutePOSTMethod is executed. This way we can customize our actions depending of returned code.

Parsing output of CLI command

The output from POST method is the JSON object itself. It’s structure is described in documentation. If we call this method on asav-1 firewall we will get following JSON object in response

{
"response": [
"\nRouting entry for 10.100.15.0 255.255.255.0\n  Known via \"connected\", distance 0, metric 0 (connected, via interface)\n  Routing Descriptor Blocks:\n  * directly connected, via lxc-sshd-5\n      Route metric is 0, traffic share count is 1\n\n"
]
}

So in response we received familiar CLI output from ASA routing table. Take note that new lines are represented as \n so optically formatting is not really clear for reader. But thankfuly it’s machine that have to hande it,

We pass this object for parsing to our next method

# Function:	CheckIfRouteIsInRoutingTable()
# Argument:
# Return: 	boolean
# Checking if route is in table via ASA CLI command
sub CheckIfRouteIsInRoutingTable {
    if ( $_[0] =~ /not in table/ ) {
        return false;
    }
    else {
        return true;
    }
}

What we do here is searching for string “not in table” in received JSON object. In our scenario it’s simple and enough to return false if string is found or true if string is not found

Second function is similar looking for string “directly connected”.

# Function:	CheckIfRouteDirectlyConnected()
# Argument:
# Return: 	boolean
# Checking if route is in table via ASA CLI command
sub CheckIfRouteDirectlyConnected {
    if ( $_[0] =~ /directly connected/ ) {
        return true;
    }
    else {
        return false;
    }
}

 Main program

So looks like we have all required components so we can work on main peice of our program. Because of modular aproach to the script it just require to check parameters and execute one subrouting and parse it’s result.

# MAIN PROGRAM STARTS HERE!!

# Get arguments
my $MAINCheckedIPAddress = $ARGV[0];
my $MAINManagementIP     = $ARGV[1];

#Check number of arguments
if ( @ARGV != 2 ) {
    print
        "Usage: IfDirectlyConnected.pl  \n\n";
    exit;
}

#Execute CLI command and get "show route" from selected device
my $MAINRoutingEntryForCheckedIP
    = GetASARoutingTableEntry( $MAINManagementIP, $MAINCheckedIPAddress );

print "Checking address "
    . $MAINCheckedIPAddress
    . " on firewall "
    . $MAINManagementIP . "\n";

if ( CheckIfRouteIsInRoutingTable($MAINRoutingEntryForCheckedIP) ) {
    if ( CheckIfRouteDirectlyConnected($MAINRoutingEntryForCheckedIP) ) {
        print "RESULT: Destination route is directly connected\n\n";
    }
    else {
        print "RESULT: Route in routing table but not directly connected\n\n";
    }
}
else {
    print "RESULT: Subnet not in routing table\n\n";
}

And that’s it. If you are beginner in programming I definitely recommend to draw block diagram first so you can understand and define modularity of the program and then implement it. In this example it’s easy to understand what is happening and connect subroutines to particular block on diagram.

Leave a Reply

%d bloggers like this: