Skip to main content

I'm trying to extract ip and hostname from a nested json. There are multiple ips and hostnames depending on the alert category. How can we extract and assign all the IPs and hostnames  it to target.ip  and target.hostname?

{
"target": {
    "total_count": 2,
    "data": [
      {
        "device": [
          {
            "value": "10.10.10.10",
            "type": "ip"
          },
          {
            "value": "abc",
            "type": "hostname"
          }
        ],
        "type": "endpoint",
      },
      {
        "device": [
          {
            "value": "11.11.11.11",
            "type": "ip"
          },
          {
            "value": "z44",
            "type": "hostname"
          }
        ],
    "type": "endpoint",
    }
  ],  
}

}

Parser syntax documentation doesn't have references for this type of nested json data where key value pairs are separated.

I tokenized the IPs/Hostnames separately. It is doable but a bit complex when the keys are double-repeated like this.


However target.hostname has a single value unlike the target.IP . Do you need all the 2 IPs/Hostnames to be in a single event so should I go ahead and map the 2 hostnames to another repeated field like target.labels ? or is it ok to split the log entry into multiple separate events ?


 


 


filter {
json {
source => "message"
array_function => "split_columns"
}

mutate {
replace => {
"ipDedupRegex" => " "
}
}


mutate {
replace => {
"hostDedupRegex" => " "
}
}



for k,v in target.data map {




for k2,v2 in v.device map {


if [v2][type] == "ip" {



if [v2][value] !~ ipDedupRegex {



if k=="0" {

mutate {
replace => {
"ipDedupRegex" => "%{v2.value}"
}
}




}

else {

mutate {
replace => {
"ipDedupRegex" => "%{ipDedupRegex}|%{v2.value}"
}
}

}


mutate {
merge => {
"ipList_" => "v2.value"
}
}

}




statedump {}



}

#if [v2][type] == "hostname" {
#mutate {
# replace => {
# "hostname_" => "%{v2.value}"
# }
#}
#}

if [v2][type] == "hostname" {



if [v2][value] !~ ipDedupRegex {



if k=="0" {

mutate {
replace => {
"hostDedupRegex" => "%{v2.value}"
}
}




}

else {

mutate {
replace => {
"hostDedupRegex" => "%{hostDedupRegex}|%{v2.value}"
}
}

}


mutate {
merge => {
"hostList_" => "v2.value"
}
}

}




statedump {}



}



}
}
}

 


@AbdElHafez could you just use indexing for the IP's or hostnames like


target.ip[0]


target.ip[1]


 


I tokenized the IPs/Hostnames separately. It is doable but a bit complex when the keys are double-repeated like this.


However target.hostname has a single value unlike the target.IP . Do you need all the 2 IPs/Hostnames to be in a single event so should I go ahead and map the 2 hostnames to another repeated field like target.labels ? or is it ok to split the log entry into multiple separate events ?


 


 


filter {
json {
source => "message"
array_function => "split_columns"
}

mutate {
replace => {
"ipDedupRegex" => " "
}
}


mutate {
replace => {
"hostDedupRegex" => " "
}
}



for k,v in target.data map {




for k2,v2 in v.device map {


if [v2][type] == "ip" {



if [v2][value] !~ ipDedupRegex {



if k=="0" {

mutate {
replace => {
"ipDedupRegex" => "%{v2.value}"
}
}




}

else {

mutate {
replace => {
"ipDedupRegex" => "%{ipDedupRegex}|%{v2.value}"
}
}

}


mutate {
merge => {
"ipList_" => "v2.value"
}
}

}




statedump {}



}

#if [v2][type] == "hostname" {
#mutate {
# replace => {
# "hostname_" => "%{v2.value}"
# }
#}
#}

if [v2][type] == "hostname" {



if [v2][value] !~ ipDedupRegex {



if k=="0" {

mutate {
replace => {
"hostDedupRegex" => "%{v2.value}"
}
}




}

else {

mutate {
replace => {
"hostDedupRegex" => "%{hostDedupRegex}|%{v2.value}"
}
}

}


mutate {
merge => {
"hostList_" => "v2.value"
}
}

}




statedump {}



}



}
}
}

 


i.e. This mapping below is more consistent, however looking at the type of logs you are trying to ingest I feel it would fit more in the asset context logs not the event ones ;


 


 


filter {
json { source => "message" array_function => "split_columns"}
mutate {convert => {"target.total_count" => "string"}}
mutate {replace => {"count" => "%{target.total_count}"}}
for k,v in target.data map {
mutate {replace => {"event1" => ""}}
mutate {replace => {"event1.idm.read_only_udm.metadata.event_type" => "GENERIC_EVENT"}}

for k2,v2 in v.device map {
if [v2][type] == "ip" {
mutate {replace => {"ip" => "%{v2.value}"}}
mutate {merge => {"event1.idm.read_only_udm.target.ip" => "ip"}}
}
if [v2][type] == "hostname"{
mutate {replace => {"host" => "%{v2.value}"}}
mutate {rename => {"v2.value" => "event1.idm.read_only_udm.target.hostname"}}
}
}


mutate {
merge => {
"@output" => "event1"
}
}

statedump {label=>"outerLoop"}


}
statedump {}
}

 



 


 


@AbdElHafez could you just use indexing for the IP's or hostnames like


target.ip[0]


target.ip[1]


 


I know but the hostname is unique so it will have to go in the labels section eventually.


I think the 2nd version I added just now is the better version if the data is not ingested within an asset context.


Hi @AbdElHafez Many Thanks for the detailed post. We are getting alerts from a external system and this particular part in json contains the ip/hostname of the machines which are affected. It can range from 1 to 30+. So, we thought to map it to "target" namespace. 

