Transformations

Mutate data structure upon ingest

Overview

Transformations are functions you can use in custom log source schemas to modify the shape of your data upon ingest into Panther. The data will then be stored in the new format.

Transformations help align stored data to the needs of detection and query logic, removing the need for ad-hoc data manipulation and expediting detection writing and search.

The following transformations are available:

Order of transformation execution

There is a specific order in which transformations are performed, which ensures that the transformations are applied one after another in a predictable manner. The order of execution is the sequence provided in the transformation list in the Overview, above.

Follow the defined order to accurately transform data. Each transformation in the sequence operates on the data in the state it was left after the previous transformation. Knowing this order maintains consistency and avoids unexpected results.

Combining transformations

Individual transformations can be combined in pairs or sequences to achieve more complex data transformations. This allows for greater flexibility and customization to meet specific data requirements and facilitate efficient detection creation and search operations.

Suppose there is a field that contains personal identification numbers (PINs). For security purposes, you want to rename the field to something less revealing while also applying a mask to redact the PINs.

To achieve this, you can use the rename transformation to change the field's name to something abstract. For instance, you could rename the field to userId.

Next, apply the mask transformation to the userId field to replace the digits of the PIN with a predefined number of asterisks. This way, the PIN remains hidden, ensuring data privacy.

You can define a field schema with both a rename and a mask directive like this:

- name: userId
  type: string
  rename:
    from: PIN
  mask:
    type: redact
    to: "*****"

You will achieve to transform a payload like this:

{
  "PIN": "1234"
}

To this:

{
  "userId": "*****"
}

rename

The rename transformation changes the name of a field. This can be useful if you want to standardize field names across data sources, improve the clarity of your data's structure, or adjust field names containing invalid characters or reserved keywords.

By defining a field schema with a rename directive, such as:

- name: user
  type: string
  rename: 
    from: "@user"
- name: role
  type: object
  fields:
   - name: level
     type: string
     rename:
       from: type

You will transform a payload like this:

{
  "@user": "john"
  "role": {
    "type": "admin"
  }
}

To this:

{
  "user": "john"
  "role": {
    "level": "admin"
  }
}

copy

The copy transformation copies the value of a nested field into another top-level field. This can be useful if you'd like to flatten your data's JSON structure. If desired, you can then mark your newly defined field as an indicator.

By defining a field schema with a copy directive, such as:

- name: message
  type: string
  copy:
    from: attributes.message
- name: attributes
  type: json

You will transform a payload like this:

{
  "attributes": {
      "message": "hello there", 
      "user": "someone"
  }
}

To this:

{
  "message": "hello there",
  "attributes": {
      "message": "hello there", 
      "user": "someone"
  }
}

concat

The concat transformation allows you to concatenate multiple fields' values into the value of a new field. The resulting combined field can be used, for example, as a key for enrichment.

Fields whose type is timestamp cannot be used in concatenation operations.

To use concat, declare a string field to store the result of the concatenation. Within concat, define the paths, and optionally a separator. Within paths, you must use absolute paths to specify the existing schema fields you'd like to combine. The order of these fields determines the concatenation order. If separator is not defined, the default separator is an empty string ("").

By defining a field schema with a concat directive, such as:

- name: ip
  type: string
- name: ports
  type: object
  fields:
   - name: https
     type: int
- name: socket
  type: string
  concat:
   separator: ":"
   paths: 
    - ip
    - ports.https

You will transform a payload like this:

{
  "ip": "192.168.0.1"
  "ports": {
    "https": 443
  }
}

To this:

{
  "ip": "192.168.0.1"
  "ports": {
    "https": 443
  },
  "socket": "192.168.0.1:443"
}

split

The split transformation allows you to extract a specific value from a string field by splitting it based on a separator. The resulting split fields can be treated as individual schema fields, making it possible to designate them as indicators. Split transformation can also help with data normalization into standardized fields, making it easier to handle unstructured data formats.

Only fields with a type of string can be split into other fields (i.e., the value of split:from: must be a field that contains type: string).

