Your data. Anywhere you go.

New Relic for iOS or Android


Download on the App Store    Android App on Google play


New Relic Insights App for iOS


Download on the App Store


Learn more

Close icon

Relic Solution: Using Synthetics to Monitor SSL Expiration

rfb

#1

No one wants an SSL certificate to expire on their watch, but they can be hard to keep track of. So let’s show you how to proactively monitor SSL certificate expiration dates. For example, let’s say you want an alert to be triggered if your SSL certification expires within the next 30 days.

If you’re interested in simply knowing whether the SSL certification is expired or not, you can use a free ping test.

Below is a code snippet that provides a synthetic monitor (which must be an API test) that will fail if your SSL certificate expires within X days. Copy the code below, paste it into an API test in New Relic Synthetics, hit validate and enjoy!

Have questions or improvements? Let us know!

var request = require('request');  

/* 
************************************************** 
What is the URL that you want to check? 
************************************************** 
*/ 
var urlToMonitor = 'https://newrelic.com'; 
console.log('Preparing to monitor '+urlToMonitor);  

/* 
************************************************** 
How many days before your certification expires do you want this monitor to fail?  
I.e. daysBeforeExpiration = 30 
This monitor will start failing within 30 days of the SSL certificate expiring 
************************************************** 
*/ 
var daysBeforeExpiration = 30; 
console.log('This monitor will fail if the SSL certificate expires within the next '+daysBeforeExpiration+' days...');  

var r = request({   
   url: urlToMonitor,   
   method: 'HEAD',   
   gzip: true,   
   followRedirect: false,      
   followAllRedirects: false 
});  

r.on('response', 
   function(res) {   
      var certDetails = (res.req.connection.getPeerCertificate());   
      var expirationDate = new Date(certDetails.valid_to);   
      var certificateIssuer = certDetails.issuer.O;   
      //   
      console.log('This certificate was issued by '+certificateIssuer, '');   
      console.log('This SSL certificate will expire on '+expirationDate, '');   
      //   
      //Let's offset the failure date by the user supplied daysBeforeExpiration   
      expirationDate.setDate(expirationDate.getDate() - daysBeforeExpiration);   
      //   
      var currentDate = new Date();   
      //   
      console.log('**** Offset expiration date: '+expirationDate);   
      console.log('**** Date at time of testing: '+currentDate);   
      //   
      if(expirationDate < currentDate){     
         throw new Error('The test has FAILED as the offset expiration date is before now!');   
      }else{     
         console.log('The test is a SUCCESS, the expiration date is after now...');   
      } 
   }
);

SSL certificate monitoring
Monitoring subdomains for multiple issues
Explorers Hub Treasure Hunt
Feature Idea: Is there a way to monitor revoked certificates in synthetics API test?
#2

Now this is an extremely useful sample script.

Hmm, thinks of using custom events inserted into Insights to create a dashboard displaying a list of sites with certificates due to expire in the next month!


#3

If anyone’s got some spare time, it would be good to extend this code to walk through the certificate chain to check for expiry of intermediate certs as well.

While expiring service certificates are the most commonly hit gotcha, intermediate certificate expiry is something I’ve been tripped up by in the past.

To be honest, when using public CA’s, there shouldn’t really be a scenario where certificates are issued beyond the lifespan of the CA chain. However, I have seen this occur with internal PKI solutions


SSL problem did not trip up synthetics browser
#4

@pault! It is great to see you here again! I’ll make sure that the folks who sorted this idea get your feedback and see if they can come up with anything.


#5

I concur that this script is very useful, and I have tested it. Seems to work.

Curious though - this seems very generic. On a site, I might have multiple servers - is there any way I can specify the particular server (IP or server name) to check the cert on?

thx!


#6

Edited to add fixed the script.

I gone done it :smiley:

First the script, which will create a custom SSLCertificateCheck in Insights for each url that is provided to be monitored. The synthetic should run once a day.

var request = require('request'),
  assert = require('assert'),
  Q = require('q');
/*
**************************************************
What is the URL that you want to check?
**************************************************
*/
var urlsToMonitor = ['https://newrelic.com/', 'https://google.com/'];

var licenseKey = '{YOUR INSIGHTS API INSERT KEY}';
var accountId = '{YOUR ACCOUNT TO POST EVENTS}';

function treatAsUTC(date) {
    var result = new Date(date);
    result.setMinutes(result.getMinutes() - result.getTimezoneOffset());
    return result;
}

