Converting Heartbeat's tls.certificate_not_valid_after field to a "days_til_expiration" integer using Logstash

First off, thanks to the Heartbeat team for adding in TLS certificate metadata with Heartbeat HTTP pings! This is a big win for everyone.

I've seen some others looking to achieve the same result in their own environments so I figure it was a good idea to share.

When using an HTTP monitor in Heartbeat, if the site is a HTTPS URL then Heartbeat automatically generates two fields of TLS metadata in each event called tls.certificate_not_valid_after and tls.certificate_not_valid_before.
These fields are formatted in ISO8601 - which is PERFECT for Kibana. However, for my use case my users want a simple "days_left_till_expiration" field that literally only displays a single integer --- the days left for when the TLS cert will expire. I needed a way to sort of convert these time fields into a single integer for my users. This is where Logstash comes into play...

In our architecture of Elastic Stack clusters, including all of our beats configurations, are designed to purposely send all beats data to a centralized Logstash cluster behind a load balancer. We do this to simplify our overall architecture. Even though almost all of our beats data is simply a 'pass through' with no filtering, this allows us the flexibility to take advantage of Logstash if needed.

So using Logstash, we can use a small ruby script with a simple calculation to effectively convert the tls.certificate_not_valid_after time field into an integer showing days left until expiration.

Here is the filter we are using:

filter {

  # Create a new field from the Logstash generated @timestamp field called 'event_time'. This will be used for time calc.
  mutate
  {
    add_field => ["event_time", "%{@timestamp}"]
  }

  # At this point, the date fields are strings which ruby has issues with. Save each date field that needs to be calculated into their own new field targets so ruby won't have problems.
  date {
		id => "get_timedate"
		match => [ "event_time", "ISO8601" ]
		target => "tls_start_date"
	}
  date {
		id => "parse_tls_end_date"
		match => [ "[tls][certificate_not_valid_after]", "ISO8601" ]
		target => "tls_end_date"
	}

  # Use ruby to calculate days left between the two time fields. Save the result in a new field called tls.days_to_expiration.
  ruby {
    init => "require 'time'"
	  code => "event.set('[tls][days_to_expiration]', (event.get('[tls_end_date]') - event.get('[tls_start_date]'))/3600/24)"
  }

  # Drop the fields used to calculate as they are not needed anymore.
  # Note that I am purposely adding days_to_expiration as a child to the 'tls' metadata in the heartbeat json document, right along with the existing TLS time fields. Consistency is a good thing :)
  mutate {
    convert => { "[tls][days_to_expiration]" => "integer" }
    remove_field => [ "tls_end_date", "tls_start_date", "event_time", "headers" ]
  }
}

Now that we have tls.days_to_expiration we can create a Kibana visualization to display what certs are going to expire soon, or how many days left, or any other useful visual. Also, now you can easily create a watch to email out a notification when your TLS certs are approaching expiration!

This is just one strategy to get the job done, and this filter can probably be re-worked for better performance. But this works extremely well for my environment at this time. I don't see a need to modify my filter.

I hope this helps others that have been trying to figure out a similar solution for TLS cert expiration.

(Tested on both Logstash and Heartbeat v6.5.4 and v6.6.1).

  • Joey D
1 Like

Thanks @joedissmeyer that's a very nifty solution. Impressive use of LS to solve this problem.

Our hope is to add more of a countdown style 'days remaining' field to the monitor detail pages for the new Uptime app, followed by a special alert on this field as well. Right now that page is a bit bare, but we hope to change all that ASAP :slight_smile: First, however, we need to build out the alerting system in the first place.

1 Like

Joey, this is what I was looking for, thank you so much!!!

Even though is working for me I get this error in logstash output:

[ERROR][logstash.filters.ruby    ] Ruby exception occurred: undefined method `-' for nil:NilClass

I thought that it could be and issue when monitor.status was down and I applied an if statement on the filter but still get the same error:

if "up" in [monitor][status]

Im running logstash on docker 6.6.2, does this happened to you as well?

Again thank you very much for this.

EDIT: Fixed it by adding this to the filter:

if [tls][certificate_not_valid_after]
1 Like

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.