From 936290689dda35c9fa09fce7af5a542fe2041db4 Mon Sep 17 00:00:00 2001 From: Austin Byers Date: Mon, 5 Mar 2018 16:25:17 -0800 Subject: [PATCH] Add SNS and SQS outputs (#620) --- .pylintrc | 2 +- conf/lambda.json | 4 - conf/outputs.json | 6 + docs/source/outputs.rst | 2 + manage.py | 21 +- stream_alert/alert_processor/main.py | 12 +- stream_alert/alert_processor/outputs/aws.py | 195 ++++++++++++------ .../alert_processor/outputs/github.py | 4 +- stream_alert/alert_processor/outputs/jira.py | 8 +- .../alert_processor/outputs/komand.py | 4 +- .../alert_processor/outputs/output_base.py | 16 +- .../alert_processor/outputs/pagerduty.py | 22 +- .../alert_processor/outputs/phantom.py | 4 +- stream_alert/alert_processor/outputs/slack.py | 4 +- stream_alert_cli/terraform/alert_processor.py | 10 +- .../modules/tf_alert_processor_iam/README.md | 3 +- .../modules/tf_alert_processor_iam/main.tf | 40 ++++ .../tf_alert_processor_iam/variables.tf | 12 ++ tests/unit/conf/lambda.json | 8 - tests/unit/conf/outputs.json | 8 +- .../stream_alert_alert_processor/__init__.py | 1 + .../stream_alert_alert_processor/test_main.py | 6 +- .../test_outputs/test_aws.py | 127 ++++++++---- .../test_outputs/test_github.py | 15 +- .../test_outputs/test_jira.py | 27 ++- .../test_outputs/test_komand.py | 13 +- .../test_outputs/test_output_base.py | 18 +- .../test_outputs/test_pagerduty.py | 58 +++--- .../test_outputs/test_phantom.py | 24 ++- .../test_outputs/test_slack.py | 12 +- .../terraform/test_alert_processor.py | 17 +- tests/unit/stream_alert_cli/test_outputs.py | 15 +- 32 files changed, 480 insertions(+), 238 deletions(-) diff --git a/.pylintrc b/.pylintrc index 68d2851b2..00b5c8feb 100644 --- a/.pylintrc +++ b/.pylintrc @@ -355,7 +355,7 @@ valid-metaclass-classmethod-first-arg=mcs [DESIGN] # Maximum number of arguments for function / method -max-args=5 +max-args=8 # Maximum number of attributes for a class (see R0902). max-attributes=15 diff --git a/conf/lambda.json b/conf/lambda.json index cb62742b9..cfa637791 100644 --- a/conf/lambda.json +++ b/conf/lambda.json @@ -14,10 +14,6 @@ "throttles_alarm_period_secs": 120, "throttles_alarm_threshold": 0 }, - "outputs": { - "aws-lambda": [], - "aws-s3": [] - }, "source_bucket": "PREFIX_GOES_HERE.streamalert.source", "source_current_hash": "", "source_object_key": "", diff --git a/conf/outputs.json b/conf/outputs.json index bd4dcd6ba..63b601696 100644 --- a/conf/outputs.json +++ b/conf/outputs.json @@ -5,6 +5,12 @@ "aws-s3": { "sample-bucket": "sample.bucket.name" }, + "aws-sns": { + "sample-topic": "sample-topic-name" + }, + "aws-sqs": { + "sample-queue": "sample-queue-name" + }, "komand": [ "sample-integration" ], diff --git a/docs/source/outputs.rst b/docs/source/outputs.rst index 4386e116e..e6a8f3b20 100644 --- a/docs/source/outputs.rst +++ b/docs/source/outputs.rst @@ -10,6 +10,8 @@ Out of the box, StreamAlert supports: * **AWS Lambda** * **AWS S3** +* **AWS SNS** +* **AWS SQS** * **Komand** * **PagerDuty** * **Phantom** diff --git a/manage.py b/manage.py index 3fd3bcf0f..3c29307d5 100755 --- a/manage.py +++ b/manage.py @@ -85,11 +85,25 @@ def _add_output_subparser(subparsers): Examples: - manage.py output new --service manage.py output new --service aws-s3 manage.py output new --service pagerduty manage.py output new --service slack +The following outputs are supported: + + aws-firehose + aws-lambda + aws-s3 + aws-sns + aws-sqs + github + jira + komand + pagerduty + pagerduty-incident + pagerduty-v2 + phantom + slack """.format(version)) output_parser = subparsers.add_parser( 'output', @@ -108,8 +122,9 @@ def _add_output_subparser(subparsers): output_parser.add_argument( '--service', choices=[ - 'aws-firehose', 'aws-lambda', 'aws-s3', 'jira', 'komand', 'pagerduty', 'pagerduty-v2', - 'pagerduty-incident', 'phantom', 'slack', 'github' + 'aws-firehose', 'aws-lambda', 'aws-s3', 'aws-sns', 'aws-sqs', + 'github', 'jira', 'komand', 'pagerduty', 'pagerduty-incident', 'pagerduty-v2', + 'phantom', 'slack', ], required=True, help=ARGPARSE_SUPPRESS) diff --git a/stream_alert/alert_processor/main.py b/stream_alert/alert_processor/main.py index 3efdedecd..d29cee206 100644 --- a/stream_alert/alert_processor/main.py +++ b/stream_alert/alert_processor/main.py @@ -44,14 +44,16 @@ def handler(event, context): if not config: return - region = context.invoked_function_arn.split(':')[3] + split_arn = context.invoked_function_arn.split(':') + region = split_arn[3] + account_id = split_arn[4] function_name = context.function_name # Return the current list of statuses back to the caller - return list(run(event, region, function_name, config)) + return list(run(event, region, account_id, function_name, config)) -def run(alert, region, function_name, config): +def run(alert, region, account_id, function_name, config): """Send an Alert to its described outputs. Args: @@ -70,6 +72,7 @@ def run(alert, region, function_name, config): } region (str): The AWS region of the currently executing Lambda function + account_id (str): The 12-digit AWS account ID of the currently executing Lambda function function_name (str): The name of the lambda function config (dict): The loaded configuration for outputs from conf/outputs.json @@ -101,7 +104,8 @@ def run(alert, region, function_name, config): continue # Retrieve the proper class to handle dispatching the alerts of this services - dispatcher = StreamAlertOutput.create_dispatcher(service, region, function_name, config) + dispatcher = StreamAlertOutput.create_dispatcher( + service, region, account_id, function_name, config) if not dispatcher: continue diff --git a/stream_alert/alert_processor/outputs/aws.py b/stream_alert/alert_processor/outputs/aws.py index c144ad293..554cbfec8 100644 --- a/stream_alert/alert_processor/outputs/aws.py +++ b/stream_alert/alert_processor/outputs/aws.py @@ -77,7 +77,7 @@ class KinesisFirehoseOutput(AWSOutput): @classmethod def get_user_defined_properties(cls): - """Properties asssigned by the user when configuring a new Firehose output + """Properties assigned by the user when configuring a new Firehose output Every output should return a dict that contains a 'descriptor' with a description of the integration being configured. @@ -146,7 +146,90 @@ def _firehose_request_wrapper(json_alert, delivery_stream): delivery_stream, resp['RecordId']) - return self._log_status(resp) + return self._log_status(resp, kwargs['descriptor']) + + +@StreamAlertOutput +class LambdaOutput(AWSOutput): + """LambdaOutput handles all alert dispatching to AWS Lambda""" + __service__ = 'aws-lambda' + + @classmethod + def get_user_defined_properties(cls): + """Get properties that must be assigned by the user when configuring a new Lambda + output. This should be sensitive or unique information for this use-case that needs + to come from the user. + + Every output should return a dict that contains a 'descriptor' with a description of the + integration being configured. + + Sending to Lambda also requires a user provided Lambda function name and optional qualifier + (if applicable for the user's use case). A fully-qualified AWS ARN is also acceptable for + this value. This value should not be masked during input and is not a credential requirement + that needs encrypted. + + Returns: + OrderedDict: Contains various OutputProperty items + """ + return OrderedDict([ + ('descriptor', + OutputProperty(description='a short and unique descriptor for this Lambda function ' + 'configuration (ie: abbreviated name)')), + ('aws_value', + OutputProperty(description='the AWS Lambda function name, with the optional ' + 'qualifier (aka \'alias\'), to use for this ' + 'configuration (ie: output_function:qualifier)', + input_restrictions={' '})), + ]) + + def dispatch(self, **kwargs): + """Send alert to a Lambda function + + The alert gets dumped to a JSON string to be sent to the Lambda function + + Args: + **kwargs: consists of any combination of the following items: + descriptor (str): Service descriptor (ie: slack channel, pd integration) + rule_name (str): Name of the triggered rule + alert (dict): Alert relevant to the triggered rule + """ + alert = kwargs['alert'] + alert_string = json.dumps(alert['record']) + function_name = self.config[self.__service__][kwargs['descriptor']] + + # Check to see if there is an optional qualifier included here + # Acceptable values for the output configuration are the full ARN, + # a function name followed by a qualifier, or just a function name: + # 'arn:aws:lambda:aws-region:acct-id:function:function-name:prod' + # 'function-name:prod' + # 'function-name' + # Checking the length of the list for 2 or 8 should account for all + # times a qualifier is provided. + parts = function_name.split(':') + if len(parts) == 2 or len(parts) == 8: + function = parts[-2] + qualifier = parts[-1] + else: + function = parts[-1] + qualifier = None + + LOGGER.debug('Sending alert to Lambda function %s', function_name) + + client = boto3.client('lambda', region_name=self.region) + # Use the qualifier if it's available. Passing an empty qualifier in + # with `Qualifier=''` or `Qualifier=None` does not work and thus we + # have to perform different calls to client.invoke(). + if qualifier: + resp = client.invoke(FunctionName=function, + InvocationType='Event', + Payload=alert_string, + Qualifier=qualifier) + else: + resp = client.invoke(FunctionName=function, + InvocationType='Event', + Payload=alert_string) + + return self._log_status(resp, kwargs['descriptor']) @StreamAlertOutput @@ -156,7 +239,7 @@ class S3Output(AWSOutput): @classmethod def get_user_defined_properties(cls): - """Get properties that must be asssigned by the user when configuring a new S3 + """Get properties that must be assigned by the user when configuring a new S3 output. This should be sensitive or unique information for this use-case that needs to come from the user. @@ -225,87 +308,63 @@ def dispatch(self, **kwargs): Bucket=bucket, Key=key) - return self._log_status(resp) + return self._log_status(resp, kwargs['descriptor']) @StreamAlertOutput -class LambdaOutput(AWSOutput): - """LambdaOutput handles all alert dispatching to AWS Lambda""" - __service__ = 'aws-lambda' +class SNSOutput(AWSOutput): + """Handle all alert dispatching for AWS SNS""" + __service__ = 'aws-sns' @classmethod def get_user_defined_properties(cls): - """Get properties that must be asssigned by the user when configuring a new Lambda - output. This should be sensitive or unique information for this use-case that needs - to come from the user. - - Every output should return a dict that contains a 'descriptor' with a description of the - integration being configured. - - Sending to Lambda also requires a user provided Lambda function name and optional qualifier - (if applicabale for the user's use case). A fully-qualified AWS ARN is also acceptable for - this value. This value should not be masked during input and is not a credential requirement - that needs encrypted. + """Properties assigned by the user when configuring a new SNS output. Returns: - OrderedDict: Contains various OutputProperty items + OrderedDict: With 'descriptor' and 'aws_value' OutputProperty tuples """ return OrderedDict([ - ('descriptor', - OutputProperty(description='a short and unique descriptor for this Lambda function ' - 'configuration (ie: abbreviated name)')), - ('aws_value', - OutputProperty(description='the AWS Lambda function name, with the optional ' - 'qualifier (aka \'alias\'), to use for this ' - 'configuration (ie: output_function:qualifier)', - input_restrictions={' '})), + ('descriptor', OutputProperty( + description='a short and unique descriptor for this SNS topic')), + ('aws_value', OutputProperty(description='SNS topic name')) ]) def dispatch(self, **kwargs): - """Send alert to a Lambda function + """Send alert to an SNS topic""" + # SNS topics can only be accessed via their ARN + topic_name = self.config[self.__service__][kwargs['descriptor']] + topic_arn = 'arn:aws:sns:{}:{}:{}'.format(self.region, self.account_id, topic_name) + topic = boto3.resource('sns', region_name=self.region).Topic(topic_arn) - The alert gets dumped to a JSON string to be sent to the Lambda function + response = topic.publish(Message=json.dumps(kwargs['alert'], indent=2)) + return self._log_status(response, kwargs['descriptor']) - Args: - **kwargs: consists of any combination of the following items: - descriptor (str): Service descriptor (ie: slack channel, pd integration) - rule_name (str): Name of the triggered rule - alert (dict): Alert relevant to the triggered rule - """ - alert = kwargs['alert'] - alert_string = json.dumps(alert['record']) - function_name = self.config[self.__service__][kwargs['descriptor']] - # Check to see if there is an optional qualifier included here - # Acceptable values for the output configuration are the full ARN, - # a function name followed by a qualifier, or just a function name: - # 'arn:aws:lambda:aws-region:acct-id:function:function-name:prod' - # 'function-name:prod' - # 'function-name' - # Checking the length of the list for 2 or 8 should account for all - # times a qualifier is provided. - parts = function_name.split(':') - if len(parts) == 2 or len(parts) == 8: - function = parts[-2] - qualifier = parts[-1] - else: - function = parts[-1] - qualifier = None +@StreamAlertOutput +class SQSOutput(AWSOutput): + """Handle all alert dispatching for AWS SQS""" + __service__ = 'aws-sqs' - LOGGER.debug('Sending alert to Lambda function %s', function_name) + @classmethod + def get_user_defined_properties(cls): + """Properties assigned by the user when configuring a new SQS output. - client = boto3.client('lambda', region_name=self.region) - # Use the qualifier if it's available. Passing an empty qualifier in - # with `Qualifier=''` or `Qualifier=None` does not work and thus we - # have to perform different calls to client.invoke(). - if qualifier: - resp = client.invoke(FunctionName=function, - InvocationType='Event', - Payload=alert_string, - Qualifier=qualifier) - else: - resp = client.invoke(FunctionName=function, - InvocationType='Event', - Payload=alert_string) + Returns: + OrderedDict: With 'descriptor' and 'aws_value' OutputProperty tuples + """ + return OrderedDict([ + ('descriptor', OutputProperty( + description='a short and unique descriptor for this SQS queue')), + ('aws_value', OutputProperty(description='SQS queue name')) + ]) - return self._log_status(resp) + def dispatch(self, **kwargs): + """Send alert to an SQS queue""" + # SQS queues can only be accessed via their URL + queue_name = self.config[self.__service__][kwargs['descriptor']] + queue_url = 'https://sqs.{}.amazonaws.com/{}/{}'.format( + self.region, self.account_id, queue_name) + queue = boto3.resource('sqs', region_name=self.region).Queue(queue_url) + + response = queue.send_message(MessageBody=json.dumps(kwargs['alert'])) + return self._log_status(response, kwargs['descriptor']) diff --git a/stream_alert/alert_processor/outputs/github.py b/stream_alert/alert_processor/outputs/github.py index 02a72caf8..6ed19fe2c 100644 --- a/stream_alert/alert_processor/outputs/github.py +++ b/stream_alert/alert_processor/outputs/github.py @@ -80,7 +80,7 @@ def dispatch(self, **kwargs): """ credentials = self._load_creds(kwargs['descriptor']) if not credentials: - return self._log_status(False) + return self._log_status(False, kwargs['descriptor']) username_password = "{}:{}".format(credentials['username'], credentials['access_token']) @@ -102,4 +102,4 @@ def dispatch(self, **kwargs): except OutputRequestFailure: success = False - return self._log_status(success) + return self._log_status(success, kwargs['descriptor']) diff --git a/stream_alert/alert_processor/outputs/jira.py b/stream_alert/alert_processor/outputs/jira.py index 74c1d0432..4f02ede85 100644 --- a/stream_alert/alert_processor/outputs/jira.py +++ b/stream_alert/alert_processor/outputs/jira.py @@ -282,7 +282,7 @@ def dispatch(self, **kwargs): """ creds = self._load_creds(kwargs['descriptor']) if not creds: - return self._log_status(False) + return self._log_status(False, kwargs['descriptor']) issue_id = None comment_id = None @@ -293,7 +293,7 @@ def dispatch(self, **kwargs): # Validate successful authentication if not self._auth_cookie: - return self._log_status(False) + return self._log_status(False, kwargs['descriptor']) # If aggregation is enabled, attempt to add alert to an existing issue. If a # failure occurs in this block, creation of a new Jira issue will be attempted. @@ -305,7 +305,7 @@ def dispatch(self, **kwargs): LOGGER.debug('Sending alert to an existing Jira issue %s with comment %s', issue_id, comment_id) - return self._log_status(True) + return self._log_status(True, kwargs['descriptor']) else: LOGGER.error('Encountered an error when adding alert to existing ' 'Jira issue %s. Attempting to create new Jira issue.', @@ -319,4 +319,4 @@ def dispatch(self, **kwargs): if issue_id: LOGGER.debug('Sending alert to a new Jira issue %s', issue_id) - return self._log_status(issue_id or comment_id) + return self._log_status(issue_id or comment_id, kwargs['descriptor']) diff --git a/stream_alert/alert_processor/outputs/komand.py b/stream_alert/alert_processor/outputs/komand.py index 55726b437..2878cb1b4 100644 --- a/stream_alert/alert_processor/outputs/komand.py +++ b/stream_alert/alert_processor/outputs/komand.py @@ -66,7 +66,7 @@ def dispatch(self, **kwargs): """ creds = self._load_creds(kwargs['descriptor']) if not creds: - return self._log_status(False) + return self._log_status(False, kwargs['descriptor']) headers = {'Authorization': creds['komand_auth_token']} @@ -74,4 +74,4 @@ def dispatch(self, **kwargs): resp = self._post_request(creds['url'], {'data': kwargs['alert']}, headers, False) success = self._check_http_response(resp) - return self._log_status(success) + return self._log_status(success, kwargs['descriptor']) diff --git a/stream_alert/alert_processor/outputs/output_base.py b/stream_alert/alert_processor/outputs/output_base.py index d53c36942..62202aba7 100644 --- a/stream_alert/alert_processor/outputs/output_base.py +++ b/stream_alert/alert_processor/outputs/output_base.py @@ -58,6 +58,7 @@ def wrapper(*args, **kwargs): return wrapper return real_decorator + class StreamAlertOutput(object): """Class to be used as a decorator to register all OutputDispatcher subclasses""" _outputs = {} @@ -67,12 +68,13 @@ def __new__(cls, output): return output @classmethod - def create_dispatcher(cls, service, region, function_name, config): + def create_dispatcher(cls, service, region, account_id, function_name, config): """Returns the subclass that should handle this particular service Args: service (str): The service identifier for this output region (str): The AWS region to use for some output types + account_id (str): The AWS account ID for computing AWS output ARNs function_name (str): The invoking AWS Lambda function name config (dict): The loaded output configuration dict @@ -83,7 +85,7 @@ def create_dispatcher(cls, service, region, function_name, config): if not dispatcher: return False - return dispatcher(region, function_name, config) + return dispatcher(region, account_id, function_name, config) @classmethod def get_dispatcher(cls, service): @@ -139,8 +141,9 @@ class OutputDispatcher(object): # out for both get and post requests. This applies to both connection and read timeouts _DEFAULT_REQUEST_TIMEOUT = 3.05 - def __init__(self, region, function_name, config): + def __init__(self, region, account_id, function_name, config): self.region = region + self.account_id = account_id self.secrets_bucket = self._get_secrets_bucket_name(function_name) self.config = config @@ -250,16 +253,17 @@ def _kms_decrypt(self, data): LOGGER.error('an error occurred during credentials decryption: %s', err.response) @classmethod - def _log_status(cls, success): + def _log_status(cls, success, descriptor): """Log the status of sending the alerts Args: success (bool or dict): Indicates if the dispatching of alerts was successful + descriptor (str): Service descriptor """ if success: - LOGGER.info('Successfully sent alert to %s', cls.__service__) + LOGGER.info('Successfully sent alert to %s:%s', cls.__service__, descriptor) else: - LOGGER.error('Failed to send alert to %s', cls.__service__) + LOGGER.error('Failed to send alert to %s:%s', cls.__service__, descriptor) return bool(success) diff --git a/stream_alert/alert_processor/outputs/pagerduty.py b/stream_alert/alert_processor/outputs/pagerduty.py index ae5158f60..b49cc21d4 100644 --- a/stream_alert/alert_processor/outputs/pagerduty.py +++ b/stream_alert/alert_processor/outputs/pagerduty.py @@ -111,7 +111,7 @@ def dispatch(self, **kwargs): """ creds = self._load_creds(kwargs['descriptor']) if not creds: - return self._log_status(False) + return self._log_status(False, kwargs['descriptor']) message = 'StreamAlert Rule Triggered - {}'.format(kwargs['rule_name']) rule_desc = kwargs['alert']['rule_description'] @@ -132,7 +132,7 @@ def dispatch(self, **kwargs): except OutputRequestFailure: success = False - return self._log_status(success) + return self._log_status(success, kwargs['descriptor']) @StreamAlertOutput class PagerDutyOutputV2(OutputDispatcher): @@ -185,7 +185,7 @@ def dispatch(self, **kwargs): """ creds = self._load_creds(kwargs['descriptor']) if not creds: - return self._log_status(False) + return self._log_status(False, kwargs['descriptor']) data = events_v2_data(creds['routing_key'], **kwargs) @@ -194,7 +194,7 @@ def dispatch(self, **kwargs): except OutputRequestFailure: success = False - return self._log_status(success) + return self._log_status(success, kwargs['descriptor']) class PagerdutySearchDelay(Exception): """PagerdutyAlertDelay handles any delays looking up PagerDuty Incidents""" @@ -577,7 +577,7 @@ def dispatch(self, **kwargs): """ creds = self._load_creds(kwargs['descriptor']) if not creds: - return self._log_status(False) + return self._log_status(False, kwargs['descriptor']) # Cache base_url self._base_url = creds['api'] @@ -593,7 +593,7 @@ def dispatch(self, **kwargs): user_email = creds['email_from'] if not self._user_verify(user_email, False): LOGGER.error('Could not verify header From: %s, %s', user_email, self.__service__) - return self._log_status(False) + return self._log_status(False, kwargs['descriptor']) # Add From to the headers after verifying self._headers['From'] = user_email @@ -640,12 +640,12 @@ def dispatch(self, **kwargs): if not incident: LOGGER.error('Could not create main incident, %s', self.__service__) - return self._log_status(False) + return self._log_status(False, kwargs['descriptor']) # Extract the json blob from the response, returned by self._post_request_retry incident_json = incident.json() if not incident_json: - return self._log_status(False) + return self._log_status(False, kwargs['descriptor']) # Extract the incident id from the incident that was just created incident_id = incident_json.get('incident', {}).get('id') @@ -656,14 +656,14 @@ def dispatch(self, **kwargs): event = self._create_event(event_data) if not event: LOGGER.error('Could not create incident event, %s', self.__service__) - return self._log_status(False) + return self._log_status(False, kwargs['descriptor']) # Lookup the incident_key returned as dedup_key to get the incident id incident_key = event.get('dedup_key') if not incident_key: LOGGER.error('Could not get incident key, %s', self.__service__) - return self._log_status(False) + return self._log_status(False, kwargs['descriptor']) # Keep that id to be merged later with the created incident event_incident_id = self._get_event_incident_id(incident_key) @@ -681,4 +681,4 @@ def dispatch(self, **kwargs): note = rule_context.get('note', 'Creating SOX Incident') self._add_incident_note(merged_id, note) - return self._log_status(incident_id) + return self._log_status(incident_id, kwargs['descriptor']) diff --git a/stream_alert/alert_processor/outputs/phantom.py b/stream_alert/alert_processor/outputs/phantom.py index fd83a64bf..af4232d0e 100644 --- a/stream_alert/alert_processor/outputs/phantom.py +++ b/stream_alert/alert_processor/outputs/phantom.py @@ -140,7 +140,7 @@ def dispatch(self, **kwargs): """ creds = self._load_creds(kwargs['descriptor']) if not creds: - return self._log_status(False) + return self._log_status(False, kwargs['descriptor']) headers = {"ph-auth-token": creds['ph_auth_token']} rule_desc = kwargs['alert']['rule_description'] @@ -162,4 +162,4 @@ def dispatch(self, **kwargs): except OutputRequestFailure: success = False - return self._log_status(success) + return self._log_status(success, kwargs['descriptor']) diff --git a/stream_alert/alert_processor/outputs/slack.py b/stream_alert/alert_processor/outputs/slack.py index 548a67c12..ff59ccfd3 100644 --- a/stream_alert/alert_processor/outputs/slack.py +++ b/stream_alert/alert_processor/outputs/slack.py @@ -220,7 +220,7 @@ def dispatch(self, **kwargs): """ creds = self._load_creds(kwargs['descriptor']) if not creds: - return self._log_status(False) + return self._log_status(False, kwargs['descriptor']) slack_message = self._format_message(kwargs['rule_name'], kwargs['alert']) @@ -229,4 +229,4 @@ def dispatch(self, **kwargs): except OutputRequestFailure: success = False - return self._log_status(success) + return self._log_status(success, kwargs['descriptor']) diff --git a/stream_alert_cli/terraform/alert_processor.py b/stream_alert_cli/terraform/alert_processor.py index c6ab5d0ba..51e338281 100644 --- a/stream_alert_cli/terraform/alert_processor.py +++ b/stream_alert_cli/terraform/alert_processor.py @@ -62,7 +62,6 @@ def generate_alert_processor(config): prefix = config['global']['account']['prefix'] alert_processor_config = config['lambda']['alert_processor_config'] alarms_config = alert_processor_config.get('metric_alarms', {}) - outputs_config = alert_processor_config.get('outputs', {}) vpc_config = alert_processor_config.get('vpc_config', {}) result = infinitedict() @@ -75,8 +74,13 @@ def generate_alert_processor(config): 'prefix': prefix, 'role_id': '${module.alert_processor_lambda.role_id}', 'kms_key_arn': '${aws_kms_key.stream_alert_secrets.arn}', - 'output_lambda_functions': outputs_config.get('aws-lambda', []), - 'output_s3_buckets': outputs_config.get('aws-s3', []) + 'output_lambda_functions': [ + # Strip qualifiers: only the function name is needed for the IAM permissions + func.split(':')[0] for func in config['outputs'].get('aws-lambda', {}).values() + ], + 'output_s3_buckets': config['outputs'].get('aws-s3', {}).values(), + 'output_sns_topics': config['outputs'].get('aws-sns', {}).values(), + 'output_sqs_queues': config['outputs'].get('aws-sqs', {}).values() } # Set variables for the Lambda module diff --git a/terraform/modules/tf_alert_processor_iam/README.md b/terraform/modules/tf_alert_processor_iam/README.md index 393a0a546..2a248c04c 100644 --- a/terraform/modules/tf_alert_processor_iam/README.md +++ b/terraform/modules/tf_alert_processor_iam/README.md @@ -1,5 +1,4 @@ # Alert Processor Permissions This module adds IAM permissions specific to the alert processor: * Reading and decrypting output secrets - * Invoking Lambda outputs - * Writing to S3 outputs + * Sending to outputs (Lambda, S3, SNS, SQS) diff --git a/terraform/modules/tf_alert_processor_iam/main.tf b/terraform/modules/tf_alert_processor_iam/main.tf index 7ff996630..c460c252c 100644 --- a/terraform/modules/tf_alert_processor_iam/main.tf +++ b/terraform/modules/tf_alert_processor_iam/main.tf @@ -3,6 +3,8 @@ locals { firehose_arn_prefix = "arn:aws:firehose:${var.region}:${var.account_id}" lambda_arn_prefix = "arn:aws:lambda:${var.region}:${var.account_id}:function" + sns_arn_prefix = "arn:aws:sns:${var.region}:${var.account_id}" + sqs_arn_prefix = "arn:aws:sqs:${var.region}:${var.account_id}" // Terraform is upset if you try to index into an empty list, even if the resource count = 0. // https://github.com/hashicorp/terraform/issues/11210 @@ -10,6 +12,8 @@ locals { lambda_outputs = "${concat(var.output_lambda_functions, list("unused"))}" s3_outputs = "${concat(var.output_s3_buckets, list("unused"))}" + sns_outputs = "${concat(var.output_sns_topics, list("unused"))}" + sqs_outputs = "${concat(var.output_sqs_queues, list("unused"))}" } // Allow the Alert Processor to retrieve and decrypt output secrets @@ -116,3 +120,39 @@ data "aws_iam_policy_document" "write_to_s3_outputs" { ] } } + +// Allow the Alert Processor to publish to the configured SNS topics +resource "aws_iam_role_policy" "publish_to_sns_topics" { + count = "${length(var.output_sns_topics)}" + name = "SNSPublish_${element(local.sns_outputs, count.index)}" + role = "${var.role_id}" + policy = "${element(data.aws_iam_policy_document.publish_to_sns_topics.*.json, count.index)}" +} + +data "aws_iam_policy_document" "publish_to_sns_topics" { + count = "${length(var.output_sns_topics)}" + + statement { + effect = "Allow" + actions = ["sns:Publish"] + resources = ["${local.sns_arn_prefix}:${element(local.sns_outputs, count.index)}"] + } +} + +// Allow the Alert Processor to send to the configured SQS queues +resource "aws_iam_role_policy" "send_to_sqs_queues" { + count = "${length(var.output_sqs_queues)}" + name = "SQSSend_${element(local.sqs_outputs, count.index)}" + role = "${var.role_id}" + policy = "${element(data.aws_iam_policy_document.send_to_sqs_queues.*.json, count.index)}" +} + +data "aws_iam_policy_document" "send_to_sqs_queues" { + count = "${length(var.output_sqs_queues)}" + + statement { + effect = "Allow" + actions = ["sqs:SendMessage*"] + resources = ["${local.sqs_arn_prefix}:${element(local.sqs_outputs, count.index)}"] + } +} diff --git a/terraform/modules/tf_alert_processor_iam/variables.tf b/terraform/modules/tf_alert_processor_iam/variables.tf index 566977085..02803c81d 100644 --- a/terraform/modules/tf_alert_processor_iam/variables.tf +++ b/terraform/modules/tf_alert_processor_iam/variables.tf @@ -29,3 +29,15 @@ variable "output_s3_buckets" { default = [] description = "Optional list of configured S3 bucket outputs (bucket names)" } + +variable "output_sns_topics" { + type = "list" + default = [] + description = "Optional list of configured SNS outputs (topic names)" +} + +variable "output_sqs_queues" { + type = "list" + default = [] + description = "Optional list of configured SQS outputs (queue names)" +} diff --git a/tests/unit/conf/lambda.json b/tests/unit/conf/lambda.json index 5d9e16c52..e409a758c 100644 --- a/tests/unit/conf/lambda.json +++ b/tests/unit/conf/lambda.json @@ -14,14 +14,6 @@ "throttles_alarm_period_secs": 5, "throttles_alarm_threshold": 6 }, - "outputs": { - "aws-lambda": [ - "test-lambda-output" - ], - "aws-s3": [ - "test-s3-output" - ] - }, "source_bucket": "unit.testing.streamalert.source", "source_current_hash": "12345", "source_object_key": "lambda/alert/source.zip", diff --git a/tests/unit/conf/outputs.json b/tests/unit/conf/outputs.json index 747f09420..a21d3d176 100644 --- a/tests/unit/conf/outputs.json +++ b/tests/unit/conf/outputs.json @@ -4,11 +4,17 @@ }, "aws-lambda": { "unit_test_lambda": "unit_test_function", - "unit_test_lambda_qual": "unit_test_function:production" + "unit_test_lambda_qual": "unit_test_qualified_function:production" }, "aws-s3": { "unit_test_bucket": "unit.test.bucket.name" }, + "aws-sns": { + "unit_test_topic": "unit_test_topic_name" + }, + "aws-sqs": { + "unit_test_queue": "unit_test_queue_name" + }, "pagerduty": [ "unit_test_pagerduty" ], diff --git a/tests/unit/stream_alert_alert_processor/__init__.py b/tests/unit/stream_alert_alert_processor/__init__.py index 48564fa87..a3a48aab0 100644 --- a/tests/unit/stream_alert_alert_processor/__init__.py +++ b/tests/unit/stream_alert_alert_processor/__init__.py @@ -16,6 +16,7 @@ from stream_alert.alert_processor.main import _load_output_config REGION = 'us-east-1' +ACCOUNT_ID = '123456789012' FUNCTION_NAME = 'corp-prefix_prod_streamalert_alert_processor' CONFIG = _load_output_config('tests/unit/conf/outputs.json') KMS_ALIAS = 'alias/stream_alert_secrets_test' diff --git a/tests/unit/stream_alert_alert_processor/test_main.py b/tests/unit/stream_alert_alert_processor/test_main.py index e15c9a06b..ed1b4deb0 100644 --- a/tests/unit/stream_alert_alert_processor/test_main.py +++ b/tests/unit/stream_alert_alert_processor/test_main.py @@ -39,7 +39,7 @@ def test_handler_run(run_mock): # This test will load the actual config, so we should compare the # function call against the same config here. - run_mock.assert_called_with(None, REGION, FUNCTION_NAME, _load_output_config()) + run_mock.assert_called_with(None, REGION, '5'*12, FUNCTION_NAME, _load_output_config()) @patch('logging.Logger.error') @@ -68,7 +68,9 @@ def test_load_output_config(): config = _load_output_config('tests/unit/conf/outputs.json') assert_equal(set(config.keys()), { - 'aws-firehose', 'aws-s3', 'aws-lambda', 'pagerduty', 'phantom', 'slack'}) + 'aws-firehose', 'aws-s3', 'aws-lambda', 'aws-sns', 'aws-sqs', + 'pagerduty', 'phantom', 'slack' + }) def test_sort_dict(): diff --git a/tests/unit/stream_alert_alert_processor/test_outputs/test_aws.py b/tests/unit/stream_alert_alert_processor/test_outputs/test_aws.py index 0b2498454..441d17724 100644 --- a/tests/unit/stream_alert_alert_processor/test_outputs/test_aws.py +++ b/tests/unit/stream_alert_alert_processor/test_outputs/test_aws.py @@ -16,7 +16,7 @@ # pylint: disable=abstract-class-instantiated,protected-access,attribute-defined-outside-init,no-self-use import boto3 from mock import patch -from moto import mock_s3, mock_lambda, mock_kinesis +from moto import mock_kinesis, mock_lambda, mock_s3, mock_sns, mock_sqs from nose.tools import ( assert_equal, assert_false, @@ -29,10 +29,12 @@ AWSOutput, KinesisFirehoseOutput, LambdaOutput, - S3Output + S3Output, + SNSOutput, + SQSOutput ) from stream_alert_cli.helpers import create_lambda_function -from tests.unit.stream_alert_alert_processor import CONFIG, FUNCTION_NAME, REGION +from tests.unit.stream_alert_alert_processor import ACCOUNT_ID, CONFIG, FUNCTION_NAME, REGION from tests.unit.stream_alert_alert_processor.helpers import get_alert @@ -57,33 +59,6 @@ def test_aws_format_output_config(self): assert_is_not_none(formatted_config.get('unit_test_bucket')) -@mock_s3 -class TestS3Ouput(object): - """Test class for S3Output""" - DESCRIPTOR = 'unit_test_bucket' - SERVICE = 'aws-s3' - - def setup(self): - """Setup before each method""" - self._dispatcher = S3Output(REGION, FUNCTION_NAME, CONFIG) - bucket = CONFIG[self.SERVICE][self.DESCRIPTOR] - boto3.client('s3', region_name=REGION).create_bucket(Bucket=bucket) - - def test_locals(self): - """S3Output local variables""" - assert_equal(self._dispatcher.__class__.__name__, 'S3Output') - assert_equal(self._dispatcher.__service__, self.SERVICE) - - @patch('logging.Logger.info') - def test_dispatch(self, log_mock): - """S3Output - Dispatch Success""" - assert_true(self._dispatcher.dispatch(descriptor=self.DESCRIPTOR, - rule_name='rule_name', - alert=get_alert())) - - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) - - @mock_kinesis class TestFirehoseOutput(object): """Test class for AWS Kinesis Firehose""" @@ -92,7 +67,7 @@ class TestFirehoseOutput(object): def setup(self): """Setup before each method""" - self._dispatcher = KinesisFirehoseOutput(REGION, FUNCTION_NAME, CONFIG) + self._dispatcher = KinesisFirehoseOutput(REGION, ACCOUNT_ID, FUNCTION_NAME, CONFIG) delivery_stream = CONFIG[self.SERVICE][self.DESCRIPTOR] boto3.client('firehose', region_name=REGION).create_delivery_stream( DeliveryStreamName=delivery_stream, @@ -120,7 +95,8 @@ def test_dispatch(self, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) def test_dispatch_ignore_large_payload(self): """Output Dispatch - Kinesis Firehose with Large Payload""" @@ -132,14 +108,14 @@ def test_dispatch_ignore_large_payload(self): @mock_lambda -class TestLambdaOuput(object): +class TestLambdaOutput(object): """Test class for LambdaOutput""" DESCRIPTOR = 'unit_test_lambda' SERVICE = 'aws-lambda' def setup(self): """Setup before each method""" - self._dispatcher = LambdaOutput(REGION, FUNCTION_NAME, CONFIG) + self._dispatcher = LambdaOutput(REGION, ACCOUNT_ID, FUNCTION_NAME, CONFIG) create_lambda_function(CONFIG[self.SERVICE][self.DESCRIPTOR], REGION) def test_locals(self): @@ -154,15 +130,92 @@ def test_dispatch(self, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.info') def test_dispatch_with_qualifier(self, log_mock): """LambdaOutput - Dispatch Success, With Qualifier""" alt_descriptor = '{}_qual'.format(self.DESCRIPTOR) - create_lambda_function(alt_descriptor, REGION) + create_lambda_function(CONFIG[self.SERVICE][alt_descriptor], REGION) + assert_true(self._dispatcher.dispatch(descriptor=alt_descriptor, rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, alt_descriptor) + + +@mock_s3 +class TestS3Output(object): + """Test class for S3Output""" + DESCRIPTOR = 'unit_test_bucket' + SERVICE = 'aws-s3' + + def setup(self): + """Setup before each method""" + self._dispatcher = S3Output(REGION, ACCOUNT_ID, FUNCTION_NAME, CONFIG) + bucket = CONFIG[self.SERVICE][self.DESCRIPTOR] + boto3.client('s3', region_name=REGION).create_bucket(Bucket=bucket) + + def test_locals(self): + """S3Output local variables""" + assert_equal(self._dispatcher.__class__.__name__, 'S3Output') + assert_equal(self._dispatcher.__service__, self.SERVICE) + + @patch('logging.Logger.info') + def test_dispatch(self, log_mock): + """S3Output - Dispatch Success""" + assert_true(self._dispatcher.dispatch(descriptor=self.DESCRIPTOR, + rule_name='rule_name', + alert=get_alert())) + + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) + + +@mock_sns +class TestSNSOutput(object): + """Test class for SNSOutput""" + DESCRIPTOR = 'unit_test_topic' + SERVICE = 'aws-sns' + + def setup(self): + """Create the dispatcher and the mock SNS topic.""" + self._dispatcher = SNSOutput(REGION, ACCOUNT_ID, FUNCTION_NAME, CONFIG) + topic_name = CONFIG[self.SERVICE][self.DESCRIPTOR] + boto3.client('sns', region_name=REGION).create_topic(Name=topic_name) + + @patch('logging.Logger.info') + def test_dispatch(self, log_mock): + """SNSOutput - Dispatch Success""" + assert_true(self._dispatcher.dispatch(descriptor=self.DESCRIPTOR, + rule_name='rule_name', + alert=get_alert())) + + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) + + +@mock_sqs +class TestSQSOutput(object): + """Test class for SQSOutput""" + DESCRIPTOR = 'unit_test_queue' + SERVICE = 'aws-sqs' + + def setup(self): + """Create the dispatcher and the mock SQS queue.""" + self._dispatcher = SQSOutput(REGION, ACCOUNT_ID, FUNCTION_NAME, CONFIG) + queue_name = CONFIG[self.SERVICE][self.DESCRIPTOR] + boto3.client('sqs', region_name=REGION).create_queue(QueueName=queue_name) + + @patch('logging.Logger.info') + def test_dispatch(self, log_mock): + """SQSOutput - Dispatch Success""" + assert_true(self._dispatcher.dispatch(descriptor=self.DESCRIPTOR, + rule_name='rule_name', + alert=get_alert())) + + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) diff --git a/tests/unit/stream_alert_alert_processor/test_outputs/test_github.py b/tests/unit/stream_alert_alert_processor/test_outputs/test_github.py index 119d647e2..111833b0a 100644 --- a/tests/unit/stream_alert_alert_processor/test_outputs/test_github.py +++ b/tests/unit/stream_alert_alert_processor/test_outputs/test_github.py @@ -21,7 +21,8 @@ from stream_alert.alert_processor.outputs.github import GithubOutput from stream_alert_cli.helpers import put_mock_creds -from tests.unit.stream_alert_alert_processor import CONFIG, FUNCTION_NAME, KMS_ALIAS, REGION +from tests.unit.stream_alert_alert_processor import \ + ACCOUNT_ID, CONFIG, FUNCTION_NAME, KMS_ALIAS, REGION from tests.unit.stream_alert_alert_processor.helpers import ( get_alert, remove_temp_secrets @@ -41,7 +42,7 @@ class TestGithubOutput(object): def setup(self): """Setup before each method""" - self._dispatcher = GithubOutput(REGION, FUNCTION_NAME, CONFIG) + self._dispatcher = GithubOutput(REGION, ACCOUNT_ID, FUNCTION_NAME, CONFIG) remove_temp_secrets() output_name = self._dispatcher.output_cred_name(self.DESCRIPTOR) put_mock_creds(output_name, self.CREDS, self._dispatcher.secrets_bucket, REGION, KMS_ALIAS) @@ -66,7 +67,8 @@ def test_dispatch_success(self, url_mock, log_mock): assert_equal(decoded_username_password, '{}:{}'.format(self.CREDS['username'], self.CREDS['access_token'])) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.info') @patch('requests.post') @@ -80,7 +82,8 @@ def test_dispatch_success_with_labels(self, url_mock, log_mock): alert=get_alert())) assert_equal(url_mock.call_args[1]['json']['labels'], ['label1', 'label2']) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @patch('requests.post') @@ -93,7 +96,7 @@ def test_dispatch_failure(self, url_mock, log_mock): assert_false(self._dispatcher.dispatch(descriptor=self.DESCRIPTOR, rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') def test_dispatch_bad_descriptor(self, log_mock): @@ -102,4 +105,4 @@ def test_dispatch_bad_descriptor(self, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, 'bad_descriptor') diff --git a/tests/unit/stream_alert_alert_processor/test_outputs/test_jira.py b/tests/unit/stream_alert_alert_processor/test_outputs/test_jira.py index 47ad857bb..cd60cba34 100644 --- a/tests/unit/stream_alert_alert_processor/test_outputs/test_jira.py +++ b/tests/unit/stream_alert_alert_processor/test_outputs/test_jira.py @@ -20,7 +20,8 @@ from stream_alert.alert_processor.outputs.jira import JiraOutput from stream_alert_cli.helpers import put_mock_creds -from tests.unit.stream_alert_alert_processor import CONFIG, FUNCTION_NAME, KMS_ALIAS, REGION +from tests.unit.stream_alert_alert_processor import \ + ACCOUNT_ID, CONFIG, FUNCTION_NAME, KMS_ALIAS, REGION from tests.unit.stream_alert_alert_processor.helpers import get_alert, remove_temp_secrets @@ -40,7 +41,7 @@ class TestJiraOutput(object): def setup(self): """Setup before each method""" - self._dispatcher = JiraOutput(REGION, FUNCTION_NAME, CONFIG) + self._dispatcher = JiraOutput(REGION, ACCOUNT_ID, FUNCTION_NAME, CONFIG) self._dispatcher._base_url = self.CREDS['url'] remove_temp_secrets() output_name = self._dispatcher.output_cred_name(self.DESCRIPTOR) @@ -63,7 +64,8 @@ def test_dispatch_issue_new(self, post_mock, get_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.info') @patch('requests.get') @@ -83,7 +85,8 @@ def test_dispatch_issue_existing(self, post_mock, get_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.info') @patch('requests.get') @@ -103,7 +106,8 @@ def test_dispatch_issue_empty_comment(self, post_mock, get_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) @patch('requests.get') def test_get_comments_success(self, get_mock): @@ -157,7 +161,7 @@ def test_auth_failure(self, post_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @patch('requests.post') @@ -171,7 +175,7 @@ def test_auth_empty_response(self, post_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @patch('requests.get') @@ -191,7 +195,7 @@ def test_issue_creation_failure(self, post_mock, get_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @patch('requests.get') @@ -211,7 +215,7 @@ def test_issue_creation_empty_search(self, post_mock, get_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @patch('requests.get') @@ -230,7 +234,7 @@ def test_issue_creation_empty_response(self, post_mock, get_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @patch('requests.get') @@ -261,4 +265,5 @@ def test_dispatch_bad_descriptor(self, log_error_mock): rule_name='rule_name', alert=get_alert())) - log_error_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_error_mock.assert_called_with('Failed to send alert to %s:%s', + self.SERVICE, 'bad_descriptor') diff --git a/tests/unit/stream_alert_alert_processor/test_outputs/test_komand.py b/tests/unit/stream_alert_alert_processor/test_outputs/test_komand.py index e288725bd..1a00a43d9 100644 --- a/tests/unit/stream_alert_alert_processor/test_outputs/test_komand.py +++ b/tests/unit/stream_alert_alert_processor/test_outputs/test_komand.py @@ -20,7 +20,8 @@ from stream_alert.alert_processor.outputs.komand import KomandOutput from stream_alert_cli.helpers import put_mock_creds -from tests.unit.stream_alert_alert_processor import CONFIG, FUNCTION_NAME, KMS_ALIAS, REGION +from tests.unit.stream_alert_alert_processor import \ + ACCOUNT_ID, CONFIG, FUNCTION_NAME, KMS_ALIAS, REGION from tests.unit.stream_alert_alert_processor.helpers import get_alert, remove_temp_secrets @@ -36,7 +37,7 @@ class TestKomandutput(object): def setup(self): """Setup before each method""" - self._dispatcher = KomandOutput(REGION, FUNCTION_NAME, CONFIG) + self._dispatcher = KomandOutput(REGION, ACCOUNT_ID, FUNCTION_NAME, CONFIG) remove_temp_secrets() output_name = self._dispatcher.output_cred_name(self.DESCRIPTOR) put_mock_creds(output_name, self.CREDS, self._dispatcher.secrets_bucket, REGION, KMS_ALIAS) @@ -50,7 +51,8 @@ def test_dispatch_existing_container(self, post_mock, log_mock): assert_true(self._dispatcher.dispatch(descriptor=self.DESCRIPTOR, alert=get_alert())) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @patch('requests.post') @@ -63,7 +65,7 @@ def test_dispatch_container_failure(self, post_mock, log_mock): assert_false(self._dispatcher.dispatch(descriptor=self.DESCRIPTOR, alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') def test_dispatch_bad_descriptor(self, log_error_mock): @@ -71,4 +73,5 @@ def test_dispatch_bad_descriptor(self, log_error_mock): assert_false(self._dispatcher.dispatch(descriptor='bad_descriptor', alert=get_alert())) - log_error_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_error_mock.assert_called_with('Failed to send alert to %s:%s', + self.SERVICE, 'bad_descriptor') diff --git a/tests/unit/stream_alert_alert_processor/test_outputs/test_output_base.py b/tests/unit/stream_alert_alert_processor/test_outputs/test_output_base.py index b7d600aae..295baa108 100644 --- a/tests/unit/stream_alert_alert_processor/test_outputs/test_output_base.py +++ b/tests/unit/stream_alert_alert_processor/test_outputs/test_output_base.py @@ -34,7 +34,8 @@ ) from stream_alert.alert_processor.outputs.aws import S3Output from stream_alert_cli.helpers import encrypt_with_kms, put_mock_creds, put_mock_s3_object -from tests.unit.stream_alert_alert_processor import CONFIG, FUNCTION_NAME, KMS_ALIAS, REGION +from tests.unit.stream_alert_alert_processor import \ + ACCOUNT_ID, CONFIG, FUNCTION_NAME, KMS_ALIAS, REGION from tests.unit.stream_alert_alert_processor.helpers import remove_temp_secrets @@ -68,6 +69,7 @@ def test_create_dispatcher(): dispatcher = StreamAlertOutput.create_dispatcher( 'aws-s3', REGION, + ACCOUNT_ID, FUNCTION_NAME, CONFIG ) @@ -89,6 +91,8 @@ def test_output_loading(): 'aws-firehose', 'aws-lambda', 'aws-s3', + 'aws-sns', + 'aws-sqs', 'github', 'jira', 'komand', @@ -108,7 +112,7 @@ class TestOutputDispatcher(object): @patch.object(OutputDispatcher, '__abstractmethods__', frozenset()) def setup(self): """Setup before each method""" - self._dispatcher = OutputDispatcher(REGION, FUNCTION_NAME, CONFIG) + self._dispatcher = OutputDispatcher(REGION, ACCOUNT_ID, FUNCTION_NAME, CONFIG) self._descriptor = 'desc_test' def test_local_temp_dir(self): @@ -157,14 +161,16 @@ def test_kms_decrypt(self): @patch('logging.Logger.info') def test_log_status_success(self, log_mock): """OutputDispatcher - Log status success""" - self._dispatcher._log_status(True) - log_mock.assert_called_with('Successfully sent alert to %s', 'test_service') + self._dispatcher._log_status(True, self._descriptor) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + 'test_service', self._descriptor) @patch('logging.Logger.error') def test_log_status_failed(self, log_mock): """OutputDispatcher - Log status failed""" - self._dispatcher._log_status(False) - log_mock.assert_called_with('Failed to send alert to %s', 'test_service') + self._dispatcher._log_status(False, self._descriptor) + log_mock.assert_called_with('Failed to send alert to %s:%s', + 'test_service', self._descriptor) @patch('requests.Response') def test_check_http_response(self, mock_response): diff --git a/tests/unit/stream_alert_alert_processor/test_outputs/test_pagerduty.py b/tests/unit/stream_alert_alert_processor/test_outputs/test_pagerduty.py index a9c806381..987c3b64e 100644 --- a/tests/unit/stream_alert_alert_processor/test_outputs/test_pagerduty.py +++ b/tests/unit/stream_alert_alert_processor/test_outputs/test_pagerduty.py @@ -24,7 +24,8 @@ PagerDutyIncidentOutput ) from stream_alert_cli.helpers import put_mock_creds -from tests.unit.stream_alert_alert_processor import CONFIG, FUNCTION_NAME, KMS_ALIAS, REGION +from tests.unit.stream_alert_alert_processor import \ + ACCOUNT_ID, CONFIG, FUNCTION_NAME, KMS_ALIAS, REGION from tests.unit.stream_alert_alert_processor.helpers import get_alert, remove_temp_secrets @@ -40,7 +41,7 @@ class TestPagerDutyOutput(object): def setup(self): """Setup before each method""" - self._dispatcher = PagerDutyOutput(REGION, FUNCTION_NAME, CONFIG) + self._dispatcher = PagerDutyOutput(REGION, ACCOUNT_ID, FUNCTION_NAME, CONFIG) remove_temp_secrets() output_name = self._dispatcher.output_cred_name(self.DESCRIPTOR) put_mock_creds(output_name, self.CREDS, self._dispatcher.secrets_bucket, REGION, KMS_ALIAS) @@ -62,7 +63,8 @@ def test_dispatch_success(self, post_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @patch('requests.post') @@ -74,7 +76,7 @@ def test_dispatch_failure(self, post_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') def test_dispatch_bad_descriptor(self, log_mock): @@ -83,7 +85,7 @@ def test_dispatch_bad_descriptor(self, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, 'bad_descriptor') @mock_s3 @mock_kms @@ -97,7 +99,7 @@ class TestPagerDutyOutputV2(object): def setup(self): """Setup before each method""" - self._dispatcher = PagerDutyOutputV2(REGION, FUNCTION_NAME, CONFIG) + self._dispatcher = PagerDutyOutputV2(REGION, ACCOUNT_ID, FUNCTION_NAME, CONFIG) remove_temp_secrets() output_name = self._dispatcher.output_cred_name(self.DESCRIPTOR) put_mock_creds(output_name, self.CREDS, self._dispatcher.secrets_bucket, REGION, KMS_ALIAS) @@ -118,7 +120,8 @@ def test_dispatch_success(self, post_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @patch('requests.post') @@ -132,7 +135,7 @@ def test_dispatch_failure(self, post_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') def test_dispatch_bad_descriptor(self, log_mock): @@ -141,7 +144,7 @@ def test_dispatch_bad_descriptor(self, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, 'bad_descriptor') #pylint: disable=too-many-public-methods @@ -163,7 +166,7 @@ class TestPagerDutyIncidentOutput(object): def setup(self): """Setup before each method""" - self._dispatcher = PagerDutyIncidentOutput(REGION, FUNCTION_NAME, CONFIG) + self._dispatcher = PagerDutyIncidentOutput(REGION, ACCOUNT_ID, FUNCTION_NAME, CONFIG) self._dispatcher._base_url = self.CREDS['api'] remove_temp_secrets() output_name = self._dispatcher.output_cred_name(self.DESCRIPTOR) @@ -518,7 +521,8 @@ def test_dispatch_success_good_user(self, get_mock, post_mock, put_mock, log_moc rule_name='rule_name', alert=get_alert(context=ctx))) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.info') @patch('requests.put') @@ -554,7 +558,8 @@ def test_dispatch_success_good_policy(self, get_mock, post_mock, put_mock, log_m rule_name='rule_name', alert=get_alert(context=ctx))) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.info') @patch('requests.put') @@ -595,7 +600,8 @@ def test_dispatch_success_with_priority(self, get_mock, post_mock, put_mock, log rule_name='rule_name', alert=get_alert(context=ctx))) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.info') @patch('requests.put') @@ -632,7 +638,8 @@ def test_dispatch_success_bad_user(self, get_mock, post_mock, put_mock, log_mock rule_name='rule_name', alert=get_alert(context=ctx))) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.info') @patch('requests.put') @@ -666,7 +673,8 @@ def test_dispatch_success_no_context(self, get_mock, post_mock, put_mock, log_mo rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @patch('requests.post') @@ -687,7 +695,7 @@ def test_dispatch_failure_bad_everything(self, get_mock, post_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.info') @patch('requests.put') @@ -721,7 +729,8 @@ def test_dispatch_success_bad_policy(self, get_mock, post_mock, put_mock, log_mo rule_name='rule_name', alert=get_alert(context=ctx))) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.info') @patch('requests.put') @@ -755,7 +764,8 @@ def test_dispatch_success_no_merge_response(self, get_mock, post_mock, put_mock, rule_name='rule_name', alert=get_alert(context=ctx))) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @patch('requests.post') @@ -777,7 +787,7 @@ def test_dispatch_no_dispatch_no_incident_response(self, get_mock, post_mock, lo rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @patch('requests.post') @@ -801,7 +811,7 @@ def test_dispatch_no_dispatch_no_incident_event(self, get_mock, post_mock, log_m rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @patch('requests.post') @@ -825,7 +835,7 @@ def test_dispatch_no_dispatch_no_incident_key(self, get_mock, post_mock, log_moc rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @patch('requests.post') @@ -846,7 +856,7 @@ def test_dispatch_bad_dispatch(self, get_mock, post_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @@ -862,7 +872,7 @@ def test_dispatch_bad_email(self, get_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') def test_dispatch_bad_descriptor(self, log_mock): @@ -871,4 +881,4 @@ def test_dispatch_bad_descriptor(self, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, 'bad_descriptor') diff --git a/tests/unit/stream_alert_alert_processor/test_outputs/test_phantom.py b/tests/unit/stream_alert_alert_processor/test_outputs/test_phantom.py index c3456c72a..09f278ea7 100644 --- a/tests/unit/stream_alert_alert_processor/test_outputs/test_phantom.py +++ b/tests/unit/stream_alert_alert_processor/test_outputs/test_phantom.py @@ -20,7 +20,8 @@ from stream_alert.alert_processor.outputs.phantom import PhantomOutput from stream_alert_cli.helpers import put_mock_creds -from tests.unit.stream_alert_alert_processor import CONFIG, FUNCTION_NAME, KMS_ALIAS, REGION +from tests.unit.stream_alert_alert_processor import \ + ACCOUNT_ID, CONFIG, FUNCTION_NAME, KMS_ALIAS, REGION from tests.unit.stream_alert_alert_processor.helpers import get_alert, remove_temp_secrets @@ -36,7 +37,7 @@ class TestPhantomOutput(object): def setup(self): """Setup before each method""" - self._dispatcher = PhantomOutput(REGION, FUNCTION_NAME, CONFIG) + self._dispatcher = PhantomOutput(REGION, ACCOUNT_ID, FUNCTION_NAME, CONFIG) remove_temp_secrets() output_name = self._dispatcher.output_cred_name(self.DESCRIPTOR) put_mock_creds(output_name, self.CREDS, self._dispatcher.secrets_bucket, REGION, KMS_ALIAS) @@ -56,7 +57,8 @@ def test_dispatch_existing_container(self, post_mock, get_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.info') @patch('requests.get') @@ -74,7 +76,8 @@ def test_dispatch_new_container(self, post_mock, get_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @patch('requests.get') @@ -93,7 +96,7 @@ def test_dispatch_container_failure(self, post_mock, get_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @patch('requests.get') @@ -112,7 +115,7 @@ def test_dispatch_check_container_error(self, post_mock, get_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @patch('requests.get') @@ -130,7 +133,7 @@ def test_dispatch_check_container_no_response(self, post_mock, get_mock, log_moc rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @patch('requests.get') @@ -149,7 +152,7 @@ def test_dispatch_setup_container_error(self, post_mock, get_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @patch('requests.get') @@ -168,7 +171,7 @@ def test_dispatch_failure(self, post_mock, get_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') def test_dispatch_bad_descriptor(self, log_error_mock): @@ -177,7 +180,8 @@ def test_dispatch_bad_descriptor(self, log_error_mock): rule_name='rule_name', alert=get_alert())) - log_error_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_error_mock.assert_called_with('Failed to send alert to %s:%s', + self.SERVICE, 'bad_descriptor') @patch('stream_alert.alert_processor.outputs.output_base.OutputDispatcher._get_request') @patch('stream_alert.alert_processor.outputs.output_base.OutputDispatcher._post_request') diff --git a/tests/unit/stream_alert_alert_processor/test_outputs/test_slack.py b/tests/unit/stream_alert_alert_processor/test_outputs/test_slack.py index 0a42894d8..7361a36d5 100644 --- a/tests/unit/stream_alert_alert_processor/test_outputs/test_slack.py +++ b/tests/unit/stream_alert_alert_processor/test_outputs/test_slack.py @@ -21,7 +21,8 @@ from stream_alert.alert_processor.outputs.slack import SlackOutput from stream_alert_cli.helpers import put_mock_creds -from tests.unit.stream_alert_alert_processor import CONFIG, FUNCTION_NAME, KMS_ALIAS, REGION +from tests.unit.stream_alert_alert_processor import \ + ACCOUNT_ID, CONFIG, FUNCTION_NAME, KMS_ALIAS, REGION from tests.unit.stream_alert_alert_processor.helpers import ( get_random_alert, get_alert, @@ -40,7 +41,7 @@ class TestSlackOutput(object): def setup(self): """Setup before each method""" - self._dispatcher = SlackOutput(REGION, FUNCTION_NAME, CONFIG) + self._dispatcher = SlackOutput(REGION, ACCOUNT_ID, FUNCTION_NAME, CONFIG) remove_temp_secrets() output_name = self._dispatcher.output_cred_name(self.DESCRIPTOR) put_mock_creds(output_name, self.CREDS, self._dispatcher.secrets_bucket, REGION, KMS_ALIAS) @@ -179,7 +180,8 @@ def test_dispatch_success(self, url_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Successfully sent alert to %s', self.SERVICE) + log_mock.assert_called_with('Successfully sent alert to %s:%s', + self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') @patch('requests.post') @@ -193,7 +195,7 @@ def test_dispatch_failure(self, url_mock, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, self.DESCRIPTOR) @patch('logging.Logger.error') def test_dispatch_bad_descriptor(self, log_mock): @@ -202,4 +204,4 @@ def test_dispatch_bad_descriptor(self, log_mock): rule_name='rule_name', alert=get_alert())) - log_mock.assert_called_with('Failed to send alert to %s', self.SERVICE) + log_mock.assert_called_with('Failed to send alert to %s:%s', self.SERVICE, 'bad_descriptor') diff --git a/tests/unit/stream_alert_cli/terraform/test_alert_processor.py b/tests/unit/stream_alert_cli/terraform/test_alert_processor.py index 9309b81d5..c507df479 100644 --- a/tests/unit/stream_alert_cli/terraform/test_alert_processor.py +++ b/tests/unit/stream_alert_cli/terraform/test_alert_processor.py @@ -37,8 +37,13 @@ def test_generate_all_options(self): 'alert_processor_iam': { 'account_id': '12345678910', 'kms_key_arn': '${aws_kms_key.stream_alert_secrets.arn}', - 'output_lambda_functions': ['test-lambda-output'], - 'output_s3_buckets': ['test-s3-output'], + 'output_lambda_functions': [ + 'unit_test_function', + 'unit_test_qualified_function' + ], + 'output_s3_buckets': ['unit.test.bucket.name'], + 'output_sns_topics': ['unit_test_topic_name'], + 'output_sqs_queues': ['unit_test_queue_name'], 'prefix': 'unit-testing', 'region': 'us-west-1', 'role_id': '${module.alert_processor_lambda.role_id}', @@ -75,9 +80,13 @@ def test_generate_all_options(self): def test_generate_minimal_options(self): """CLI - Terraform Generate Alert Processor - Minimal Options""" - for key in ['log_level', 'log_retention_days', 'metric_alarms', 'outputs', 'vpc_config']: + # Remove extra Lambda options + for key in ['log_level', 'log_retention_days', 'metric_alarms', 'vpc_config']: del self.alert_proc_config[key] + # Remove all outputs from the config + self.config['outputs'] = {} + result = alert_processor.generate_alert_processor(config=self.config) expected = { @@ -87,6 +96,8 @@ def test_generate_minimal_options(self): 'kms_key_arn': '${aws_kms_key.stream_alert_secrets.arn}', 'output_lambda_functions': [], 'output_s3_buckets': [], + 'output_sns_topics': [], + 'output_sqs_queues': [], 'prefix': 'unit-testing', 'region': 'us-west-1', 'role_id': '${module.alert_processor_lambda.role_id}', diff --git a/tests/unit/stream_alert_cli/test_outputs.py b/tests/unit/stream_alert_cli/test_outputs.py index 51aab701d..cb2f33d60 100644 --- a/tests/unit/stream_alert_cli/test_outputs.py +++ b/tests/unit/stream_alert_cli/test_outputs.py @@ -35,12 +35,15 @@ def test_load_output_config(): loaded_config_keys = sorted(config.keys()) expected_config_keys = [ - u'aws-firehose', - u'aws-lambda', - u'aws-s3', - u'pagerduty', - u'phantom', - u'slack'] + 'aws-firehose', + 'aws-lambda', + 'aws-s3', + 'aws-sns', + 'aws-sqs', + 'pagerduty', + 'phantom', + 'slack' + ] assert_list_equal(loaded_config_keys, expected_config_keys)