############################################### # Logstash configuration for importing Hayabusa JSONL data # Author: Zach Mathis (@yamatosecurity) # Date: 2025-09-18 # Version: 1.0.0 ############################################### filter { if ![labels] or ![labels][type] { mutate { add_field => { "[labels][type]" => "hayabusa" } } } if [labels][type] == "hayabusa" { # Flatten [raw] and build *Text summaries ruby { code => ' begin raw_data = event.get("raw") emptyish = ->(v) { v.nil? || v.to_s.strip == "" || v == "-" } sanitize = ->(s) { s.to_s.strip.gsub(/[.\s]+/, "_" ) } if raw_data && raw_data.is_a?(Hash) raw_data.each do |key, value| # Skip Timestamp (often present elsewhere) next if key == "Timestamp" # --- Details.* -> Details_ + DetailsText --- if key == "Details" && value.is_a?(Hash) parts = [] value.each do |nested_key, nested_value| next if emptyish.call(nested_value) orig_k = nested_key.to_s v = nested_value.to_s flat_k = (orig_k.strip == "" ? "Details_Value" : "Details_#{sanitize.call(orig_k)}") event.set(flat_k, v) parts << (orig_k.strip == "" ? v : "#{orig_k}=#{v}") end if parts.empty? event.remove("DetailsText") else event.set("DetailsText", parts.join(" ¦ ")) end next end # --- ExtraFieldInfo.* -> ExtraFieldInfo_ + ExtraFieldInfoText --- if key == "ExtraFieldInfo" && value.is_a?(Hash) parts = [] value.each do |nested_key, nested_value| next if emptyish.call(nested_value) orig_k = nested_key.to_s v = nested_value.to_s flat_k = (orig_k.strip == "" ? "ExtraFieldInfo_Value" : "ExtraFieldInfo_#{sanitize.call(orig_k)}") event.set(flat_k, v) parts << (orig_k.strip == "" ? v : "#{orig_k}=#{v}") end if parts.empty? event.remove("ExtraFieldInfoText") else event.set("ExtraFieldInfoText", parts.join(" ¦ ")) end next end # Pass through rule dates as strings (date filter will normalize) if ["RuleModifiedDate", "RuleCreationDate"].include?(key) if emptyish.call(value) event.remove(key) else event.set(key, value.to_s.strip) end next end # Arrays -> comma-joined string if value.is_a?(Array) event.set(key, value.map { |v| v.to_s }.join(", ")) next end # Generic empties -> remove; everything else -> string if emptyish.call(value) event.remove(key) else event.set(key, value.to_s) end end end rescue => e event.tag("_ruby_exception") event.set("error_message", e.message) end ' } # Parse/normalize rule dates (kept as separate fields) if [RuleModifiedDate] { date { match => ["RuleModifiedDate","yyyy-MM-dd"] target => "RuleModifiedDate" tag_on_failure => ["_rulemodifieddate_parse_fail"] } } if [RuleCreationDate] { date { match => ["RuleCreationDate","yyyy-MM-dd"] target => "RuleCreationDate" tag_on_failure => ["_rulecreationdate_parse_fail"] } } # Remove raw so ES won't try to parse raw.RuleModifiedDate='-' mutate { remove_field => ["raw", "error_message"] } } }