Beispiel #1
0
    def _create_dispatcher(self, output):
        """Create a dispatcher for the given output.

        Args:
            output (str): Alert output, e.g. "aws-sns:topic-name"

        Returns:
            OutputDispatcher: Based on the output type.
                Returns None if the output is invalid or not defined in the config.
        """
        try:
            service, descriptor = output.split(':')
        except ValueError:
            LOGGER.error(
                'Improperly formatted output [%s]. Outputs for rules must '
                'be declared with both a service and a descriptor for the '
                'integration (ie: \'slack:my_channel\')', output)
            return None

        if service not in self.config or descriptor not in self.config[service]:
            LOGGER.error('The output \'%s\' does not exist!', output)
            return None

        return StreamAlertOutput.create_dispatcher(service, self.region,
                                                   self.account_id,
                                                   self.prefix, self.config)
Beispiel #2
0
def configure_output(options):
    """Configure a new output for this service

    Args:
        options (argparser): Basically a namedtuple with the service setting
    """
    account_config = CONFIG['global']['account']
    region = account_config['region']
    prefix = account_config['prefix']
    kms_key_alias = account_config['kms_key_alias']
    # Verify that the word alias is not in the config.
    # It is interpolated when the API call is made.
    if 'alias/' in kms_key_alias:
        kms_key_alias = kms_key_alias.split('/')[1]

    # Retrieve the proper service class to handle dispatching the alerts of this services
    output = StreamAlertOutput.get_dispatcher(options.service)

    # If an output for this service has not been defined, the error is logged
    # prior to this
    if not output:
        return

    # get dictionary of OutputProperty items to be used for user prompting
    props = output.get_user_defined_properties()

    for name, prop in props.iteritems():
        # pylint: disable=protected-access
        props[name] = prop._replace(value=user_input(
            prop.description, prop.mask_input, prop.input_restrictions))

    output_config = CONFIG['outputs']
    service = output.__service__

    # If it exists already, ask for user input again for a unique configuration
    if config_outputs.output_exists(output_config, props, service):
        return configure_output(options)

    secrets_bucket = '{}.streamalert.secrets'.format(prefix)
    secrets_key = output.output_cred_name(props['descriptor'].value)

    # Encrypt the creds and push them to S3
    # then update the local output configuration with properties
    if config_outputs.encrypt_and_push_creds_to_s3(region, secrets_bucket,
                                                   secrets_key, props,
                                                   kms_key_alias):
        updated_config = output.format_output_config(output_config, props)
        output_config[service] = updated_config
        CONFIG.write()

        LOGGER_CLI.info(
            'Successfully saved \'%s\' output configuration for service \'%s\'',
            props['descriptor'].value, options.service)
    else:
        LOGGER_CLI.error(
            'An error occurred while saving \'%s\' '
            'output configuration for service \'%s\'',
            props['descriptor'].value, options.service)
Beispiel #3
0
def test_output_loading():
    """OutputDispatcher - Loading Output Classes"""
    loaded_outputs = set(StreamAlertOutput.get_all_outputs())
    # Add new outputs to this list to make sure they're loaded properly
    expected_outputs = {
        'aws-firehose', 'aws-lambda', 'aws-s3', 'jira', 'pagerduty',
        'pagerduty-v2', 'pagerduty-incident', 'phantom', 'slack'
    }
    assert_items_equal(loaded_outputs, expected_outputs)
Beispiel #4
0
def test_create_dispatcher():
    """StreamAlertOutput - Create Dispatcher"""
    dispatcher = StreamAlertOutput.create_dispatcher(
        'aws-s3',
        REGION,
        FUNCTION_NAME,
        CONFIG
    )
    assert_is_instance(dispatcher, S3Output)
Beispiel #5
0
def _setup_output_subparser(subparser):
    """Add the output subparser: manage.py output SERVICE"""
    outputs = sorted(StreamAlertOutput.get_all_outputs().keys())

    # Output parser arguments
    subparser.add_argument(
        'service',
        choices=outputs,
        metavar='SERVICE',
        help=
        'Create a new StreamAlert output for one of the available services: {}'
        .format(', '.join(outputs)))
Beispiel #6
0
def test_user_defined_properties():
    """OutputDispatcher - User Defined Properties"""
    for output in StreamAlertOutput.get_all_outputs().values():
        props = output.get_user_defined_properties()
        # The user defined properties should at a minimum contain a descriptor
        assert_is_not_none(props.get('descriptor'))
