Sunday, August 16, 2020

Why Automate? Writing a self-testing Python class for REST or XML API invocation

 So far, most API invocations, at least in terms of what you need to do, are pretty simple to execute.

Then again, just about every other administrative function on a computer is, as well. For example:

  • Clicking a button
  • Typing in a command or variable
Interacting with a programmable interface is as simple as any other interaction with a computer.

The primary goal with an API is not to simply replace any of those functions normally performed by a user. Using a programmable interface effectively skips most of the rigmarole performed by a skilled administrator, like:
  • Ensuring the change is correct
  • Ensuring the change is appropriate
  • Ensuring that the change won't have unexpected impacts
As an example, when you enter a vehicle to back it out of a driveway, you achieve these goals:
  • Correct: You ensure that you are entering the right vehicle, in the right driveway, and will head in the right direction.
  • Appropriate: You typically do not perform this action if you have no need, but you also don't use someone else's vehicle without permission, drive inappropriate speeds, or take unnecessary steps that could endanger life
  • Performs as expected: People are generally more unpredictable, so the analogy falls apart here. But generally, people get where they intend to go while driving.
While most people don't always fully realize that they're performing these steps, each is typically present. We see many instances in the industry where engineers are considered "unreliable". In my experience, these individuals just aren't aware of those steps, and simply need to make it a conscious effort.

This has to be a fully conscious effort when developing software or automating changes. While a programmable interface does not perform these things automatically, we can do them ourselves relatively easily, given the right tools.

Let's cover this in micro first - and cover the concept of unit testing.

Unit testing is based upon the principle, that, for every individual thing you can do programmatically, you should at least test once.

The website Software Testing Fundamentals actually covers unit testing itself much more thoroughly than I will here, as this is geared more towards immediate practical applications for people who don't exclusively write code for a living.

This is step one to ensuring that programmatic changes are correct, appropriate, and won't have unintended side effects or at least ensuring your infrastructure won't end up on r/softwaregore

For this to work, every single software function executed must be proven just like any other mathematical formula. Typically, the easiest way to do this from a pure mathematics standpoint is by trying the formula in reverse.

I'll be honest, this doesn't scale particularly well when dealing with infrastructure programmability. We used to joke in college that physicists and mathematicians would start with "assuming a cow is a sphere at absolute zero in a vacuum," but we didn't really understand that yet. The joke was probably inherited and re-used, where:

We, as engineers designing infrastructure, have limited time and resources to tackle the fractal complexity of what we consider the "real-world." 

Infrastructure designers and maintainers live somewhere between the two, where software is based on mathematics but is slowly approaching the fractal complexity of the "real world."

So, we rip off what other engineering disciplines have done for millennia, component testing.

Typically, engineers test a component based on results, or by breaking a component. Some examples of where these approaches are practical are:
  • Mathematical proofs and sanity checks: Generally, if you ask for a fraction, you want a fraction. If you ask for a boolean, you want true or false. If you ask for a routing table, you probably don't want a VPN client table.
  • Simulations: Run the code against simulated production systems, remembering that machines don't really mind ugly levels of repetition. Sample sizes of less than 100% on individual tests are impossible in the real world, so test coverage stays in the low percentages and is later found statistically representative. We're not really burdened by this here!
  • Fuzzing: Intentionally feed garbage input, give a piece of software 
Third-party tools like pipelines can cover automated test EXECUTION, but before we cover that, we need to cover how to test, and better yet, how to bake testing in so that it doesn't take much effort.

If you already have a library that you're re-using to execute changes, you're handing off responsibility for mathematical proofs, but as the person executing a change, you still have operational responsibility for any unintended effects. So you treat this as an engineer, and move forward with simulations and fuzzing.

Let's start by creating a Python class. PEP 8 - the style guide for writing python code, has a lot to say about names. I'll call this one IronStrataReliquary, for the following reasons:
  • CapWords: This is just what PEP 8 agrees is convention compliant. 
  • Obvious: 
    • Iron is a common prefix for Palo Alto coding projects - it's a portmanteau derived from a common acronym for Palo Alto Networks (PAN). 
    • Strata is the currently rebranded signifier for Palo Alto's NGFW or "Core" Product line. this delineates from Cortex or Prisma.
    • We love things in threes. Reliquary is a thing that holds relics - I picked this because the word "toolbox" was too derivative
  • Unique: We want to package this class as an installable, and if the name conflicts with existing software, it's typically because of a class name. 
From here, we structure the class by illustrating a rough outline for what the class should contain:
  • Initialization: This is not a C Constructor. This is effectively a script or function to bootstrap an object. In our case - __init__ contains or initial connection to a Strata appliance, and will prepare it for immediate use. 
  • Variables: I am storing API XML responses against variable names in a table:
    • Name: What you'd find it by, annotated with the version first tested against
    • XML Query string: This is you asking the API for something
    • XML Response string: This is what a normal response should contain, in some form. See how easy this is?
  • HTTP Errors: Just a quick one - I didn't create it. I added in HTTP errors that an NGFW can throw as well.
  • API GET/POST functions. Feed this XML, they'll send it to an NGFW.
  • Data conversion: Interpret HTTP errors, convert XML to JSON, etc.
All I have to do, once done, is write an exceedingly simple script to test this out:

Since the array in question already stored expected responses, I'm able to apply a for loop and just iterate through all of the provided XML Queries and responses to test the code with nearly full coverage. After I've finished the rest of the PEP 8 / Code conformance, the last remaining work is to:
  • Explore the API and add more variables/responses
  • Export strata_bibliotheca to a JSON file for easy management outside of the Python class.

PAN-OS IPv6 Error: bgp peer local address 0:0:0:0:0:0:0:0 does not belong to interface

  When encountering this error, please ensure that "Enable IPv6" is set under interfaces: Hope this helps! Happy IPv6ing!