Skip to content
This repository has been archived by the owner on Jun 5, 2023. It is now read-only.

Slack Webhook Pipeline for Notifier #563

Merged
merged 13 commits into from Sep 14, 2017
7 changes: 7 additions & 0 deletions configs/forseti_conf.yaml.sample
Expand Up @@ -143,3 +143,10 @@ notifier:
sendgrid_api_key: ''
sender: ''
recipient: ''
# Slack webhook pipeline
# Create an incoming webhook in your organization's Slack setting, located at:
# https://[your_org].slack.com/apps/manage/custom-integrations
# Add the provided URL in the configuration below in `webhook_url`.
- name: slack_webhook_pipeline
configuration:
webhook_url: ''
94 changes: 94 additions & 0 deletions google/cloud/security/notifier/pipelines/slack_webhook_pipeline.py
@@ -0,0 +1,94 @@
# Copyright 2017 The Forseti Security Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Slack webhook pipeline to perform notifications"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super-nit: Don't forget the period to end the sentence.

import json
import requests

# TODO: Investigate improving so we can avoid the pylint disable.
# pylint: disable=line-too-long
from google.cloud.security.common.util import log_util
from google.cloud.security.notifier.pipelines import base_notification_pipeline as bnp
# pylint: enable=line-too-long


LOGGER = log_util.get_logger(__name__)

TEMP_DIR = '/tmp'
VIOLATIONS_JSON_FMT = 'violations.{}.{}.{}.json'
OUTPUT_TIMESTAMP_FMT = '%Y%m%dT%H%M%SZ'


class SlackWebhookPipeline(bnp.BaseNotificationPipeline):
"""Slack webhook pipeline to perform notifications"""

def _dump_slack_output(self, data, indent=0):
"""Iterate over a dictionary and output a custom formatted string

Args:
data (dict): a dictionary of violation data
indent (int): number of spaces for indentation

Returns:
output: a string formatted violation
"""
output = ''
for key, value in data.iteritems():
output += '\t' * indent + '*' + str(key) + '*:'
if isinstance(value, dict):
output += '\n' + self._dump_slack_output(value,
indent + 1) + '\n'
else:
if not value:
value = 'n/a'
output += '\t' * (indent + 1) + '`' + str(value) + '`\n'

return output

def _compose(self, **kwargs):
"""Composes the slack webhook content

Args:
**kwargs: Arbitrary keyword arguments.

Returns:
webhook_payload: a string formatted violation
"""

violation = kwargs.get('violation')

payload = {
'type': self.resource,
'details': json.loads(violation.get('violation_data'))
}

return self._dump_slack_output(payload)

def _send(self, **kwargs):
"""Sends a post to a Slack webhook url

Args:
**kwargs: Arbitrary keyword arguments.
payload: violation data for body of POST request\
"""
url = self.pipeline_config.get('webhook_url')
request = requests.post(url, json={'text': kwargs.get('payload')})

LOGGER.info(request)

def run(self):
"""Run the slack webhook pipeline"""
for violation in self.violations:
webhook_payload = self._compose(violation=violation)
self._send(payload=webhook_payload)
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -43,6 +43,7 @@
'PyYAML==3.12',
'ratelimiter==1.1.0',
'retrying==1.3.3',
'requests==2.18.4',
'sendgrid==3.6.3',
'SQLAlchemy==1.1.9',
'pygraph>=0.2.1',
Expand Down
54 changes: 54 additions & 0 deletions tests/notifier/pipelines/slack_webhhok_pipeline_test.py
@@ -0,0 +1,54 @@
# Copyright 2017 The Forseti Security Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests the email scanner summary pipeline."""

import mock
import unittest

from google.cloud.security.notifier.pipelines import slack_webhook_pipeline
from tests.unittest_utils import ForsetiTestCase


class SlackWebhookPipelineTest(ForsetiTestCase):
"""Tests for the slack_webhook_pipeline."""

def test_can_compose_slack_message(self):
"""Test that the slack message is built correctly."""

violation_data = """
{
"role": "READER",
"email": "",
"bucket": "test-bucket-world-readable-123",
"domain": "",
"entity": "allUsers"
}
"""

violation = {'violation_data': violation_data, 'resource_id': '123',
'rule_name': 'Public buckets (allUsers)', 'rule_index': 0L,
'violation_type': 'BUCKET_VIOLATION', 'id': 1L, 'resource_type': 'bucket'}

with mock.patch.object(slack_webhook_pipeline.SlackWebhookPipeline, '__init__', lambda x: None):
slack_pipeline = slack_webhook_pipeline.SlackWebhookPipeline()
slack_pipeline.resource = 'buckets_acl_violations'
actual_output = slack_pipeline._compose(violation=violation)

expected_output = "*type*:\t`buckets_acl_violations`\n*details*:\n\t*bucket*:\t\t`test-bucket-world-readable-123`\n\t*domain*:\t\t`n/a`\n\t*role*:\t\t`READER`\n\t*email*:\t\t`n/a`\n\t*entity*:\t\t`allUsers`"

self.assertEqual(expected_output.strip(), actual_output.strip())


if __name__ == '__main__':
unittest.main()