15 Feb

Cisco ASA REST API – Part VI: Swift on iPhone

Another not planned chapter 🙂 If you remember from Cisco ASA REST API – Part II: How it’s really working? Cisco mentions in documentation about three programming languages – Perl, Python and JavaScript via node.js. They even provide examples of code for the URI and methods that are supported. But does it mean it’s closed list of languages where REST API can be used? Definitely not! Remember, it’s still using the URI same as in web browser and methods that are same as for web servers. So you can use any programming language you want.

Why Swift? Because I got bored one evening 🙂 Well, that’s partially true. I’ve heard good opinions about Swift language from professional developers. It’s now open language available for many platforms, not only Apple products. I also like to try new things and was curious if learning at least basics of new language by myself would be hard and how quick I can do that. Also Apple was very helpful because of nice tutorial from Apple Developers which show step by step how to use XCode, build application interface and connect code to objects. There are many examples on Internet, I think the hardest thing at the beginning was to understand some language semantic constructions and get familiar with API of system libraries. Also, if you ever start programming in Swift remember that current version is Swift 3.0, but many examples on the web are from older versions and won’t work without minor or major changes to the code.

So what was my concept of an application? Easy, I just wanted to get information about firmware version installed on ASA. But of course if you have idea of other apps then sky is the limit 😉

Swift and XCode

In my article I wont be talking about how to use XCode, install additional libraries or create GUI. Most of things you need to know you will find on Apple tutorial. I will just focus on code specific to REST API. Project files are available on GitHub packed in one archive so just unzip, import to XCode and try it by yourself. Please also note, that it’s required that you act as a CA for certificate for your firewall and disable some ATS security features so self-signed certificate on ASA is accepted – both requirements were described in my previous articles in details.

My app has 4 objects in GUI:

  • Static label asking for host name of firewall
  • Text field where host name can be provided by user
  • Button used to execute action
  • Label where information about firewall response is displayed (hidden at the beginning)

simulator-screen-shot-14-feb-2017-22-01-54

Labels and text field references are defined as:

@IBOutlet weak var ProvideIPAddressLabel: UILabel!
@IBOutlet weak var IPAddressTextField: UITextField!
@IBOutlet weak var OutputLabel: UILabel!

The button reference is defined as function executed when button is pressed by user

@IBAction func CheckVersion(_ sender: UIButton) {
}

Network connections and JSON in Swift

When user press the button code defined inside of CheckVersion function is executed. To get firmware version from ASA we must use REST API and execute GET method on URI https:///api/monitoring/device/components/version. That will give us JSON object in reply where in asaVersion field information about it is stored.

{
  "kind": "object#Version",
  "selfLink": "/api/monitoring/device/version",
  "upTimeinSeconds": 32340,
  "deviceType": "ASAv",
  "firewallMode": "Router",
  "totalFlashinMB": 129024,
  "asaVersion": "9.6(2)",
  "currentTimeinSeconds": 1487113971
}

There are two ways we can open network connection – it’s either by using build-in functions or adding Alamofire library to your project. Alamofire looks to be a little more intuitive and more clear from source code perspective way to handle network connection. But it’s always a matter of personal preferences.

My application code available on GitHub have some additional outputs for troubleshooting purposes. Examples in text are stripped of this part so the whole example is more clear for the reader. The print() function is used for debugging or additional information for troubleshooting only. Those messages are not displayed in application but only written down to output window which is at the bottom of XCode main window visible when you run application in XCode Simulator.

The whole implementation is I’d say pretty simple in its logic. Let’s look at it step by step