function daysBetween(startDate, endDate) {
    var millisecondsPerDay = 24 * 60 * 60 * 1000;
    return Math.round((treatAsUTC(endDate) - treatAsUTC(startDate)) / millisecondsPerDay);
}

function insertInsightsEvent(urlMonitored, certificateIssuer, daysToExpiration, expirationMilliseconds){
  var options = {
    uri: 'https://insights-collector.newrelic.com/v1/accounts/'+accountId+'/events',
    body: '[{"eventType":"SSLCertificateCheck","Url":"'+urlMonitored+'","Issuer":"'+certificateIssuer+'","DaysToExpiration":'+daysToExpiration+', "ExpirationDate":'+expirationMilliseconds+'}]',
		headers:{
			'X-Insert-Key': licenseKey,
			'Content-Type': 'application/json'
		}
  };
  console.log("Posting event for: "+urlMonitored);
  request.post(options, function(error,response, body){
      console.log(response.statusMessage);
      console.log(response.statusCode + " status code");
      assert.ok(response.statusCode == 200, 'Expected 200 OK response');  
      var info = JSON.parse(body);
      assert.ok(info.success == true, 'Expected True results in Response Body, result was ' + info.success);
      console.log("SSL cert check completed successfully");
    });
}

function processSite(urlToMonitor)
{
	var deferred = Q.defer();
  console.log('Preparing to monitor '+urlToMonitor);

  var options = {
    url: urlToMonitor,
    method: 'HEAD',
    gzip: true,
    followRedirect: false,
    followAllRedirects: false
  };

  request(options, function(error, response, body){
      var certDetails = (response.req.connection.getPeerCertificate());
      var currentDate = new Date();
      var certExpirationDate = new Date(certDetails.valid_to);
      var daysToExpiration = daysBetween(currentDate, certExpirationDate);
      var certificateIssuer = certDetails.issuer.O;
  
      console.log('This certificate was issued by '+certificateIssuer, '');
      console.log('This SSL certificate will expire on '+certExpirationDate, '');
      console.log('**** Date at time of testing: '+currentDate);
      console.log('**** Days to expiration: '+daysToExpiration);
      console.log("Creating event for: "+options.url);
      
      insertInsightsEvent(options.url, certificateIssuer, daysToExpiration, certExpirationDate.getTime());
      deferred.resolve();
    }
  );

  return deferred.promise;
}

for(var i=0; i< urlsToMonitor.length; i++)
{
  var urlToMonitor = urlsToMonitor[i];
  processSite(urlToMonitor);
}

You can then set up a dashboard for the checks. We have a 90 day limit on ours so that we have time to action the new certificate.

NRQL for left hand widget:

SELECT count(*) AS 'Number of Expiring Certs' FROM SSLCertificateCheck WHERE DaysToExpiration < 90

NRQL for right hand widget:

SELECT * FROM SSLCertificateCheck since '00:00:00' ago ORDER BY DaysToExpiration

You need to use the Insights Data Formatter to set your preference for the ExpirationDate.

Finally, you can set up an alert using the NRQL for the left hand widget to notify someone that a certificate requires action.


#7

As always, you are awesome @stefan_garnham - this is a great template for others!


#8

I authored the original script. I made an OTC account just to log in and tell you that this extended functionality is AWESOME! Thanks for the share @stefan_garnham - rock on - appreciate the value add here.


#9

Lisa - you’re absolutely right on. When I authored the script, I was hoping to give a baseline for folks to extend as needed. That said, I believe capturing IP may be fairly trivial.

Getting the IP address depends on what your architecture looks like, if you’re passing through a proxy, etc. Generally, putting this in the callback should do the trick (Please note: I have not tested this method to capture IP):

var ip = res.req.headers[‘x-forwarded-for’] ||
res.req.connection.remoteAddress ||
res.req.socket.remoteAddress ||
res.req.connection.socket.remoteAddress;

That said, I’d strongly advise reviewing the request module docs and peep this StackOverflow post to gain a better understanding: https://stackoverflow.com/questions/8107856/how-to-determine-a-users-ip-address-in-node

Rock on - thank you much!


#10

