diff --git a/responders/AWSLambda/AWSInvokeLambda.json b/responders/AWSLambda/AWSInvokeLambda.json new file mode 100755 index 000000000..f317635d3 --- /dev/null +++ b/responders/AWSLambda/AWSInvokeLambda.json @@ -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" + } + } + \ No newline at end of file diff --git a/responders/AWSLambda/AWSInvokeLambda.py b/responders/AWSLambda/AWSInvokeLambda.py new file mode 100755 index 000000000..150e15a3b --- /dev/null +++ b/responders/AWSLambda/AWSInvokeLambda.py @@ -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() diff --git a/responders/AWSLambda/Dockerfile b/responders/AWSLambda/Dockerfile new file mode 100755 index 000000000..0a745d7f0 --- /dev/null +++ b/responders/AWSLambda/Dockerfile @@ -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 diff --git a/responders/AWSLambda/README.md b/responders/AWSLambda/README.md new file mode 100644 index 000000000..47e4a9493 --- /dev/null +++ b/responders/AWSLambda/README.md @@ -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:::function:" + ] + } + ] +} +``` +- 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 '' invoked successfully.", "response": "" } +``` + +#### 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}" +``` diff --git a/responders/AWSLambda/assets/awslambda.png b/responders/AWSLambda/assets/awslambda.png new file mode 100644 index 000000000..01a84ca17 Binary files /dev/null and b/responders/AWSLambda/assets/awslambda.png differ diff --git a/responders/AWSLambda/requirements.txt b/responders/AWSLambda/requirements.txt new file mode 100755 index 000000000..e996eb0fb --- /dev/null +++ b/responders/AWSLambda/requirements.txt @@ -0,0 +1,2 @@ +cortexutils +boto3 \ No newline at end of file