Verify if user provided any input

    @IBAction func CheckVersion(_ sender: UIButton) {
        // Check if anything is in TextField box
        guard let text = IPAddressTextField.text, !text.isEmpty else {
            let Alert = UIAlertController(title: "Alert!", message: "No hostname address provided", preferredStyle: UIAlertControllerStyle.alert)
            Alert.addAction(UIAlertAction(title: "Return", style: UIAlertActionStyle.default, handler: nil))
            self.present(Alert , animated: true, completion: nil)
            return
        }

First we check if UITextField object contain any data. If its empty then we prompt user by Alert that there should be something in the text field.

Verify if input is not IPv4 address

        // Validate if IP address
        guard isNotValidIP(s: IPAddressTextField.text!) else {
            let Alert = UIAlertController(title: "Alert!", message: "Hostname is required, IP address is not allowed", preferredStyle: UIAlertControllerStyle.alert)
            Alert.addAction(UIAlertAction(title: "Return", style: UIAlertActionStyle.default, handler: nil))
            self.present(Alert , animated: true, completion: nil)
            return
        }

If we have input from user we need to verify if it’s IPv4 address or something else them might be a FQDN. The IPv6 is omitted in this example. Again if the input is IPv4 address Alert is displayed with proper error message

simulator-screen-shot-14-feb-2017-22-02-15

Verify if we can resolve DNS entry


        let hostRef = CFHostCreateWithName(nil, IPAddressTextField.text as! CFString).takeRetainedValue()
        debugPrint(CFHostStartInfoResolution(hostRef, .addresses, nil))
        guard CFHostStartInfoResolution(hostRef, .addresses, nil) else {
            let Alert = UIAlertController(title: "Alert!", message: "DNS resolution of host failed", preferredStyle: UIAlertControllerStyle.alert)
            Alert.addAction(UIAlertAction(title: "Return", style: UIAlertActionStyle.default, handler: nil))
            self.present(Alert , animated: true, completion: nil)
            return
        }

We still don’t know if it’s hostname or not, only IPv4 address as input was excluded so far. I decided to use functions provided by Apple in standard library to complete this task.
The CFHostCreateWithName() creates instance of CFHost object based on provided string. Then CFHostStartInfoResolution() function performs DNS resolution. If it fails then we display Alert box.

simulator-screen-shot-14-feb-2017-22-02-54

Execute REST API method on firewall

At this stage if everything went right we know we have input from user that is valid FQDN. So we move to Alamofire library to execute the GET method to fetch ASA version


        // Exec GET method for ASA firmware version
        // using Alamofire library
        self.OutputLabel.isHidden = true

        let credential = URLCredential(user: "cisco", password: "cisco", persistence: .forSession)
        let APIMethodURI = "https://" + IPAddressTextField.text! + "/api/monitoring/device/components/version"
        Alamofire.request(APIMethodURI).authenticate(usingCredential: credential).responseJSON { response in
            if let JSONResponse = response.result.value as? [String: Any] {
                self.OutputLabel.text = "ASA firmware version: " + (JSONResponse["asaVersion"] as! String)
                self.OutputLabel.isHidden = false

            } else {

                self.OutputLabel.text = "No information from firewall or other error"
                self.OutputLabel.isHidden = false
            }
        }

    }

Yes, that’s it. Let’s look closer now. First we define credentials because those are required to connect to REST API. We use static definition but of course it can be taken as an input from user as well. We also define in APIMethodURI the URI to be called. Then we execute request function from Alamofire library, the GET is default method so we don’t have to explicitly define it. We authenticate connection using credentials and expecting JSON object as result. We will store response in variable named response.

The response is a JSON object so we convert it to an array of strings. But if we get correct response then we display UITextLabel with string containing the value of the Array we just created from JSON value. We refer to this array by names so we need to know the structure of JSON we are expecting to receive. In our case the value is stored in key ‘asaVersion’ so we refer to this object via JSONResponse["asaVersion"] and converting this to String.

simulator-screen-shot-14-feb-2017-22-03-44

In case we catch an error we display Alert information about it. In this example we don’t handle what was the problem but in real world we should analyze both Alamofire and REST API errors to provide proper error handling.

Is it IPv4 or FQDN?

As mentioned in Apple App Transport Security (ATS) and ASA self-signed SSL certificate the ATS operates using FQDN nit IP addresses. That means if we enter IP address the connection to firewall will fail. So we need to check if user input in text field is IPv4 or string that might be the name we can resolve using DNS. I omit IPv6 in this example.

To accomplish this task easiest way is to check if entered value is IPv4 address and negate the result – we can always operate on Boolean variables. The function is as below


    private func isNotValidIP(s: String) -> Bool {
        let parts = s.components(separatedBy: ".")
        let nums = parts.flatMap { Int($0) }
        return !(parts.count == 4 && nums.count == 4 && nums.filter { $0 >= 0 && $0 < 256}.count == 4)
    }

What we do here is split the value of the string using ‘.’ as separator. Then we check if we got 4 parts (it’s stored in parts.count), if each part was containing data (the flatMap function is creating array of non-nil values so we should get 4 of them). Finally we don’t check each value in array is a value in a range from 0 to 255 inclusive but if array containing values from this range equals to 4. If all three conditions are met we know that String in text field is IPv4 address so we negate the Boolean value that is returned by this function.

Summary

As you can see we are not limited to perl, python and node.js when using REST API. We can use any programming language – most of them support the HTTP methods that are fundamental for REST API. Most of them also include or have external libraries that will let us handle JSON structures.

Leave a Reply

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