To use split, declare a field of any primitive type (i.e., excluding object, array, and JSON) to store the result. Within the split directive, include the following required fields:

  • from: Provide the absolute path of the field to be divided.

  • separator: Provide the character to split on.

  • index: Provide the position of the value within the resulting array produced by the split.

By defining a field schema with a split directive, such as:

- name: socket
  type: string
- name: ip
  type: string
  split:
   from: socket
   separator: ":"
   index: 0
- name: port
  type: int
  split:
   from: socket
   separator: ":"
   index: 1  

You will transform a payload like this:

{
  "socket": "192.168.0.1:443"
}

To this:

{
  "socket": "192.168.0.1:443",
  "ip": "192.168.0.1",
  "port": 443
}

You can also use split to split array elements. For example, using the following schema:

- name: traffic
  type: array
  element: 
    type: object
    fields:
      - name: socket
        type: string
      - name: ip
        type: string
        indicators: [ip]
        split: 
          from: traffic.socket
          separator: ":"
          index: 0
      - name: port
        type: int
        split: 
          from: traffic.socket
          separator: ":"
          index: 1
       

You will transform a payload like this:

{
  "traffic": [
   {
     "socket": "192.168.0.1:443"
   },
   {
     "socket": "192.168.0.2:80"
   } 
  ]
}

To this:

{
  "traffic": [
   {
     "socket": "192.168.0.1:443",
     "ip": "192.168.0.1",
     "port": 443
   },
   {
     "socket": "192.168.0.2:80",
     "ip": "192.168.0.2",
     "port": 80
   } 
  ]
}

mask

The mask transformation enables you to conceal sensitive information in your logs. Masking is useful if you need to protect the confidentiality of certain data.

There are two masking techniques:

  • Obfuscation (also known as hashing): This technique hashes data, using an optional salt value. With this technique, the value keeps its referential integrity.

  • Redaction: This technique replaces sensitive values with REDACTED, or some other string you provide. With this technique, the value loses its referential integrity.

Note that masking a certain field means you cannot later use Panther's search tools to query for its original value, but you can search for a hashed value.

Obfuscation (hashing)

Hashing incoming data means you can enhance its security while still retaining its usability in the future. To strengthen the protection hashing provides, you can include a salt.

To use obfuscation, on the target field in your schema, include mask. Under mask, include type, and optionally salt.

The value of type is the hashing algorithm you want to use. Supported values include:

  • sha256

  • md5

  • sha1

  • sha512

The value of the optional salt key is a string of your choice. This value is appended to the field's value before it is hashed.

By defining a field schema with a mask directive such as:

- name: username
  type: string
  mask:
    type: sha256 
    salt: random_salt # optional

You will transform a payload like this:

{
  "username": "john"
}

To this:

{
  "username": "98b4ceb956e9ed4539b0721add25cab0bacce4307cf3140c4430c1513476a3e4"
}

Redaction

Redacting incoming data means replacing it with a predefined value. This technique is useful if you'd like to ensure the sensitive information is not accessible or recoverable.

To use redaction, on the target field in your schema, include mask. Under mask, include type: redact, and optionally to.

The optional to key takes a string value that will replace the actual event value. If to is not included, its default, REDACTED, is used.

By defining a field schema with a mask directive such as:

- name: username
  type: string
  mask:
    type: redact 
    to: "XXXX" # optional, default: "REDACTED"

You will transform a payload like this:

{
  "username": "john"
}

To this:

{
  "username": "XXXX"
}

isEmbeddedJSON

Sometimes JSON values are delivered embedded in a string.

To have Panther parse the escaped JSON inside the string, use an isEmbeddedJSON: true flag. This flag is valid for values of type object, array and json.

By defining a field schema with a isEmbeddedJSON directive such as:

- name: message
  type: object
  isEmbeddedJSON: true
  fields:
    - name: foo
      type: string

You will transform a payload like this:

{
  "timestamp": "2021-03-24T18:15:23Z",
  "message": "{\"foo\":\"bar\"}"
}

To this:

{
  "timestamp": "2021-03-24T18:15:23Z",
  "message": {
    "foo": "bar"
  }
}

Last updated