PyPanther Detections
Configure detections fully in Python
Overview
PyPanther Detections are in closed beta starting with Panther version 1.108. Please share any bug reports and feature requests with your Panther support team.
PyPanther Detections are Panther’s evolved approach to detections-as-code. In this framework, detections are defined fully in Python, enabling component reusability and simple rule overrides. The foundation of PyPanther Detections is the Panther-managed pypanther
Python library.
Key features
An entirely Python-native experience for importing, writing, modifying, testing, and uploading rules—eliminating the need to manage a fork or clone of panther-analysis.
The ability to apply custom configurations to Panther-managed rules through overrides, filters, and inheritance.
The ability to selectively choose the set of rules you want to include in your Panther deployment package.
Benefits
PyPanther Detections have the following benefits:
No upstream merge conflicts: In the v1 model, merge conflicts can arise when syncing your customized fork of
panther-analysis
with the upstream repository. In this PyPanther model, Panther-managed rules exist separately from your rule configurations, eliminating the possibility of merge conflicts.Full flexibility and composability: This feature offers complete flexibility in rule creation, enabling full modularity, composability, the ability to override any rule attribute, and full Python inheritance—all providing a customizable and user-centric experience.
First-class developer experience: Backed by a portable, open-source Python library called
pypanther
, this framework provides a superior local development experience by hooking into native applications and developer workflows. This library can also be loaded into any Python environment, such as Jupyter Notebooks.
PyPanther Detections vs. v1 detections
This documentation uses the term "v1 detections," which refers to rules created in the format described in Writing Python Detections. PyPanther Detections are sometimes referred to as "v2 detections."
Panther has developed a tool that translates detections from v1 to PyPanther format—see the convert
command on Using the pypanther Command Line Tool.
PyPanther Detections differ from v1 detections in the following areas:
File structure: A rule in the v1 framework requires two files: a Python file to define rule logic and dynamic alerting functions and a YAML file to set metadata. A PyPanther rule is written entirely in Python.
This singular rule definition in a Python class—which contains all functions, properties, and helpers—enables overrides and composability.
Process for retrieving Panther-managed detections: In v1 detections, you must periodically sync your copy of
panther-analysis
with upstream changes. With PyPanther Detections, however, no Git syncing is required—the latest Panther-managed content is always available in thepypanther
Python library.Packs: Panther-managed v1 detections are bundled in Detection Packs. With PyPanther Detections, you can choose which detections you want to include in your Panther instance using
get_panther_rules()
(or direct imports) withregister()
.
The same detection, Box.New.Login
is defined below in both versions:
Limitations of PyPanther Detections
PyPanther Detections are currently designed for real-time rules developed in the CLI workflow.
While PyPanther Detections are evolving rapidly, they currently have the following limitations:
(Planned) It’s not possible to define new custom Lookup Tables.
It is possible to reference data from existing custom Lookup Tables, as well as Panther-managed Enrichment Providers.
(Planned) No support for Policies, Scheduled Rules, Simple Rules, Correlation Rules.
Only rules (or “real-time rules”) are supported in the PyPanther framework.
(Planned) It’s not possible to use Data Replay in the CLI or Console workflows.
(Planned) It’s not possible to define your own data models.
It is possible to reference Panther-managed data models in the
pypanther
library, usingevent.udm()
.
Library version pinning and declaring custom dependencies from the repository are not supported.
It’s not possible to edit PyPanther Detections in the Panther Console.
The CLI tool used with PyPanther Detections,
pypanther
, does not have all of the commandspanther_analysis_tool
does.See a list of available
pypanther
commands on Using the pypanther CLI Tool.
There are limitations on the functionality of the
upload
command. See theupload
limitations section.
Getting started using PyPanther Detections
Panther has provided this pypanther-starter-kit repository, containing PyPanther Detection examples, which you can clone to quickly get up and running.
Get started by following the setup instructions in the repository's README.
Creating PyPanther Detections
Before writing PyPanther Detections, you’ll need to set up your environment. See Getting started using PyPanther Detections, above.
When working with PyPanther Detections:
A
main.py
file controls your entire detection configuration, outlining which rules to register, override configurations, and custom rule definitions. You can either:(Recommended) Define your detections in various other files/folders, then import them into
main.py
Define your detections in
main.py
A PyPanther rule is defined in a single Python file. Within it, you can import Panther-managed (or your own custom) PyPanther rules and specify overrides. A single Python file can define multiple detections.
All PyPanther rules subclass the
pypanther
Rule
class or a parent class of typeRule
.Rules must be registered to be tested and uploaded to your Panther instance.
All event object functions currently available in v1 detections are available in PyPanther Detections. These include:
get()
,deep_get()
,deep_walk()
, andudm()
.All alert functions available in Python (v1) detections are available in PyPanther Detections, such as
title()
andseverity()
. SeeRule
auxiliary/alerting function reference.
Writing a custom PyPanther Detection
A "custom" PyPanther rule is one that you write completely from scratch—i.e., one that isn't built from a Panther-managed rule. Custom PyPanther rules are defined in a Python class that subclasses the pypanther
Rule
class. In this class, you must:
Define a
rule()
function(Optional) Define any of the other alert functions, like
title()
ordestinations()
Define certain attributes, such as
log_types
(Optional) Define additional attributes, such as
threshold
ordedup_period_minutes
The id
attribute is only required for a rule if you plan to register it.
See the Rule
property reference section for a full list of required and optional fields.
Importing Panther-managed rules
Panther-managed rules can be imported directly or using the get_panther_rules()
function.
Panther-managed rules currently all have a -prototype
suffix (e.g., AWS.Root.Activity-prototype
). This is temporary, and will be removed in the future.
You may want to import Panther-managed rules (into main.py
or another file) to either register them individually as-is, set overrides on them, or subclass them. You can import Panther-managed rules directly (from the pypanther.rules
module) or by using the get_panther_rules()
function.
To import a Panther-managed rule directly using the rules
module, you would use a statement like:
The get_panther_rules()
function can filter on any Rule
class attribute, such as default_severity
, log_types
, or tag
. When filtering, keys use AND
logic, and values use OR
logic.
Get all Panther-managed rules using get_panther_rules()
:
Get Panther-managed rules with certain severities using get_panther_rules()
:
Get Panther-managed rules for certain log types using get_panther_rules()
:
Get Panther-managed rules that meet multiple criteria using get_panther_rules()
:
See Using list comprehension for an example of how to use get_panther_rules()
with advanced Python.
Once you’ve imported a Panther-managed rule, you can modify it using overrides or inheritance.
Applying overrides on existing rules
If your objective is to modify a rule's logic, it's recommended to use include/exclude filters instead of overriding the rule()
function itself. Learn more in Use filters instead of overriding rule()
.
When making overrides on Panther-managed detections, it's recommended to:
Outside of
main.py
, store all of your overrides inapply_overrides()
functions.In
main.py
, callpypanther
'sapply_overrides()
to apply each of yourapply_overrides()
functions.
Learn more about apply_overrides()
in PyPanther Detections Style Guide.
Overriding single attributes
You can override a single rule attribute in a one-line statement using the override()
function:
Overriding multiple attributes with the override function
It’s also possible to make multi-attribute overrides with the override()
function:
Applying overrides on multiple PyPanther rules
To apply overrides on multiple rules at once, iterate over the collection using a for
loop.
This could be useful, for example, when updating a certain attribute for all rules associated to a certain LogType
.
Extending list attributes on existing rules
When making modifications to an existing rule, you might want to add items to a list-type rule attribute (like tags
, tests
, include_filters
, or exclude_filters
) while preserving the existing list.
Instead of overriding the attribute (using one of the methods in Applying overrides on existing rules), which would replace the existing list value, use the pypanther
extend()
function to append new values to the list attribute.
Creating include or exclude filters
PyPanther Detection filters let you exclude certain events from being evaluated by a rule. Filters are designed to be applied on top of existing rule logic (likely for Panther-managed PyPanther Detections you are importing).
Each filter defines logic that is run before the rule()
function, and the outcome of the filter determines whether or not the event should go on to be evaluated by the rule.
Common use cases for filters include:
To target only certain environments, like
prod
To exclude events that are known false positives, due to a misconfiguration or other non-malicious scenario
There are two types of filters:
include_filters
: If the filter returnsTrue
for an event, the event is evaluated byrule()
exclude_filters
: If the filter returnsTrue
for an event, the event is dismissed (i.e., not evaluated byrule()
)
The Rule
base class has include_filters
and exclude_filters
attributes, which each contain a list of functions that will be evaluated against the log event.
Examples as standalone functions:
Example as part of an inherited rule definition:
Filters can also be reused with a for
loop to be applied to multiple rules:
Ensuring necessary fields are set on configuration-required rules
Panther-managed rules that require some customer configuration before they are uploaded into a Panther environment may include a validate_config()
function, which defines one or more conditions that must be met for the rule to pass the test
command (and function properly).
Most commonly, validate_config()
verifies that some class variable, such as an allowlist or denylist, has been assigned a value. If the requirements included in validate_config()
are not met, an exception will be raised when the pypanther
test
command is run (if the rule is registered).
Example:
In this example, if allowed_domains
is not assigned a non-empty list, an assertion error will be thrown during pypanther
test
.
To set this value, you can use a statement like:
Creating PyPanther rules with inheritance
You can use inheritance to create rules that are subclasses of other rules (that are Panther-managed or custom).
It’s recommended to use inheritance when you’re creating a collection of rules that all share a number of characteristics—for example, rule()
function logic, property values, class variables, or helper functions. In this case, it’s useful to create a base rule that all related rules inherit from.
For example, it may be useful to create a base rule for each LogType
, from which all rules for that LogType
are extended.
Inheritance is commonly used with filters—i.e., subclassed rules can define additional criteria an event must meet in order to be processed by the rule.
If you don’t plan to register a base rule, it’s not required to provide it an id
property.
Example:
Using advanced Python
Because PyPanther rules are fully defined in Python, you can use its full expressiveness when customizing your detections.
Calling super()
super()
For more advanced use cases, you can supplement the logic in functions defined by the parent rule.
Using list comprehension
You can use Python’s list comprehension functionality to create a new list based on an existing list with condensed syntax. This may be particularly useful when you want to filter a list of detections fetched using get_panther_rules()
.
Registering PyPanther Detections
Registering a PyPanther rule means including it in your Panther deployment package.
To register a rule, pass it in to register()
in your main.py
file. When you run the pypanther
upload
or test
commands, only rules passed in to register()
will be uploaded to your Panther instance or tested.
If a previously uploaded rule is not passed in to register()
on a subsequent invocation of upload
, it will be deleted in your Panther instance.
Registering a single rule more than once (perhaps because it’s included in multiple collections, which are all passed into register()
) will not result in an error.
It’s not required to register rules used as base rules with inheritance so long as you do not want the base rules themselves uploaded into your Panther instance.
Viewing registered rules
To see all rules that are registered given your currently configured repository, run the
pypanther list rules
CLI command.Learn about other commands in the pypanther CLI command reference.
Importing instances of Rule
Rule
You can use the get_rules()
function to easily fetch rules you might want to register in main.py
. get_rules()
takes in an imported package (folder) or module (file) name, and returns all rules from that package or module that inherit the pypanther
Rule
class.
Each folder containing rules imported with get_rules()
must contain an __init__.py
file. Learn more about recommended repository structure in the PyPanther Detections Style Guide.
Example using get_rules()
:
Registering vs. enabling a rule
All rules have an enabled
property, which is different from being registered. See the table below for all possible outcomes:
Testing PyPanther Detections
Defining tests
Tests for PyPanther Detections are defined by creating instances of the pypanther
RuleTest
class.
Each instance of RuleTest
must set a name
, expected_result
, and log
. Each test can also optionally define:
Additional fields (prepended with
expected_
) that verify the output of alert functions. For example,expected_severity
andexpected_title
.A
mocks
field, which takes a list ofRuleMock
s.
See a full list of available fields in the RuleTest
property reference.
Tests are associated with rules by assigning them to a rule’s tests
field. Tests can be defined directly within a rule, or separately set to a variable (that is either local or imported).
Example
Note that this rule's tests
(defined above the rule class) use mocks
, expected_title
, expected_dedup
, and expected_alert_context
.
Running tests
To run all tests defined for your PyPanther Detections, run:
To run only a subset of tests, filter the detections for which tests are run by using a filter flag with test
, such as --id
or --log-types
. See a full list of filter flags by running pypanther test --help
.
The test
command:
Only tests those detections passed into
register()
Skips tests for Panther-managed rules
If any rules fail a test, the error will print next to the rule name that was tested:
Testing custom helper functions and data fixtures
The pypanther test
command tests all PyPanther Detection classes that have a tests
attribute set. In addition to testing rule()
and alert function output in this way, if you have created custom helper functions for use in rules, you may want to write targeted tests for these helper functions. To do this, it's recommended to use a common Python testing framework, such as pytest or unittest.
Currently, pypanther
will not run these tests during pypanther test
, but they can be run locally or as part of your CI/CD workflow.
Example: using pytest to test a custom function
Say you'd like to write a reusable function that reads a list of AWS account IDs from a file and filters it to include only the production ones. (This function could then be used in an include or exclude filter.) This function might look like the following:
Given this function, there are a few ways in which you may want to test both the data fixture (AWS_ACCOUNTS
) itself, as well as the function is_prod_aws_account()
. For instance:
To verify that the
AWS_ACCOUNTS
data fixture:Has a certain number of rows
Has a certain required field
To verify that certain account IDs are included in the
prod
list (and others are not)
To test these characteristics using pytest
, you would add the following functions (all with the test_
prefix):
After you define these tests, you can invoke them by running pytest
from your command line.
Uploading PyPanther Detections to Panther
In order to use the pypanther upload
functionality, it must first be enabled for you. If you would like to upload detections, please reach out to your Panther Support team.
To upload all PyPanther Detections that are registered, run:
You must authenticate when using upload
—see Authenticating CLI commands.
You can see all upload
options by running pypanther upload -h
, but may find the following options particularly useful:
--verbose
: Generates verbose output, which includes a list of tests by detection (and their pass/fail statuses), a list of registered detections, and a list of included files--dry-run
: Do not upload, but show a summary of the changes that will be applied in the next upload--output {text, json}
: Prints output in the provided formattext
is the default value, butjson
can be useful if you plan to port the output into another workflow
If a previously uploaded rule is not passed in to register()
on a subsequent invocation of upload
, it will be deleted in your Panther instance.
Uploading a PyPanther rule with the same id
as an existing v1 rule's RuleId
will overwrite it. The same is true in the other direction—i.e., uploading a PyPanther rule with the same RuleId
as an existing PyPanther rule's id
will overwrite it.
upload
limitations
upload
limitationsThe following limitations currently apply to pypanther upload
:
A maximum of 500 custom rules can be uploaded at once.
This limit does not include Panther-managed rules (i.e., those returned by the
get_panther_rules()
function)
The total size of the zip file
pypanther
produces for upload cannot exceed 4 MB.
Last updated