Panther’s detection engine can make API calls out to third-party resources such as enrichment providers or threat intelligence feeds. While the majority of enrichment use cases can be achieved using custom or built-in Lookup Tables, Panther can support this workflow by granting special permissions to the detection engine that runs your Python code and allowing you access to secrets stored in your own AWS account.
To create and store secrets in AWS Secrets Manager, please reference the AWS documentation: Create an AWS Secrets Manager secret.
Panther’s detection engine runs within an AWS Lambda over every single log event as it’s received. To understand this level of scale, if you are processing one million log events in an hour, and if you are making an API call in even half of those detections, it will very quickly exacerbate any API rate limits that may be in place and likely affect detection performance.
Because of this, it’s crucial to implement third-party API calls strategically and deliberately in Panther detections. See the Tips section below for implementation ideas.
AWS documentation reference: Creating IAM policies.
To access your stored secrets, Panther needs an IAM role with permissions to read from Secrets Manager. Create an IAM role following the steps below.
GetSecretValue
and DescribeSecret
(see: Add IAM permissions)In order to explicitly grant Panther the necessary permissions to access your secret, you need to set the Principal(s) that are allowed to assume your role. In this case, you will grant role-based access to the Panther detections engine.
You will need the role ARN used to execute the Panther detections engine. If you want to access your secret while testing, you will need to grant access to a second role used for running unit tests.
Please reach out to your Panther team to request these two ARNs. You cannot move forward without them.
Once you’ve received the required detections engine role ARN and optionally, the unit tests role ARN from Panther, add them as Principals to your trust policy. It should look like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": [
"<Panther-detections-engine-IAM-role>",
"<Panther-unit-tests-IAM-role>"
]
},
"Action": "sts:AssumeRole"
}
]
}
Code language: JSON / JSON with Comments (json)
Your IAM policy must include these statements: GetSecretValue
and DescribeSecret
. As a best practice, it is recommended to only allow Panther access to the required secret for your API calls.
Your IAM policy should look like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Statement",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": [
"<your-secret-arn>"
]
}
]
}
Code language: JSON / JSON with Comments (json)
After you have successfully created your IAM role, provide the role ARN you copied in Step 12 to your Panther team. They will add it to a backend deployment task which creates a list of roles that the detection engine has permission to assume. Once this step has been completed, you can proceed to writing detections.
To use your secret within a Panther detection, you can first write code that retrieves the secret, and then use it to make calls to a third-party API. boto3
and requests
are two Python libraries that are installed out-of-the-box in your Panther runtime environment, and should be used for this use case.
Rather than writing logic in each rule definition to assume your custom role, you can write one helper module and import it into multiple detections. Using boto3
, you can instantiate STS and Secrets Manager clients, assume the role programmatically via the assume_role method, and then fetch the secret from Secret Manager via the get_secret_value method.
Your helper might look like this:
# custom_boto3_helpers.py
import boto3
def get_aws_credentials():
sts_client = boto3.client("sts")
assumed_role_object = sts_client.assume_role(
RoleArn="arn:aws:iam::{your-AWS-account-id}:role/{role-name}",
RoleSessionName="AssumeRoleSession1",
)
return assumed_role_object["Credentials"]
def get_stored_secret(assumed_role_obj, sec_id):
client = boto3.client(
"secretsmanager",
region_name="{your-aws-region}",
aws_access_key_id=assumed_role_obj["AccessKeyId"],
aws_secret_access_key=assumed_role_obj["SecretAccessKey"],
aws_session_token=assumed_role_obj["SessionToken"],
)
return client.get_secret_value(SecretId=sec_id)
Code language: Python (python)
Once you have defined code that retrieves the necessary credentials and fetches the secret value off of the returned credentials object, you can use the requests
library to call the API.
The example below looks up an IP address report from Virus Total. The alert_context function makes a call out to Virus Total and returns a last_analysis_stats
object. It uses the get_aws_credentials
and get_stored_secret
helpers defined above to fetch a secret named “panther-secrets/virus-total.” It also checks the IP address on the event using Panther’s unified data model syntax, meaning this detection can be easily configured to run over multiple log types.
def alert_context(event):
credentials = get_aws_credentials()
secret = get_stored_secret(credentials, "panther-secrets/virus-total")
api_key = secret.get('SecretString')
url = f"https://www.virustotal.com/api/v3/ip_addresses/{event.udm('source_ip')}"
headers = {"x-apikey": api_key, "Content-Type": "application/json"}
response = requests.get(url, headers=headers).json()
return deep_get(response, "data", "attributes")
'''
Example alert context for IP 35.216.199.51:
{
"regional_internet_registry": "RIPE NCC",
"network": "35.216.0.0/15",
"tags": [],
"country": "CH",
"last_analysis_date": 1687579849,
"as_owner": "GOOGLE",
"last_analysis_stats": {
"harmless": 64,
"malicious": 1,
"suspicious": 0,
"undetected": 23,
"timeout": 0
},
"asn": 15169,
"whois_date": 1687579885,
"last_analysis_results": {
"Bkav": {
"category": "undetected",
"result": "unrated",
"method": "blacklist",
"engine_name": "Bkav"
},
...
}
}
'''
Code language: Python (python)