Skip to content

This guide shows you how to map events to Google SecOps Unified Data Model (UDM) records in TQL. You’ll learn how to choose a UDM event type, populate metadata, model participants as UDM nouns, convert enum values, and preserve unmapped source fields.

The TQL examples in this guide build API-facing UDM records, so they use lowerCamelCase ingestion object field names such as metadata.eventType and network.ipProtocol.

Install the tenzir-udm skill when you want an agent to help with UDM schema decisions. See Use agent skills for installation and usage examples.

Ask the agent to use ingestion object field names when it maps logs into UDM event or entity objects for Google SecOps UDM API ingestion. When generated UDM field headings show two forms, choose the right-side lowerCamelCase ingestion object form. For example, use metadata.eventType and network.ipProtocol in the TQL output. If a heading has one name, use that spelling.

Start by choosing metadata.eventType. This value describes the activity that the event records, not the product that emitted the log. For example, a firewall connection log maps to NETWORK_CONNECTION, while DNS payloads map to NETWORK_DNS.

For a NETWORK_CONNECTION event, UDM expects these core sections:

  • metadata: The event timestamp and product context.
  • principal: The machine that initiated the network connection.
  • target: The destination machine if it differs from the principal.
  • network: The protocol, ports, byte counts, and other connection details.

The following example maps a parsed firewall connection event to UDM. It keeps the source data under fw, moves one-use fields into UDM, converts source strings to UDM enum values, and preserves unmapped residue in additional.

from {
ts: 2024-01-15T10:30:45Z,
action: "allowed",
src_ip: "10.0.0.5",
src_port: 51544,
dst_ip: "203.0.113.10",
dst_port: 443,
proto: "tcp",
bytes_out: 1280,
bytes_in: 8192,
}
this = {fw: this}
let $ip_protocols = {
tcp: "TCP",
udp: "UDP",
icmp: "ICMP",
}
let $actions = {
allow: "ALLOW",
allowed: "ALLOW",
deny: "BLOCK",
denied: "BLOCK",
block: "BLOCK",
blocked: "BLOCK",
}
udm.metadata = {
eventTimestamp: move fw.ts,
eventType: "NETWORK_CONNECTION",
vendorName: "Example Networks",
productName: "Example Firewall",
productEventType: fw.action,
}
udm.principal = {
ip: [move fw.src_ip],
port: move fw.src_port,
}
udm.target = {
ip: [move fw.dst_ip],
port: move fw.dst_port,
}
udm.network = {
ipProtocol: $ip_protocols[(move fw.proto).to_lower()]? else "UNKNOWN_IP_PROTOCOL",
sentBytes: move fw.bytes_out,
receivedBytes: move fw.bytes_in,
}
udm.securityResult = [{
action: [$actions[fw.action.to_lower()]? else "UNKNOWN_ACTION"],
actionDetails: move fw.action,
}]
this = {...udm, additional: fw}
{
metadata: {
eventTimestamp: 2024-01-15T10:30:45Z,
eventType: "NETWORK_CONNECTION",
vendorName: "Example Networks",
productName: "Example Firewall",
productEventType: "allowed",
},
principal: {
ip: [
"10.0.0.5",
],
port: 51544,
},
target: {
ip: [
"203.0.113.10",
],
port: 443,
},
network: {
ipProtocol: "TCP",
sentBytes: 1280,
receivedBytes: 8192,
},
securityResult: [
{
action: [
"ALLOW",
],
actionDetails: "allowed",
},
],
additional: {},
}

Use the same structure for larger mappings:

  • Keep a source namespace: Move the parsed event under a short namespace such as fw, dns, edr, or event before you create UDM fields.
  • Set metadata.eventType early: Let the UDM event type drive which participant nouns and protocol fields you populate.
  • Model participants as nouns: Use principal for the initiating entity, target for the destination, observer for the sensor, and intermediary for proxies or middleboxes.
  • Convert enum values explicitly: Map source strings to UDM enum names such as TCP, UDP, ALLOW, or BLOCK.
  • Preserve unmapped fields: Keep source fields that don’t have a deliberate UDM target under additional so reviewers can audit the mapping.

Last updated: