From 2d86412f46ea2f65b4878b67491f6803ca914dc5 Mon Sep 17 00:00:00 2001 From: Jason Ish Date: Tue, 23 Sep 2025 17:17:00 -0600 Subject: [PATCH] ike: log attributes as objects IKE attributes are an array of TLV style objects, this means there can be duplicate types seen on the wire. However, Suricata logs these as a mapping with the type as the key. This can result in the JSON containing duplicate keys. To address this, log the attributes as an array of objects, allow duplicates to exist, for example: "client": { "proposals": [ { "sa_life_duration": "Unknown", "sa_life_duration_raw": 86400, } } } is now logged as: "client": { "proposals": [ {"key": "sa_life_duration", "value": "Unknown", "raw": 86400} ] } Also adds `"version": 2` to each IKE record to note the change of format from previous versions. Ticket: #7902 --- etc/schema.json | 120 +++++++++++++++-------------------------- rust/src/ike/logger.rs | 46 ++++++++-------- 2 files changed, 68 insertions(+), 98 deletions(-) diff --git a/etc/schema.json b/etc/schema.json index b2b418b6ac..85e66bdf97 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -2459,29 +2459,37 @@ "type": "object", "additionalProperties": false, "properties": { - "alg_auth": { - "type": "string" - }, - "alg_auth_raw": { + "_v": { + "desription": "The version of the IKE log record (not IKE version)", "type": "integer" }, - "alg_dh": { - "type": "string" - }, - "alg_dh_raw": { - "type": "integer" - }, - "alg_enc": { - "type": "string" - }, - "alg_enc_raw": { - "type": "integer" - }, - "alg_hash": { - "type": "string" - }, - "alg_hash_raw": { - "type": "integer" + "attributes": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string", + "enum": [ + "alg_auth", + "alg_dh", + "alg_enc", + "alg_hash", + "sa_key_length", + "sa_life_duration", + "sa_life_type" + ] + }, + "raw": { + "type": ["string", "number"] + }, + "value": { + "type": "string" + } + } + } }, "exchange_type": { "type": "integer", @@ -2531,47 +2539,23 @@ "type": "object", "additionalProperties": false, "properties": { - "alg_auth": { - "type": "string" - }, - "alg_auth_raw": { - "type": "integer" - }, - "alg_dh": { - "type": "string" - }, - "alg_dh_raw": { - "type": "integer" - }, - "alg_enc": { - "type": "string" - }, - "alg_enc_raw": { - "type": "integer" - }, - "alg_hash": { - "type": "string" - }, - "alg_hash_raw": { - "type": "integer" - }, - "sa_key_length": { - "type": "string" - }, - "sa_key_length_raw": { - "type": "integer" - }, - "sa_life_duration": { - "type": "string" + "key": { + "type": "string", + "enum": [ + "alg_auth", + "alg_dh", + "alg_enc", + "alg_hash", + "sa_key_length", + "sa_life_duration", + "sa_life_type" + ] }, - "sa_life_duration_raw": { - "type": "integer" + "raw": { + "type": ["string", "number"] }, - "sa_life_type": { + "value": { "type": "string" - }, - "sa_life_type_raw": { - "type": "integer" } } } @@ -2642,24 +2626,6 @@ "role": { "type": "string" }, - "sa_key_length": { - "type": "string" - }, - "sa_key_length_raw": { - "type": "integer" - }, - "sa_life_duration": { - "type": "string" - }, - "sa_life_duration_raw": { - "type": "integer" - }, - "sa_life_type": { - "type": "string" - }, - "sa_life_type_raw": { - "type": "integer" - }, "version_major": { "type": "integer" }, diff --git a/rust/src/ike/logger.rs b/rust/src/ike/logger.rs index 8238501427..f8b4f3d896 100644 --- a/rust/src/ike/logger.rs +++ b/rust/src/ike/logger.rs @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Open Information Security Foundation +/* Copyright (C) 2020-2025 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free @@ -26,27 +26,30 @@ use std::convert::TryFrom; const LOG_EXTENDED: u32 = 0x01; +// IKE log format version. Version 2 is for 9.0 even if changes are +// made before 9.0 is final. +const IKE_LOG_VERSION: u8 = 2; + +/// Add IKE attributes as objects. +/// +/// This function does not open the array, it should be opened by the +/// caller. fn add_attributes(transform: &Vec, js: &mut JsonBuilder) -> Result<(), JsonError> { for attribute in transform { - js.set_string( - attribute.attribute_type.to_string().as_str(), - attribute.attribute_value.to_string().as_str(), - )?; - - if let Some(numeric_value) = attribute.numeric_value { - js.set_uint( - format!("{}_raw", attribute.attribute_type).as_str(), - numeric_value as u64, - )?; - } else if let Some(hex_value) = &attribute.hex_value { - js.set_string( - format!("{}_raw", attribute.attribute_type).as_str(), - hex_value, - )?; + js.start_object()?; + js.set_string("key", &attribute.attribute_type.to_string())?; + js.set_string("value", &attribute.attribute_value.to_string())?; + + if let Some(v) = attribute.numeric_value { + js.set_uint("raw", v as u64)?; + } else if let Some(v) = &attribute.hex_value { + js.set_string("raw", v)?; } + + js.close()?; } - return Ok(()); + Ok(()) } fn log_ike( @@ -54,6 +57,9 @@ fn log_ike( ) -> Result<(), JsonError> { jb.open_object("ike")?; + // The version of the IKE record format. + jb.set_uint("_v", IKE_LOG_VERSION)?; + jb.set_uint("version_major", tx.hdr.maj_ver as u64)?; jb.set_uint("version_minor", tx.hdr.min_ver as u64)?; jb.set_string("init_spi", &tx.hdr.spi_initiator)?; @@ -76,15 +82,15 @@ fn log_ike( if tx.ike_version == 1 { if state.ikev1_container.server.nb_transforms > 0 { // log the first transform as the chosen one + jb.open_array("attributes")?; add_attributes(&state.ikev1_container.server.transform, jb)?; + jb.close()?; } if tx.direction == Direction::ToClient && tx.hdr.ikev1_transforms.len() > 1 { // in case we have multiple server transforms log them in a list jb.open_array("server_proposals")?; for server_transform in &tx.hdr.ikev1_transforms { - jb.start_object()?; add_attributes(server_transform, jb)?; - jb.close()?; } jb.close()?; } @@ -160,9 +166,7 @@ fn log_ikev1(state: &IKEState, tx: &IKETransaction, jb: &mut JsonBuilder) -> Res if tx.direction == Direction::ToServer && !tx.hdr.ikev1_transforms.is_empty() { jb.open_array("proposals")?; for client_transform in &tx.hdr.ikev1_transforms { - jb.start_object()?; add_attributes(client_transform, jb)?; - jb.close()?; } jb.close()?; // proposals }