Using AWS Secrets Manager with Panther Detections

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.

alert

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.

Create a custom IAM role

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.

  1. Navigate to IAM
  2. Click Create role
  3. Select Custom trust policy (see: Add trust policy)
  4. Add the required permissions
  1. Click Next
  2. Select Create policy
  1. Under Specify permissions, add GetSecretValue and DescribeSecret (see: Add IAM permissions)
  1. Give your policy a name and description
  1. Navigate back to your role
  2. In the Add permissions page, find and select your new policy (you may need to click the refresh button)
  1. Select Create role
  1. Copy the Role ARN

Add trust policy

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.

heart

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)

Add IAM permissions

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)

Finish the configuration

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.

Writing detections using stored secrets

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)

Tips

Recommended Resources

Escape Cloud Noise. Detect Security Signal.
Request a Demo