Skip to content

Commit

Permalink
Merge pull request #1266 from nusantara-self/feature/aws-lambda-respo…
Browse files Browse the repository at this point in the history
…nder

Add AWS Invoke Lambda responder
  • Loading branch information
nusantara-self authored Oct 24, 2024
2 parents 14dcd45 + f68ce99 commit e5d9f39
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 0 deletions.
70 changes: 70 additions & 0 deletions responders/AWSLambda/AWSInvokeLambda.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"name": "AWSLambda_InvokeFunction",
"version": "1.0",
"author": "nusantara-self",
"url": "https://github.com/TheHive-Project/Cortex-Analyzers",
"license": "AGPL-V3",
"description": "Invokes the configured AWS Lambda function",
"dataTypeList": ["thehive:case", "thehive:alert", "thehive:case_artifact", "thehive:task"],
"command": "AWSLambda/AWSInvokeLambda.py",
"baseConfig": "AWSLambda",
"configurationItems": [
{
"name": "aws_access_key_id",
"description": "AWS Access Key ID",
"type": "string",
"multi": false,
"required": true,
"defaultValue": ""
},
{
"name": "aws_secret_access_key",
"description": "AWS Secret Access Key",
"type": "string",
"multi": false,
"required": true,
"defaultValue": ""
},
{
"name": "aws_region",
"description": "AWS Region",
"type": "string",
"multi": false,
"required": true,
"defaultValue": "us-east-1"
},
{
"name": "lambda_function_name",
"description": "Name of the AWS Lambda function to invoke",
"type": "string",
"multi": false,
"required": true,
"defaultValue": ""
},
{
"name": "invocation_type",
"description": "Invocation type for the lambda function. Default is 'RequestResponse'. Change to 'Event' for asynchronous invocation.",
"type": "string",
"multi": false,
"required": true,
"defaultValue": "RequestResponse"
},
{
"name": "add_tag_to_case",
"description": "Add a tag to case mentioning the AWS Lambda function that was invoked",
"type": "boolean",
"multi": false,
"required": true,
"defaultValue": true
}
],
"registration_required": true,
"subscription_required": true,
"free_subscription": false,
"service_homepage": "https://aws.amazon.com/lambda/",
"service_logo": {
"path": "assets/awslambda.png",
"caption": "AWS Lambda logo"
}
}

97 changes: 97 additions & 0 deletions responders/AWSLambda/AWSInvokeLambda.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/usr/bin/env python3
from cortexutils.responder import Responder
import boto3
import json
from botocore.exceptions import BotoCoreError, ClientError

class AWSLambda(Responder):
def __init__(self):
Responder.__init__(self)
self.aws_access_key_id = self.get_param('config.aws_access_key_id', None, 'AWS Access Key ID missing')
self.aws_secret_access_key = self.get_param('config.aws_secret_access_key', None, 'AWS Secret Access Key missing')
self.aws_region = self.get_param('config.aws_region', None, 'AWS Region missing')
self.lambda_function_name = self.get_param('config.lambda_function_name', None, 'Lambda Function Name missing')
self.invocation_type = self.get_param('config.invocation_type', None, 'RequestResponse')
self.add_tag_to_case = self.get_param('config.add_tag_to_case', True)

def run(self):
Responder.run(self)

payload_data = self.get_param("data", None, "No data was passed from TheHive")

# Initialize a session using boto3
session = boto3.Session(
aws_access_key_id=self.aws_access_key_id,
aws_secret_access_key=self.aws_secret_access_key,
region_name=self.aws_region
)

# Initialize the Lambda client
lambda_client = session.client('lambda')

try:
# Invoke the Lambda function
response = lambda_client.invoke(
FunctionName=self.lambda_function_name,
InvocationType=self.invocation_type,
Payload=json.dumps(payload_data)
)


if self.invocation_type == 'Event':
# In case of async invocations (Event) , there is no response payload
message = f'Lambda function {self.lambda_function_name} invoked asynchronously (Event mode). Invocation acknowledged, no response payload.'
self.report({"message": message})
return

if 'FunctionError' in response:
self._handle_error(
message="Error from Lambda function",
error_type='LambdaFunctionError',
details=response.get('FunctionError', 'Unknown function error'),
additional_info=None
)
return

# Extract and decode response payload
response_payload = json.loads(response['Payload'].read())
message=f'Lambda function {self.lambda_function_name} invoked successfully: {response_payload}'
self.report({"message": message})

except BotoCoreError as e:
self._handle_error(
message="BotoCoreError occurred",
error_type='BotoCoreError',
details=str(e)
)

except ClientError as e:
error_message = e.response['Error']['Message']
self._handle_error(
message="ClientError occurred",
error_type='ClientError',
details=error_message,
additional_info=e.response
)

except Exception as e:
self._handle_error(
message="An unexpected exception occurred",
error_type='GeneralException',
details=str(e)
)

def _handle_error(self, message, error_type, details, additional_info=None):
"""Helper function to handle errors and return a string message."""
error_message = f"[{error_type}] {message}: {details} \n\nAdditional info: {additional_info}"
self.error(error_message)

def operations(self, raw):
operations = []
if self.add_tag_to_case:
tag = f"AWSLambdaInvoked-{self.lambda_function_name}"
operations.append(self.build_operation('AddTagToCase', tag=tag))
return operations

if __name__ == '__main__':
AWSLambda().run()
6 changes: 6 additions & 0 deletions responders/AWSLambda/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM python:3

WORKDIR /worker
COPY . AWSInvokeLambda
RUN test ! -e AWSInvokeLambda/requirements.txt || pip install --no-cache-dir -r AWSInvokeLambda/requirements.txt
ENTRYPOINT AWSInvokeLambda/AWSInvokeLambda.py
42 changes: 42 additions & 0 deletions responders/AWSLambda/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
### AWS Lambda Responder

This responder triggers an AWS Lambda function using the provided credentials and configuration, directly from TheHive. By default, it can be triggered from an alert, case, observable, task and sends the data of the object as input to the AWS Lambda Function for its execution.
Make sure to manage these different objects appropriately if needed.

#### Setup example
- Log in to your [AWS Management Console](https://aws.amazon.com/console/) go to **IAM**
- Create a **new IAM user** (e.g. CortexAWSlambda-invoke-responder) with AWS Credentials type : Access key - Programmatic
- Choose **attach policies directly** and attach a policy you created with least privilege, for example:
```
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"lambda:InvokeFunction"
],
"Resource": [
"arn:aws:lambda:<AWS_REGION>:<AWS_ACCOUNT_ID>:function:<LAMBDA_FUNCTION_NAME>"
]
}
]
}
```
- Go to your newly created user, to **Security tab** and create **access key** for an **Application running outside AWS**
- Configure properly the responder with the right credentials & aws region

#### Successful Execution

When an execution is successful in `RequestResponse` mode, the responder will be marked as "Success" with a report message in the following format:

```
{ "message": "Lambda function '<name-of-lambda-function>' invoked successfully.", "response": "<response from lambda function>" }
```

#### Failed Execution

When an execution fails in `RequestResponse` mode, the responder will be marked as "Failure" with a report message in the following format:
```
"[{error_type}] {message}: {details}\n\nAdditional info: {additional_info}"
```
Binary file added responders/AWSLambda/assets/awslambda.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions responders/AWSLambda/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cortexutils
boto3

0 comments on commit e5d9f39

Please sign in to comment.