rust/ldap: implement logger

pull/11515/head
Giuseppe Longo 11 months ago committed by Victor Julien
parent 93da339975
commit 910a5b226c

@ -2376,6 +2376,366 @@
},
"additionalProperties": false
},
"ldap": {
"type": "object",
"optional": true,
"properties": {
"request": {
"type": "object",
"properties": {
"operation": {
"type": "string"
},
"message_id": {
"type": "integer"
},
"search_request": {
"type": "object",
"optional": "true",
"properties": {
"base_object": {
"type": "string"
},
"scope": {
"type": "integer"
},
"deref_alias": {
"type": "integer"
},
"size_limit": {
"type": "integer"
},
"time_limit": {
"type": "integer"
},
"types_online": {
"type": "boolean"
},
"attributes": {
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
}
}
},
"bind_request": {
"type": "object",
"optional": "true",
"properties": {
"version": {
"type": "integer"
},
"name": {
"type": "string"
},
"sasl": {
"type": "object",
"optional": "true",
"properties": {
"mechanism": {
"type": "string"
},
"credentials": {
"type": "string",
"optional": "true"
}
}
}
}
},
"modify_request": {
"type": "object",
"optional": "true",
"properties": {
"object": {
"type": "string"
},
"changes": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"operation": {
"type": "string"
},
"modification": {
"type": "object",
"properties": {
"attribute_type": {
"type": "string"
},
"attribute_values": {
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
}
}
}
}
}
}
}
},
"add_request": {
"type": "object",
"optional": "true",
"properties": {
"entry": {
"type": "string"
},
"attributes": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"values": {
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
}
}
}
}
}
},
"del_request": {
"type": "object",
"optional": "true",
"properties": {
"dn": {
"type": "string"
}
}
},
"mod_dn_request": {
"type": "object",
"optional": "true",
"properties": {
"entry": {
"type": "string"
},
"new_rdn": {
"type": "string"
},
"delete_old_rdn": {
"type": "boolean"
},
"new_superior": {
"type": "string",
"optional": "true"
}
}
},
"compare_request": {
"type": "object",
"optional": "true",
"properties": {
"entry": {
"type": "string"
},
"attribute_value_assertion": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"value": {
"type": "string"
}
}
}
}
},
"abandon_request": {
"type": "object",
"optional": "true",
"properties": {
"message_id": {
"type": "integer"
}
}
},
"extended_request": {
"type": "object",
"optional": "true",
"properties": {
"name": {
"type": "string"
},
"value": {
"type": "string",
"optional": "true"
}
}
}
},
"additionalProperties": false
},
"responses": {
"type": "array",
"optional": "true",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"search_result_done": {
"type": "object",
"optional": "true",
"properties": {
"result_code": {
"type": "string"
},
"matched_dn": {
"type": "string"
},
"message": {
"type": "string"
}
}
},
"bind_response": {
"type": "object",
"optional": "true",
"properties": {
"result_code": {
"type": "string"
},
"matched_dn": {
"type": "string"
},
"message": {
"type": "string"
},
"server_sasl_creds": {
"type": "string",
"optional": "true"
}
}
},
"modify_response": {
"type": "object",
"optional": "true",
"properties": {
"result_code": {
"type": "string"
},
"matched_dn": {
"type": "string"
},
"message": {
"type": "string"
}
}
},
"add_response": {
"type": "object",
"optional": "true",
"properties": {
"result_code": {
"type": "string"
},
"matched_dn": {
"type": "string"
},
"message": {
"type": "string"
}
}
},
"del_response": {
"type": "object",
"optional": "true",
"properties": {
"result_code": {
"type": "string"
},
"matched_dn": {
"type": "string"
},
"message": {
"type": "string"
}
}
},
"mod_dn_response": {
"type": "object",
"optional": "true",
"properties": {
"result_code": {
"type": "string"
},
"matched_dn": {
"type": "string"
},
"message": {
"type": "string"
}
}
},
"compare_response": {
"type": "object",
"optional": "true",
"properties": {
"result_code": {
"type": "string"
},
"matched_dn": {
"type": "string"
},
"message": {
"type": "string"
}
}
},
"extended_response": {
"type": "object",
"optional": "true",
"properties": {
"result_code": {
"type": "string"
},
"matched_dn": {
"type": "string"
},
"message": {
"type": "string"
},
"name": {
"type": "string"
},
"value": {
"type": "string"
}
}
},
"intermediate_response": {
"type": "object",
"optional": "true",
"properties": {
"name": {
"type": "string"
},
"value": {
"type": "string"
}
}
}
}
}
}
}
},
"metadata": {
"type": "object",
"optional": true,
@ -4277,6 +4637,10 @@
"Errors encountered parsing Kerberos v5/UDP protocol",
"$ref": "#/$defs/stats_applayer_error"
},
"ldap": {
"description": "Errors encountered parsing LDAP protocol",
"$ref": "#/$defs/stats_applayer_error"
},
"modbus": {
"description": "Errors encountered parsing Modbus protocol",
"$ref": "#/$defs/stats_applayer_error"
@ -4441,6 +4805,10 @@
"description": "Number of flows for Kerberos v5/UDP protocol",
"type": "integer"
},
"ldap": {
"description": "Number of flows for LDAP protocol",
"type": "integer"
},
"modbus": {
"description": "Number of flows for Modbus protocol",
"type": "integer"
@ -4600,6 +4968,10 @@
"Number of transactions for Kerberos v5/UDP protocol",
"type": "integer"
},
"ldap": {
"description": "Number of transactions for LDAP protocol",
"type": "integer"
},
"modbus": {
"description": "Number of transactions for Modbus protocol",
"type": "integer"

@ -422,6 +422,7 @@ pub unsafe extern "C" fn rs_ldap_register_parser() {
SCLogError!("Invalid value for ldap.max-tx");
}
}
AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_LDAP);
} else {
SCLogDebug!("Protocol detection and parser disabled for LDAP.");
}

