Automated TLS scan and display results with grades
Motivation
Do you know how many TLS certificates you have? When they expire? Are your server configured the best way possible? Wouldn't it be great if you had a list with this information on it? A list which updates every night with the latest results from an upgraded ssl scan? Here is an example on how it could look like:
Each link to the hostnames opens the technical information in JSON format generated by Qualys ssl-scan. It's not pretty but very useful.
This blog post will show how Geir Harald Hansen and I made this tool in less than 1 hour.
Command-line ssl testing tool
Qualys SSL scan is a great tool which can scan your site and give you a grade of how good your certificate is set up and the quality of it. Qualys have a [git repository (https://github.com/ssllabs/ssllabs-scan) with a command-line reference-implementation client for SSL Labs APIs, designed for automated and/or bulk testing. Please read the license carefully before using it.
TLS overview
At work we have a lot of servers that uses TLS for encrypted communication with our users. Even though our different CA authorities send us an email when the certificate is about to expire it might be hard to keep track of it all.
Getting started
In this blog post we will show you how it's possible to make a simple proof of concept scanner that will generate such a simple list for you. You will need Go >= 1.3.
In this example we use an ubuntu machine with nginx web server.
To use the command-line reference-implementation client for SSL Labs APIs you need to compile it using the Google go programming language. In Ubuntu, install it like this:
sudo apt-get install golang
Type go version
to check which version of go you have installed.
Enter your favourite project folder and clone the Qualys ssllabs-scan repo:
https://github.com/ssllabs/ssllabs-scan.git
Enter the ssllabs-scan directory and test typing
go build ssllabs-scan.go
This will make an executable named ssllabs-scan
that I'll use in my script. You can try this script checking e.g for the grade on www.reglund.com:
./ssllabs-scan --grade www.reglund.com
The tool will run for about 2 minutes and will provide you with the grade. If you run the script with no options you will get json code with all the results in. Try to check out.
To run ssllabs-scan from the command line it's possible to run tests on servers e.g. in your internal network not facing internet directly.
We used perl to write a simple proof of concept code with a goal to make a list of three things. A up to date security grade from qualys, the server name and the expiration date.
For simplicity we put all the servers into an array. In this example I use only two servers. we call the tool scan-ssl.pl:
my @ips = ('www.reglund.com','example.com','');
Next we need perl to update the ssllabs-scan repository in case some changes have been pushed. After the pull/update to get the newest definitions we delete the previous compiled tool and compile a new one:
system("cd ~/your/git/folder/ssllabs-scan/ && git pull && rm ./ssllabs-scan && go build ssllabs-scan.go");
Next we need to write to a file the results of the scan. In this case I write the file directly into the /var/www directory.
my $filename = '/var/www/sslscan.html';
open(my $fh, '>', $filename) or die "Could not open file '$filename' $!";
We've divided the html file up in three parts; top, middle and bottom where there is only the middle one that are dynamic generated. The top imports a simple stylesheet and starts a simple table and prints it to the file using the filehandler $fh.
my $html_top = << "END";
<html>
<head><link rel='stylesheet' type='text/css' href='sslscan.css'></head>
<body>
<table>
<tr>
<th>Hostname</th><th>TLS grade</th>
END
print $fh $html_top;
To make the file readable for the world we need to change the rights of the file:
chmod 0644,$filename;
After this we need to iterate through each host and scan it. The result from each host is placed in the $result variable and the result is on JSON format. To traverse the JSON result we need to decode it which is placed in the $decoded variable. We want to save the JSON file and use the hostname as filename with json at the end to show it's a json file. The JSON files also needs to be readable for all, and we chmod each file.
foreach(@ips){
my $result = `~/git/ssllabs-scan/ssllabs-scan $_`;
my $decoded = decode_json($result);
my $jsonfilename = "/path/to/your/json/$_.json";
open(my $jfh, '>', $jsonfilename) or die "Could not open file '$jsonfilename' $!";
print $jfh $result;
close $jfh;
chmod 0644,$jsonfilename;
Now that we have the JSON file we need to get the content we need to build our simple web page. We use perls JSON library to decode the JSON. This way we can fetch the values we want in the JSON structure. We need the hostname, grade and the expire date called notafter in the JSON code.
my ($host, $grade, $notafter, $date_class);
if ( $decoded->[0]{endpoints}[0]{grade} ){
$date_class = "good";
$host = $decoded->[0]{host};
$grade = $decoded->[0]{endpoints}[0]{grade};
my $timestampDateTime = DateTime->from_epoch( epoch => str2time ( $timestamp ));
my $notafterDateTime = DateTime->from_epoch( epoch => $decoded->[0]{endpoints}[0]{details}{cert}{notAfter}/100\
0);
$notafterDateTime->set_time_zone('CET');
$notafter = $notafterDateTime->strftime('%Y-%m-%d');
my $diff = $notafterDateTime->subtract_datetime($timestampDateTime);
if(($diff->in_units('months') < 2) and ($diff->in_units('months') > 1)){
$date_class = "bad";
} elsif ($diff->in_units('months') < 1) {
$date_class = "ugly";
}
} else {
$host = $_;
$grade = "Error";
$notafter = "Error";
$date_class = "ugly";
}
In the css file we want to mark good grades with green color, bad with orange, and ugly with red.
my $class;
if($grade =~ /^A/) {
$class="good";
} elsif($grade =~ /^B/) {
$class="bad";
} else {
$class = "ugly";
}
We need to put all our findings into a html table. We use example.com as an example, you need to put in your own data:
my $html_middle = << "END";
<tr>
<td><a href="http://example.com/json/$_.json">$host</a></td>
<td><span class="$class">$grade</span></td>
<td><span class="$date_class">$notafter</span></td>
</tr>
END
print $fh $html_middle;
}
And at the bottom we need to finish all the tags we opened at the top:
my $html_bottom = << "END";
<tr>
<td colspan="3">Last updated: $timestamp</td>
</tr>
</table>
</body>
</html>
END
print $fh $html_bottom;
close $fh;
If you want to test this script it is available on github. Feel free to play with it as you want.
Future work
We're planning to make an alert service e.g if you suddenly get a lower grade or if you close up on an expiration. We also want to link to whatever reason you don't get an A+. It's all in the JSON file.