
How to create a load balancer SSL/TLS certificates report
Integration Published on •6 mins Last updatedA customer asked me the other day how to export a list of SSL/TLS certificate expiry dates from our load balancer appliance.
Here's what I told him...
The problem: need to monitor load balancer SSL/TLS certificates
For background on why load balancers need SSL/TLS certificates and the consequences of expired or broken certificates, check out this blog: Effortlessly monitor load balancer SSL certificates with the ADC Portal.
But for now, let's just crack on with the customer ask and our recommended solution.
So what was the request? It went something like this:
"Is there a simple way to get a list of SSL/TLS certificates with their expiration dates from the load balancer? I see that we could call https://192.168.93.60:9443/lbadmin/ajax/get_ssl.php?t=cert&v=0 over and over again, starting at 0 and continuing along the same lines until it runs out of certificates. But is there anything better? We keep expiration dates in our database, but are concerned that some of them may have gotten out of sync."
I put my thinking head on, knew I had done something like this for a previous customer, and allowed my fingers to do their thing!
The solution: Use this simple script to automate reporting
Scripting is one of the fun tasks we get to do here at Loadbalancer.org, and here's how I used it to answer this particular request.
First, I wrote the interface and linked it into the very simple API wrapper so it was secure, didn't take any args, and simply returned the data in JSON format:
<?php
// Script to walk ssl certs XML data and return domain and
// expiry date in the array key of the XML certdata.
require_once("ssl.inc");
date_default_timezone_set('Europe/London');
require_once("/etc/loadbalancer.org/api-credentials");
if (!isset($username, $password, $apikey)) {
exit;
} else if (!isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) {
header('WWW-Authenticate: Basic realm="CertsAPI"');
header('HTTP/1.0 401 Unauthorized');
exit;
} else if ($username == $_SERVER['PHP_AUTH_USER'] && $password == $_SERVER['PHP_AUTH_PW']) {
if ($_SERVER["HTTP_X_LB_APIKEY"]) {
if (trim(base64_decode($_SERVER["HTTP_X_LB_APIKEY"])) == $apikey) {
$cert_data = read_certs_xml();
if ($cert_data['cert']['_c']['file']) {
$results['certdata'][] = populate_cert_data($cert_data['cert']['_c']['file']['_v']);
} else {
foreach ($cert_data['cert'] as $key => $cert_info) {
$results['certdata'][] = populate_cert_data($cert_info['_c']['file']['_v']);
}
}
$results['certcount']=count($results['certdata']);
header('Content-Type: application/json');
header("Cache-Control: no-cache, must-revalidate");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
echo json_encode($results);
}
}
}
function populate_cert_data($data) {
$results['file'] = "/etc/loadbalancer.org/certs/". $data . "/" . $data . ".pem";
if(!file_exists($results['file'])) {
$results['error'] = "missing";
}
$cert_data = openssl_x509_parse(file_get_contents($results['file']));
$results['fqdn'] = $data;
$results['validFrom_time_t'] = date('Y-m-d', $cert_data['validFrom_time_t']);
$results['validTo_time_t'] = date('Y-m-d', $cert_data['validTo_time_t']);
return ($results);
}
The script walks the XML for all certificates and then checks if the file is present. If it isn't present, it will return the JSON below:
{
"certdata":{
"file":"\/etc\/loadbalancer.org\/cerrs\/4.example.com\/4.example.com.pem",
"error":"missing",
"name":"4.example.com",
"state":"uploaded",
"validFrom_time_t":"1970-01-01",
"validTo_time_t":"1970-01-01"
},
"certcount":6
}
So now we have:
1) The script to read the certificates present, and
2) We also have the return data format
Then we wonder, how do we call this?
Well, put simply, we run a simple HTTP GET Request, as seen below with a cURL example:
# loadbalancer.org api/v2 cURL JSON List Certs expiry
# Created by Andruw Smalley
# really simple curl command build from the input, no real validation.
while true; do
case "$1" in
-l | --loadbalancer ) loadbalancer="$2"; shift 2 ;;
-u | --username ) username="$2"; shift 2 ;;
-p | --password ) password="$2"; shift 2 ;;
-a | --apikey ) apikey==$(echo $2 | base64); shift 2 ;;
* ) break ;;
esac
done
if [ $loadbalancer != "" ] || [ $username != "" ] || [ $password != "" ] || [ $apikey != "" ]; then
curl -u ${username}:${password} -X GET \
--header "X_LB_APIKEY: ${apikey}" \
--header Content-Type:application/json \
https://${loadbalancer}:9443/api/v2/certs_with_expiry_date_report.php -k
else
echo "./apicall.sh --loadbalancer 192.168.2.21 --username loadbalancer --password loadbalancer --apikey eP68pvSMM8dvn051LL4d35569d438ue0"
fi
Now those who've read my previous blogs will recognise this script is simply the same as that in my APICALL script (only here I've changed it from POST to GET request and removed the need to send any JSON). We only need the output here, but you don't use Linux as you don't have cURL present, so you need another way to call and retrieve the JSON output.
We can use any language that will perform a HTTP GET, so that leaves us with my normal (Powershell) or C#.
Knowing the customer used C#, I'll use this as an example...
using System;
using System.Text;
using System.Diagnostics;
// c# code to retrieve cert list
// add https and port to url
String get_report_url = "https://192.168.100.100:123/api/v2/certs_with_expiry_date_report.php";
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
// ignore cert errors - needed unless you have a real cert with a real domain
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
String username = "user";
String password = "pwd";
string auth = Convert.ToBase64String(Encoding.UTF8.GetBytes(username + ":" + password));
String apikey = "apikey";
String b64apikey = Convert.ToBase64String(Encoding.UTF8.GetBytes(apikey));
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(get_report_url);
request.Method = "GET";
request.Headers.Add("Authorization", "Basic " + auth);
// original was missing the 'X-' prefix
request.Headers.Add("X-LB-APIKEY", b64apikey);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
// save to file
using (StreamWriter writer = File.CreateText(@"D:\whatever\certs.json")) {
writer.Write(responseString);
}
//Debug.WriteLine(responseString);
This also makes sense because golang is, well, "EASY" to code and makes an .exe file for Windows, Mac or Linux.
package main
import (
"fmt"
"io/ioutil"
"net/http"
"time"
"flag"
)
func main() {
var cmd_loadbalancer string
var cmd_username string
var cmd_password string
var cmd_apikey string
flag.StringVar(&cmd_loadbalancer, "-loadbalancer", "192.168.2.21", "The ip address to access your loadbalancer.org appliance")
flag.StringVar(&cmd_username, "-username", "loadbalancer", "The username to access your loadbalancer.org appliance")
flag.StringVar(&cmd_password, "-password", "loadbalancer", "The password to access your loadbalancer.org appliance")
flag.StringVar(&cmd_apikey, "-apikey", "ghGZmu4HWBTwSktjisX6xf90zORMaYAK", "The apikey to access your loadbalancer.org appliance")
client := http.Client{Timeout: 5 * time.Second}
req, err := http.NewRequest(http.MethodGet, "https://"+cmd_loadbalancer+"/api/v2/certs_with_expiry_date_report.php")
if err != nil {
log.Fatal(err)
}
req.SetBasicAuth(cmd_username, cmd_password)
req.Header.Set("X-LB-APIKEY", cmd_apikey)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
fmt.Printf("%s", body)
// print response body to screen, we could redirect to a file
// if that was wanted.
}
As you can see from the above, golang is kinda 'wow'...!
But is the script really that simple? The answer is yes! Anyone with the aptitude and wish to learn can be posting data within an hour (it took me just 10 mins to find and implement this when I looked, and its cross-platform so no more scripts to do things! Go golang!).
What about compiling?
So, you have a script, but now you ask about compiling? That's hard right? Well no, it's simple.
One would just type go build scriptname.go
to build a binary or .exe on Windows and go run scriptname.go
to test it.
I was up and running with my first sensible script in a matter of hours.
Why walk when you can run?
And there you have it. Customer ticket closed in record time. Hopefully this solution is helpful to others. It enabled another form of automation — this time for a report that did not exist.
If you have any questions please don't hesitate to ask our technical team. Attaching the /var/log/lbadmin.log to your ticket should be enough to fix any problems.
Alternatively, you can make your life EVEN EASIER by using our centralized management platform, the ADC Portal, to effortlessly monitor SSL certificate expiry dates from a single window of control:

And the best bit is that with the ADC Portal you can see not just the certificate expiry details of our load balancers, but also those of your F5, Citrix, and Kemp appliances too!
Want to easily monitor SSL certificates?
Register for the ADC Portal and never let an expired certificate slip through your fingers again!