@ -0,0 +1,351 @@
/* Copyright (C) 2024 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
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
// written by Giuseppe Longo <giuseppe@glongo.it>
use crate::jsonbuilder::{JsonBuilder, JsonError};
use crate::ldap::filters::*;
use crate::ldap::ldap::LdapTransaction;
use crate::ldap::types::*;
fn log_ldap(tx: &LdapTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("ldap")?;
if let Some(req) = &tx.request {
let protocol_op_str = req.protocol_op.to_string();
js.open_object("request")?;
js.set_uint("message_id", req.message_id.0.into())?;
js.set_string("operation", &protocol_op_str)?;
match &req.protocol_op {
ProtocolOp::SearchRequest(msg) => log_search_request(msg, js)?,
ProtocolOp::BindRequest(msg) => log_bind_request(msg, js)?,
ProtocolOp::UnbindRequest => (),
ProtocolOp::ModifyRequest(msg) => log_modify_request(msg, js)?,
ProtocolOp::AddRequest(msg) => log_add_request(msg, js)?,
ProtocolOp::DelRequest(msg) => log_del_request(msg, js)?,
ProtocolOp::ModDnRequest(msg) => log_mod_dn_request(msg, js)?,
ProtocolOp::CompareRequest(msg) => log_compare_request(msg, js)?,
ProtocolOp::ExtendedRequest(msg) => log_extended_request(msg, js)?,
_ => {}
};
log_controls(&req.controls, js)?;
js.close()?;
}
if !tx.responses.is_empty() {
js.open_array("responses")?;
for response in &tx.responses {
js.start_object()?;
let protocol_op_str = response.protocol_op.to_string();
js.set_string("operation", &protocol_op_str)?;
if tx.request.is_none() {
js.set_uint("message_id", response.message_id.0.into())?;
}
match &response.protocol_op {
ProtocolOp::SearchResultEntry(msg) => log_search_result_entry(msg, js)?,
ProtocolOp::SearchResultDone(msg) => log_search_result_done(msg, js)?,
ProtocolOp::BindResponse(msg) => log_bind_response(msg, js)?,
ProtocolOp::ModifyResponse(msg) => log_modify_response(msg, js)?,
ProtocolOp::AddResponse(msg) => log_add_response(msg, js)?,
ProtocolOp::DelResponse(msg) => log_del_response(msg, js)?,
ProtocolOp::ModDnResponse(msg) => log_mod_dn_response(msg, js)?,
ProtocolOp::CompareResponse(msg) => log_compare_response(msg, js)?,
ProtocolOp::ExtendedResponse(msg) => log_extended_response(msg, js)?,
ProtocolOp::IntermediateResponse(msg) => log_intermediate_response(msg, js)?,
_ => {}
}
log_controls(&response.controls, js)?;
js.close()?;
}
js.close()?;
}
js.close()?;
Ok(())
}
fn log_search_request(msg: &SearchRequest, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("search_request")?;
js.set_string("base_object", &msg.base_object.0)?;
js.set_uint("scope", msg.scope.0.into())?;
js.set_uint("deref_alias", msg.deref_aliases.0.into())?;
js.set_uint("size_limit", msg.size_limit.into())?;
js.set_uint("time_limit", msg.time_limit.into())?;
js.set_bool("types_only", msg.types_only)?;
if let Filter::Present(val) = &msg.filter {
js.open_object("filter")?;
js.set_string("type", "present")?;
js.set_string("value", &val.0.to_string())?;
js.close()?;
}
if !msg.attributes.is_empty() {
js.open_array("attributes")?;
for attr in &msg.attributes {
js.append_string(&attr.0)?;
}
js.close()?;
}
js.close()?;
Ok(())
}
fn log_bind_request(msg: &BindRequest, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("bind_request")?;
js.set_uint("version", msg.version.into())?;
js.set_string("name", &msg.name.0)?;
if let AuthenticationChoice::Sasl(sasl) = &msg.authentication {
js.open_object("sasl")?;
js.set_string("mechanism", &sasl.mechanism.0)?;
if let Some(credentials) = &sasl.credentials {
js.set_hex("credentials", credentials)?;
}
js.close()?;
}
js.close()?;
Ok(())
}
fn log_modify_request(msg: &ModifyRequest, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("modify_request")?;
js.set_string("object", &msg.object.0)?;
if !msg.changes.is_empty() {
js.open_array("changes")?;
for change in &msg.changes {
js.start_object()?;
js.set_string("operation", &change.operation.to_string())?;
js.open_object("modification")?;
js.set_string("attribute_type", &change.modification.attr_type.0)?;
if !change.modification.attr_vals.is_empty() {
js.open_array("attribute_values")?;
for attr in &change.modification.attr_vals {
js.append_string_from_bytes(&attr.0[..])?;
}
js.close()?;
}
js.close()?;
js.close()?;
}
js.close()?;
}
js.close()?;
Ok(())
}
fn log_add_request(msg: &AddRequest, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("add_request")?;
js.set_string("entry", &msg.entry.0)?;
if !msg.attributes.is_empty() {
js.open_array("attributes")?;
for attr in &msg.attributes {
js.start_object()?;
js.set_string("name", &attr.attr_type.0)?;
if !attr.attr_vals.is_empty() {
js.open_array("values")?;
for val in &attr.attr_vals {
js.append_string_from_bytes(&val.0)?;
}
js.close()?;
}
js.close()?;
}
js.close()?;
}
js.close()?;
Ok(())
}
fn log_del_request(msg: &LdapDN, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("del_request")?;
js.set_string("dn", &msg.0)?;
js.close()?;
Ok(())
}
fn log_mod_dn_request(msg: &ModDnRequest, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("mod_dn_request")?;
js.set_string("entry", &msg.entry.0)?;
js.set_string("new_rdn", &msg.newrdn.0)?;
js.set_bool("delete_old_rdn", msg.deleteoldrdn)?;
if let Some(newsuperior) = &msg.newsuperior {
js.set_string("new_superior", &newsuperior.0)?;
}
js.close()?;
Ok(())
}
fn log_compare_request(msg: &CompareRequest, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("compare_request")?;
js.set_string("entry", &msg.entry.0)?;
js.open_object("attribute_value_assertion")?;
js.set_string("description", &msg.ava.attribute_desc.0)?;
js.set_string_from_bytes("value", &msg.ava.assertion_value[..])?;
js.close()?;
js.close()?;
Ok(())
}
fn log_extended_request(msg: &ExtendedRequest, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("extended_request")?;
js.set_string("name", &msg.request_name.0)?;
if let Some(value) = &msg.request_value {
js.set_string_from_bytes("value", &value[..])?;
}
js.close()?;
Ok(())
}
fn log_search_result_entry(msg: &SearchResultEntry, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("search_result_entry")?;
js.set_string("base_object", &msg.object_name.0)?;
if !msg.attributes.is_empty() {
js.open_array("attributes")?;
for attr in &msg.attributes {
js.start_object()?;
js.set_string("type", &attr.attr_type.0)?;
if !attr.attr_vals.is_empty() {
js.open_array("values")?;
for val in &attr.attr_vals {
js.append_string_from_bytes(&val.0)?;
}
js.close()?;
}
js.close()?;
}
js.close()?;
}
js.close()?;
Ok(())
}
fn log_search_result_done(msg: &LdapResult, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("search_result_done")?;
log_ldap_result(msg, js)?;
js.close()?;
Ok(())
}
fn log_bind_response(msg: &BindResponse, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("bind_response")?;
log_ldap_result(&msg.result, js)?;
if let Some(creds) = &msg.server_sasl_creds {
js.set_hex("server_sasl_creds", creds)?;
};
js.close()?;
Ok(())
}
fn log_modify_response(msg: &ModifyResponse, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("modify_response")?;
log_ldap_result(&msg.result, js)?;
js.close()?;
Ok(())
}
fn log_add_response(msg: &LdapResult, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("add_response")?;
log_ldap_result(msg, js)?;
js.close()?;
Ok(())
}
fn log_del_response(msg: &LdapResult, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("del_response")?;
log_ldap_result(msg, js)?;
js.close()?;
Ok(())
}
fn log_mod_dn_response(msg: &LdapResult, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("mod_dn_response")?;
log_ldap_result(msg, js)?;
js.close()?;
Ok(())
}
fn log_compare_response(msg: &LdapResult, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("compare_response")?;
log_ldap_result(msg, js)?;
js.close()?;
Ok(())
}
fn log_extended_response(msg: &ExtendedResponse, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("extended_response")?;
log_ldap_result(&msg.result, js)?;
if let Some(name) = &msg.response_name {
js.set_string("name", &name.0)?;
}
if let Some(value) = &msg.response_value {
js.set_string_from_bytes("value", &value[..])?;
}
js.close()?;
Ok(())
}
fn log_intermediate_response(
msg: &IntermediateResponse, js: &mut JsonBuilder,
) -> Result<(), JsonError> {
js.open_object("intermediate_response")?;
if let Some(name) = &msg.response_name {
js.set_string("name", &name.0)?;
}
if let Some(value) = &msg.response_value {
js.set_string_from_bytes("value", &value[..])?;
}
js.close()?;
Ok(())
}
fn log_ldap_result(msg: &LdapResult, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.set_string("result_code", &msg.result_code.to_string())?;
js.set_string("matched_dn", &msg.matched_dn.0)?;
js.set_string("message", &msg.diagnostic_message.0)?;
Ok(())
}
fn log_controls(controls: &Option<Vec<Control>>, js: &mut JsonBuilder) -> Result<(), JsonError> {
if let Some(ctls) = controls {
js.open_array("controls")?;
for ctl in ctls {
js.start_object()?;
js.set_string("control_type", &ctl.control_type.0)?;
js.set_bool("criticality", ctl.criticality)?;
if let Some(ctl_val) = &ctl.control_value {
js.set_hex("control_value", ctl_val)?;
};
js.close()?;
}
js.close()?;
}
Ok(())
}
#[no_mangle]
pub unsafe extern "C" fn rs_ldap_logger_log(
tx: *mut std::os::raw::c_void, js: &mut JsonBuilder,
) -> bool {
let tx = cast_pointer!(tx, LdapTransaction);
log_ldap(tx, js).is_ok()
}

@ -19,4 +19,5 @@
pub mod filters;
pub mod ldap;
pub mod logger;
pub mod types;

@ -1092,6 +1092,10 @@ void OutputRegisterLoggers(void)
OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonEnipLog", "eve-log.enip",
OutputJsonLogInitSub, ALPROTO_ENIP, JsonGenericDirFlowLogger, JsonLogThreadInit,
JsonLogThreadDeinit, NULL);
/* Ldap JSON logger. */
OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonLdapLog", "eve-log.ldap",
OutputJsonLogInitSub, ALPROTO_LDAP, JsonGenericDirPacketLogger, JsonLogThreadInit,
JsonLogThreadDeinit, NULL);
/* Template JSON logger. */
OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonTemplateLog", "eve-log.template",
OutputJsonLogInitSub, ALPROTO_TEMPLATE, JsonGenericDirPacketLogger, JsonLogThreadInit,
@ -1147,6 +1151,7 @@ static EveJsonSimpleAppLayerLogger simple_json_applayer_loggers[ALPROTO_MAX] = {
{ ALPROTO_PGSQL, JsonPgsqlAddMetadata },
{ ALPROTO_TELNET, NULL }, // no logging
{ ALPROTO_WEBSOCKET, rs_websocket_logger_log },
{ ALPROTO_LDAP, rs_ldap_logger_log },
{ ALPROTO_TEMPLATE, rs_template_logger_log },
{ ALPROTO_RDP, (EveJsonSimpleTxLogFunc)rs_rdp_to_json },
{ ALPROTO_HTTP2, rs_http2_log_json },

@ -300,6 +300,7 @@ outputs:
- rfb
- sip
- quic
- ldap
- arp:
enabled: no # Many events can be logged. Disabled by default
- dhcp:

Loading…
Cancel
Save