Many different regulations have sections that cover logging requirements and one common requirement is the immutability of logs.  You must be able to prove that a log message was not changed or tampered with (many times it is worded as “you must be able to prove”, not “you must prove”).  If you have X-Pack Security then the problem is solved because the access restriction and audit logging of Elasticsearch requests usually covers whatever the requirements are of the regulations.  The free version of Elasticsearch is not so lucky.

There is no access restriction by default, no authentication, and no audit logging.  It is theoretically possible for a person after gaining access to Elasticsearch to edit and modify log messages which in turn means that you fail the audit.  However there is one option that can help to satisfy some of the requirements.  Make a copy of the original message and then digitally sign the copy.  It is then possible to go back and verify if the original message was changed or not.

(this can be done with a combination of the fingerprint-filter plugin in Logstash and several other intermediary steps to convert and sanitize the message, but the below is far easier)

 

How it works is this.

  1. Message is received by Logstash
  2. Logstash makes a copy of either the original ‘message’ field for protocols such as syslog which are text based, or makes a copy of the entire event in json format for protocols such as any of the beats.
  3. This copy is cleaned of any erroneous trailing spaces or newline returns and encoded to UTF_8.  If this is not done the signature is generated with those spaces and newline returns but then Elasticsearch will strip them out it writes it to the index; once it does that the digital signature you created will no longer match.
  4. The cleaned copy is then digitally signed by a SHA2 HMAC hash.  This makes use of a key in the hashing process so the only possible way to generate the same hash is to have access to the same key.  This digital signature is added as a field and is written into Elasticsearch.
  5. As a side effect, the copy of the original message is useful to have the ‘_all’ index redirected to it and the digital signature functions as a very useful UUID.

 

In the below, the “VsH2maNmK7r42bwGwR7mwr8az8vX59x7” is the HMAC key.  Remember to change it.

Two fields are created, [signed][message] and [signed][signature]

SHA384 can alternatively be swapped for SHA256 or SHA512 (as well as SHA, SHA1, SHA224, MD2, MD4, MD5).  All are relatively fast since most of the workload is handled in hardware in the CPU and a lot of times SHA2 (SHA256, SHA384, SHA512) is faster due to enhancements made on x64 hardware specifically for SHA2.

 

The code below will copy and sign the [message] field only.  This is useful for protocols such as syslog where the original log message is a string of text that contains the timestamp and all necessary data.

#####################
## Author: packetrevolt.com
#####################
## digitally sign the 'message' field only
ruby {
  init => "require 'openssl'"
  code => "
    event.set('[signed][message]', event.get('[message]').to_s.strip).force_encoding(Encoding::UTF_8)
    event.set('[signed][signature]', OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA384.new, 'VsH2maNmK7r42bwGwR7mwr8az8vX59x7', event.get('[signed][message]').to_s).force_encoding(Encoding::UTF_8))
  "
}
#####################

 

 

The code below will copy the entire json event in Logstash into a new field and convert it to a text string.  This is useful for protocols such as any of the beats where much of the message is already parsed out.

#####################
## Author: packetrevolt.com
#####################
## digitally sign the the entire message
ruby {
  init => "
    require 'json'
    require 'openssl'
  "
  code => "
    event.set('[signed][message]', event.to_json.to_s.strip).force_encoding(Encoding::UTF_8)
    event.set('[signed][signature]', OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA384.new, 'VsH2maNmK7r42bwGwR7mwr8az8vX59x7', event.get('[signed][message]').to_s).force_encoding(Encoding::UTF_8))
  "
}
#####################

Leave a Reply

Close Menu