Global Helper Functions

Overview

A common pattern in programming is to extract repeated code into helper functions—Panther supports this pattern with the global analysis type.

Global helpers are not best suited to frequent changes. Lookup Tables support automatic syncing with S3, which means they don't require code changes within Panther for updates.

Built-in globals

By default, Panther comes with built-in global helpers such as panther_default , panther_detection_helpers and panther_oss_helpers. panther_default is a default helper, panther_detection_helpers provides caching functions, and panther_oss_helpers provides boilerplate helpers to other common use cases. Learn more about the panther_detection_helpers package on Python Rules Caching.

While some globals require configuration, it is recommended to create a net-new global for any custom methods or logic that you would like to add. This reduces the chances of dealing with complex merge conflicts when updating your detection sources.

Using globals

Importing globals

Import global helpers in your detections by declared ID at the top of your analysis function body then call the global as if it were any other python library.

For example:

import panther_oss_helpers


def rule(event):
  return event['name'] == 'test-bucket'


def title(event):
  # Lookup the account name from an account Id
  account_name = panther_oss_helpers.lookup_aws_account_name(event['accountId'])
  return 'Suspicious request made to account ' + account_name

Adding new globals

New globals can be created from the Panther Analysis Tool or in your Panther Console.

To create a new global in the Panther Console:

  1. Log in to your Panther Console and navigate to Build > Helpers.

  2. Type your Python functions, then click Create. This global can now be imported in your rules or policies.

Removing references to helper functions from detections

If you decide to remove dependencies from your detections, we recommend staggering the changes.

Common helpers

deep_get()

deep_get() is also available as a function on the event object itself. For convenience, it's recommended to use that event function instead of this global helper function.

Located in panther_base_helpers.

deep_get() can be used to return keys that are nested within Python dictionaries. This function is useful for safely returning nested keys and avoiding an AttributeError when a key is not present.

If the key you are trying to access is nested inside a list, consider using deep_walk() instead.

Example

With the following JSON, the deep_get function would return the value of result.

{ "outcome": { "reason": "VERIFICATION_ERROR", "result": "FAILURE" }}
deep_get(event, "outcome", "result") == "FAILURE"

This can be found in the Geographically Improbable Okta Login detection.

default

deep_get() takes in an optional default parameter. If a key is not present at the expected location or the value at that location is None, the default value will be returned.

deep_get(event, "outcome", "nonexistent_key", default="Key Not Found") == "Key Not Found"

deep_walk()

deep_walk() is also available as a function on the event object itself. For convenience, it's recommended to use that event function instead of this global helper function.

Located in panther_base_helpers.

deep_walk() can be used to return values associated with keys that are deeply nested in Python dictionaries, which may contain any number of dictionaries or lists. This functionality is the key differentiator between deep_walk() and deep_get().

As with deep_get(), this traversal is safe and will avoid any exceptions or errors. In the event that a key is not present in the structure, the default value is returned.

Example

With the following object, deep_walk() would return the value of very_nested_key:

{"key": {"multiple_nested_lists_with_dict": [[[{"very_nested_key": "very_nested_value"}]]]}}
deep_walk(event, "key", "multiple_nested_lists_with_dict", "very_nested_key", default="") == "very_nested_value"

This can be found in the GCP Service Account Access Denied detection.

default

Like deep_get(), deep_walk() takes an optional default parameter. If a key is not present in the provided event, the key is None, or the key is an empty list, the default value is returned instead.

Using the above example:

deep_walk(event, "key", "multiple_nested_lists_with_dict", "very_nested_nonexistent_key", default="") == ""

return_val

Unlike deep_get(), deep_walk() can return three distinct value classifications:

  • all

  • first

  • last

all

By default, deep_walk() will return all values for a given key. This is useful for cases where a key is duplicated in an event; however, if the number of values returned by all is one, only that value is returned.

For example:

{"key": {"inner_key": [{"nested_key": "nested_value"}, {"nested_key": "nested_value2"}]}}
deep_walk(event, "key", "inner_key", "nested_key", default="") == ['nested_value', 'nested_value2']

When using all and returning multiple values, the elements in the list can be accessed like any other Python list.

first

To return only the first found value for a key, specify return_val="first".

For example:

deep_walk(event, "key", "inner_key", "nested_key", default="", return_val="first") == "nested_value"

last

To return only the last found value for a key, specify return_val="last".

For example:

deep_walk(event, "key", "inner_key", "nested_key", default="", return_val="last") == "nested_value2"

is_ip_in_network()

Located in panther_base_helpers.

is_ip_in_network() is a function to check if an IP address is within a list of IP ranges. This function can be used with a list of known internal networks for added context to the detection.

Example:

SHARED_IP_SPACE = [
    "192.168.0.0/16",
]

if is_ip_in_network(event.get("ipaddr"), SHARED_IP_SPACE):
    ...

An example can be found in the OneLogin Active Login Activity detection.

pattern_match()

Located in panther_base_helpers.

Wrapper around fnmatch for basic pattern globs. This can be used when simple pattern matching is needed without the requirement of using regex.

Example:

With the following JSON the pattern_match() function would return true.

{ "operation": "REST.PUT.OBJECT" }
pattern_match(event.get("operation", ""), "REST.*.OBJECT")

An example can be found in the AWS S3 Access Error detection.

pattern_match_list()

Located in panther_base_helpers.

Similar to pattern_match(), pattern_match_list() can check that a string matches any pattern in a given list.

Example:

With the following JSON the pattern_match_list() function would return true.

{ "userAgent": "aws-sdk-go/1.29.7 (go1.13.7; darwin; amd64) APN/1.0 HashiCorp/1.0 Terraform/0.12.24 (+https://www.terraform.io)" }
ALLOWED_USER_AGENTS = {
    "* HashiCorp/?.0 Terraform/*",
    # 'console.ec2.amazonaws.com',
    # 'cloudformation.amazonaws.com',
}

pattern_match_list(event.get("userAgent"), ALLOWED_USER_AGENTS)

An example can be found in the AWS EC2 Manual Security Group Change detection.

aws_strip_role_session_id()

Located in panther_base_helpers.

aws_strip_role_session_id() strips the session ID our of the arn.

Example:

With the following value, aws_strip_role_session_id() would return arn:aws:sts::123456789012:assumed-role/demo

{ "arn": "arn:aws:sts::123456789012:assumed-role/demo/sessionName" }
aws_strip_role_session_id(user_identity.get("arn", ""))

An example can be found in the AWS Unauthorized API Call detection.

is_base64()

Located in panther_base_helpers.

is_base64() checks if the string is base64 encoded, and if so, returns the decoded string. If not, it returns an empty string.

See an example in the Crowdstrike.Base64EncodedArgs detection.

get_string_set()

Located in panther_detection_helpers. Learn more about the panther_detection_helpers package on Python Rules Caching.

get_string_set is used to get a value from a Panther-managed cache based on its key. This is useful to retrieve state between detection invocations.

An example used in panther-analysis can be found here.

put_string_set()

Located in panther_detection_helpers. Learn more about the panther_detection_helpers package on Python Rules Caching.

put_string_set is used to store a value into a Panther-managed cache based on its key. This is useful to store state between detection invocations.

An example used in panther-analysis can be found here.

Last updated