When I tested with the config you have provided, it's extracting the same way as you provided in your screenshot. Since it is having timestamp assigned for each extraction. When I validate the parser, it is giving 1000+ events against ingested events of 100 due to individual time stamp assignment. 

So, I simplifed like below, 
 

for k,v in target.data map {
for k1,v2 in v.device map {
if [v2][type] == "ip" {
mutate {replace => {"ip" => "%{v2.value}"}}
mutate {merge => {"event1.idm.read_only_udm.target.ip" => "ip"}}
}
if [v2][type] == "hostname"{
mutate {replace => {"host" => "%{v2.value}"}}
mutate {rename => {"v2.value" => "event1.idm.read_only_udm.target.hostname"}}
}
}
}

It's extracting the IP's with target.ip[0]/target.ip[1] and so on but the hostname is getting failed to parse.




Hi @AbdElHafez Many Thanks for the detailed post. We are getting alerts from a external system and this particular part in json contains the ip/hostname of the machines which are affected. It can range from 1 to 30+. So, we thought to map it to "target" namespace. 

When I tested with the config you have provided, it's extracting the same way as you provided in your screenshot. Since it is having timestamp assigned for each extraction. When I validate the parser, it is giving 1000+ events against ingested events of 100 due to individual time stamp assignment. 

So, I simplifed like below, 
 

for k,v in target.data map {
for k1,v2 in v.device map {
if [v2][type] == "ip" {
mutate {replace => {"ip" => "%{v2.value}"}}
mutate {merge => {"event1.idm.read_only_udm.target.ip" => "ip"}}
}
if [v2][type] == "hostname"{
mutate {replace => {"host" => "%{v2.value}"}}
mutate {rename => {"v2.value" => "event1.idm.read_only_udm.target.hostname"}}
}
}
}

It's extracting the IP's with target.ip[0]/target.ip[1] and so on but the hostname is getting failed to parse.




That is because target.hostname is a unique field unlike target.ip which is repeated that is why I changed the parser to generate an event per host.


ou would need to pick a different field for the hostnames like in the about.labels for example or something similar to parse them. If you could pick which fields to be used for the hostnames then I could change the mapping back to 1 event per entry.


Using about.labels would be a correct call I guess @AbdElHafez 


Using about.labels would be a correct call I guess @AbdElHafez 


Hi @Aswin_Asokan,
Note that 'labels fields for UDM nouns' (which includes 'about.labels') was deprecated on November 29, 2024[1]. It is recommended to use 'additional.fields'.

[1] - https://cloud.google.com/chronicle/docs/deprecations

Kind Regards,

Ayman


Using about.labels would be a correct call I guess @AbdElHafez 


@Aswin_Asokan Could you give some more context about the type of the datasource and use case for the events ? As @AymanC indicated true it seems the about labels may not be a sustainable choice in the future. But I am trying to figure out if these logs are better off as asset context that could be used for enriching other events, or  UDM events ? and figure out a proper mapping for the hostnames field.


@AbdElHafez We are looking at the XDR incidents

https://conure.us.security.cisco.com/index.html#/v2/get_v2_incident__incident_id__summary 


@AbdElHafez We are looking at the XDR incidents

https://conure.us.security.cisco.com/index.html#/v2/get_v2_incident__incident_id__summary 


@Aswin_Asokan In that case the security_result field is probably more suitable.


Please try this version ;


 


 


filter {
json { source => "message" array_function => "split_columns" }

mutate {replace => {"ipDedupRegex" => " "}}
mutate {replace => {"hostDedupRegex" => " "}}
mutate {replace => {"event1.idm.read_only_udm.metadata.event_type" => "GENERIC_EVENT"}}
mutate {replace => {"event1.idm.read_only_udm.metadata.vendor_name" => "Cisco"}}
mutate {replace => {"event1.idm.read_only_udm.metadata.product_name" => "XDR"}}

for k,v in target.data map {
for k2,v2 in v.device map {
if [v2][type] == "ip" {
if [v2][value] !~ ipDedupRegex {
if k=="0" {
mutate {replace => {"ipDedupRegex" => "%{v2.value}"}}
}
else {
mutate {replace => {"ipDedupRegex" => "%{ipDedupRegex}|%{v2.value}"}}
}
mutate {merge => {"ipList_" => "v2.value"}}
}

mutate {replace => {"ip_" => "%{v2.value}"}}
mutate {merge => {"security_result_.about.ip" => "ip_"}}
#mutate {rename => {"ip_list" => "zz"}}
#statedump {}
}
else if [v2][type] == "hostname" {
if [v2][value] !~ hostDedupRegex {
if k=="0" {
mutate {replace => {"hostDedupRegex" => "%{v2.value}"}}
}
else {
mutate {replace => {"hostDedupRegex" => "%{hostDedupRegex}|%{v2.value}"}}
}
mutate {merge => {"hostList_" => "v2.value"}}
}
mutate {replace => {"security_result_.about.hostname" => "%{v2.value}"}}
#mutate {rename => {"host_" => "about_.hostname"}}
#statedump {}
}

#mutate {rename => {"about" => "x.about"}}
#mutate {merge => {"about_" => "about"}}
#mutate {rename => {"about_" => "x.about_"}}
}
#mutate {rename => {"about" => "x"}}
mutate {merge => {"event1.idm.read_only_udm.security_result" => "security_result_"}}
#mutate {rename => {"security_result" => "event1.idm.read_only_udm.security_result"}}
mutate {replace => {"security_result_" => ""}}
#mutate {replace => {"about_" => "about"}}

}
#mutate {merge => {"about_" => "about"}}
statedump {}
mutate {
merge => {
"@output" => "event1"
}
}

#SecurityResult
}

 


 


 



 


I left some unused tokens "hostList_" and "ipList_" in case you needed them separate.


Reply