For @pault, here is a snippet to walk the certificate chain. I’ll leave you to plug in the additional logic that you require :laughing:

  var certDetails = (response.req.connection.getPeerCertificate(true));
  console.log("Got certDetails");
  console.log(certDetails);
  var fingerprints = [certDetails.fingerprint];

  var currentFingerprint = '';
  if(certDetails.issuerCertificate !== "undefined")
  {
    var chainCert = certDetails.issuerCertificate;
    while(fingerprints.indexOf(currentFingerprint) == -1)
    {
      console.log("Chain Cert");
      console.log(chainCert);

      if(chainCert.issuerCertificate === "undefined") break;

      currentFingerprint = fingerprints[fingerprints.length-1];
      chainCert = chainCert.issuerCertificate;
      fingerprints.push(chainCert.fingerprint);
      
    }
  }

September 15, 2017 Post of the Week—Alerts, APM and Shopping Challenge!
#11

@stefan_garnham - you are at it again being awesome! Thanks for continuing to build out this remarkable resource!


#12

There’s a number of things to think about here if you’re setting up deeper cert monitoring on internal load balanced nodes?

  • Is your load balancer doing TLS termination or pass through? If it’s pass-through then don’t worry as your monitor should hit all the nodes eventually and spot an expiring cert chain.
  • If you are terminating TLS on the load balancer, I’d expect querying the underlying node with an internal FQDN will throw errors due to strict validation - the FQDN won’t match the certificate subject name
  • Again, if you’re terminating TLS at the load balancer, are the underlying nodes exposed to the internet/new relic? If not, you’ll need to look at deploying the synthetic on a private minion within your infra stack. Either that or open up IP access to the New Relic synthetic IP range but be aware that it’s a floating IP list in some locations.

So in simple cases, you can just add the load balanced node FQDNs to the urlsToMonitor variable but depending on your infra stack, there may be a few hoops you need to think about and jump through.

P


#13

Great use case!

Trying to run this code for my instance but seeing this:
Preparing to monitor https://newrelic.com/
Preparing to monitor https://google.com/
TypeError: Cannot read property ‘valid_to’ of null
at Request.eval [as _callback] (eval at (/opt/runtimes/3.0.0/modules/synthetics-runner/lib/job-resource/index.js:76:19), :63:52)
at Request.self.callback (/opt/runtimes/3.0.0/node_modules/request/request.js:188:22)
at emitTwo (events.js:106:13)
at Request.emit (events.js:191:7)
at Request. (/opt/runtimes/3.0.0/node_modules/request/request.js:1171:10)
at emitOne (events.js:96:13)
at Request.emit (events.js:188:7)
at IncomingMessage. (/opt/runtimes/3.0.0/node_modules/request/request.js:1091:12)
at IncomingMessage.g (events.js:292:16)
at emitNone (events.js:91:20)

The original SSL check works fine for me but would like to post data to Insights.
Not sure why (response.req.connection.getPeerCertificate()) is returning ‘null’.

Any ideas?


#14

Hi @bevan - one of the domains you provided does not have an SSL cert which is what the line that is failing is attempting to retrieve.


#15

Hi Stefan, thanks for the quick response. I was literally using the script as is (with the NR and Google URLs) and adding my Insight credentials.
Got it working by using the syntax from the first script.

Great job by those who put this together.


#16

Sorry for revisiting this, but would anyone know why I would be receiving the following for certain URL’s and how to overcome it…?

TypeError: Cannot read property ‘req’ of undefined
at Request.eval [as _callback] (eval at (/opt/runtimes/2.0.0/modules/synthetics-runner/lib/job-resource/index.js:76:19), :60:34)
at self.callback (/opt/runtimes/2.0.0/node_modules/request/request.js:198:22)
at Request.emit (events.js:107:17)
at Request.onRequestError (/opt/runtimes/2.0.0/node_modules/request/request.js:861:8)
at ClientRequest.emit (events.js:129:20)
at TLSSocket.socketErrorListener (_http_client.js:271:9)
at TLSSocket.emit (events.js:129:20)
at onwriteError (_stream_writable.js:317:10)
at onwrite (_stream_writable.js:335:5)
at TLSSocket.WritableState.onwrite (_stream_writable.js:105:5)


#17

I’m seeing the same behaviour and I’m 100% sure that the URL’s are HTTPS enabled. Does anyone have an idea?


#18

The output from @LAMBERT suggests that the response has not returned the req property on the line:

var certDetails = (response.req.connection.getPeerCertificate());

This would suggest that either the domain is not under SSL or that the version of SSL is not supported. FYI, https://google.com does not use SSL and as already been reported in this thread by @bevan


#19

is there any way to handle these situations within the script so it does not fail? I’m not the best at coding, but it would be cool to be able to know which URL is not SSL or properly supported.


#20

I am getting the exact same error. Can you please explain how did you get it working by using the syntax from the first script ?