Beispiel #7
0
def test_get_dispatcher_bad(log_mock):
    """StreamAlertOutput - Get Invalid Dispatcher"""
    dispatcher = StreamAlertOutput.get_dispatcher('aws-s4')
    assert_is_none(dispatcher)
    log_mock.assert_called_with(
        'Designated output service [%s] does not exist', 'aws-s4')
Beispiel #8
0
def test_get_dispatcher_good():
    """StreamAlertOutput - Get Valid Dispatcher"""
    dispatcher = StreamAlertOutput.get_dispatcher('aws-s3')
    assert_is_not_none(dispatcher)
Beispiel #9
0
def output_handler(options, config):
    """Configure a new output for this service

    Args:
        options (argparse.Namespace): Basically a namedtuple with the service setting

    Returns:
        bool: False if errors occurred, True otherwise
    """
    account_config = config['global']['account']
    region = account_config['region']
    prefix = account_config['prefix']
    kms_key_alias = account_config['kms_key_alias']
    # Verify that the word alias is not in the config.
    # It is interpolated when the API call is made.
    if 'alias/' in kms_key_alias:
        kms_key_alias = kms_key_alias.split('/')[1]

    # Retrieve the proper service class to handle dispatching the alerts of this services
    output = StreamAlertOutput.get_dispatcher(options.service)

    # If an output for this service has not been defined, the error is logged
    # prior to this
    if not output:
        return False

    # get dictionary of OutputProperty items to be used for user prompting
    props = output.get_user_defined_properties()

    for name, prop in props.iteritems():
        # pylint: disable=protected-access
        props[name] = prop._replace(value=user_input(
            prop.description, prop.mask_input, prop.input_restrictions))

    output_config = config['outputs']
    service = output.__service__

    # If it exists already, ask for user input again for a unique configuration
    if output_exists(output_config, props, service):
        return output_handler(options, config)

    provider = OutputCredentialsProvider(service,
                                         config=config,
                                         region=region,
                                         prefix=prefix)
    result = provider.save_credentials(props['descriptor'].value,
                                       kms_key_alias, props)
    if not result:
        LOGGER.error(
            'An error occurred while saving \'%s\' '
            'output configuration for service \'%s\'',
            props['descriptor'].value, options.service)
        return False

    updated_config = output.format_output_config(output_config, props)
    output_config[service] = updated_config
    config.write()

    LOGGER.info(
        'Successfully saved \'%s\' output configuration for service \'%s\'',
        props['descriptor'].value, options.service)
    return True
Beispiel #10
0
def run(alert, region, function_name, config):
    """Send an Alert to its described outputs.

    Args:
        alert (dict): dictionary representating an alert with the
            following structure:

            {
                'record': record,
                'rule_name': rule.rule_name,
                'rule_description': rule.rule_function.__doc__,
                'log_source': str(payload.log_source),
                'log_type': payload.type,
                'outputs': rule.outputs,
                'source_service': payload.service,
                'source_entity': payload.entity
            }

        region (str): The AWS region 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

    Yields:
        (bool, str): Dispatch status and name of the output to the handler
    """
    if not validate_alert(alert):
        LOGGER.error('Invalid alert format:\n%s', json.dumps(alert, indent=2))
        return

    LOGGER.debug('Sending alert to outputs:\n%s', json.dumps(alert, indent=2))

    # strip out unnecessary keys and sort
    alert = _sort_dict(alert)

    outputs = alert['outputs']
    # Get the output configuration for this rule and send the alert to each
    for output in set(outputs):
        try:
            service, descriptor = output.split(':')
        except ValueError:
            LOGGER.error(
                'Improperly formatted output [%s]. Outputs for rules must '
                'be declared with both a service and a descriptor for the '
                'integration (ie: \'slack:my_channel\')', output)
            continue

        if service not in config or descriptor not in config[service]:
            LOGGER.error('The output \'%s\' does not exist!', output)
            continue

        # Retrieve the proper class to handle dispatching the alerts of this services
        dispatcher = StreamAlertOutput.create_dispatcher(
            service, region, function_name, config)

        if not dispatcher:
            continue

        LOGGER.debug('Sending alert to %s:%s', service, descriptor)

        sent = False
        try:
            sent = dispatcher.dispatch(descriptor=descriptor,
                                       rule_name=alert['rule_name'],
                                       alert=alert)

        except Exception as err:  # pylint: disable=broad-except
            LOGGER.exception(
                'An error occurred while sending alert '
                'to %s:%s: %s. alert:\n%s', service, descriptor, err,
                json.dumps(alert, indent=2))

        # Yield back the result to the handler
        yield sent, output