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
pull/13975/head
Jason Ish 4 weeks ago committed by Victor Julien
parent b543e28402
commit 2d86412f46

@ -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"
},

@ -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<SaAttribute>, 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
}

Loading…
Cancel
Save