Add a integration with Demisto (#408)

* Create a DemistoClient for publishing Falco alerts to Demisto

* Extract a function for extracting description from Falco output

* Add a playbook which creates a Falco alert as a Demisto incident

* Add a Kubeless Demisto Handler for Demisto integration

* Document the integration with Demisto

* Allow changing SSL certificate verification

* Fix naming for playbook specs

* Call to lower() before checking value of VERIFY_SSL. Allow case insensitive.
This commit is contained in:
Néstor Salceda 2018-10-10 19:28:35 +02:00 committed by Mark Stemm
parent 0499811762
commit f746c4cd57
6 changed files with 209 additions and 7 deletions

View File

@ -162,3 +162,19 @@ Kubernetes.
So as soon as we notice someone wrote under /bin (and additional binaries) or
/etc, we disconnect that pod. It's like a trap for our attackers.
### Create an incident in Demisto
This playbook creates an incident in Demisto
```
./deploy_playbook -p demisto -t "falco.*.*" -e DEMISTO_API_KEY=XxXxxXxxXXXx -e DEMISTO_BASE_URL=https://..."
```
#### Parameters
* DEMISTO_API_KEY: This is the API key used for authenticating against Demisto. Create one under settings -> API keys
* DEMISTO_BASE_URL: This is the base URL where your Demisto server lives on. Ensure there's no trailing slash.
* VERIFY_SSL: Verify SSL certificates for HTTPS requests. By default is enabled.
In this example, when Falco raises any kind of alert, the alert will be created in Demisto

View File

@ -0,0 +1,22 @@
import sys
import os.path
sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__))))
import os
import playbooks
from playbooks import infrastructure
def _to_bool(value):
return value.lower() in ('yes', 'true', '1')
playbook = playbooks.CreateIncidentInDemisto(
infrastructure.DemistoClient(os.environ['DEMISTO_API_KEY'],
os.environ['DEMISTO_BASE_URL']
verify_ssl=_to_bool(os.environ.get('VERIFY_SSL', 'True')))
)
def handler(event, context):
playbook.run(event['data'])

View File

@ -23,7 +23,7 @@ class AddMessageToSlack:
def _build_slack_message(self, alert):
return {
'text': self._output(alert),
'text': _output_from_alert(alert),
'attachments': [{
'color': self._color_from(alert['priority']),
'fields': [
@ -56,12 +56,6 @@ class AddMessageToSlack:
}]
}
def _output(self, alert):
output = alert['output'].split(': ')[1]
priority_plus_whitespace_length = len(alert['priority']) + 1
return output[priority_plus_whitespace_length:]
_COLORS = {
'Emergency': '#b12737',
'Alert': '#f24141',
@ -77,6 +71,13 @@ class AddMessageToSlack:
return self._COLORS.get(priority, '#eeeeee')
def _output_from_alert(alert):
output = alert['output'].split(': ')[1]
priority_plus_whitespace_length = len(alert['priority']) + 1
return output[priority_plus_whitespace_length:]
class TaintNode:
def __init__(self, k8s_client, key, value, effect):
self._k8s_client = k8s_client
@ -99,3 +100,40 @@ class NetworkIsolatePod:
pod = alert['output_fields']['k8s.pod.name']
self._k8s_client.add_label_to_pod(pod, 'isolated', 'true')
class CreateIncidentInDemisto:
def __init__(self, demisto_client):
self._demisto_client = demisto_client
def run(self, alert):
incident = {
'type': 'Policy Violation',
'name': alert['rule'],
'details': _output_from_alert(alert),
'severity': self._severity_from(alert['priority']),
'occurred': alert['time'],
'labels': [
{'type': 'Brand', 'value': 'Sysdig'},
{'type': 'Application', 'value': 'Falco'},
{'type': 'container.id', 'value': alert['output_fields']['container.id']},
{'type': 'k8s.pod.name', 'value': alert['output_fields']['k8s.pod.name']}
]
}
self._demisto_client.create_incident(incident)
return incident
def _severity_from(self, priority):
return self._SEVERITIES.get(priority, 0)
_SEVERITIES = {
'Emergency': 4,
'Alert': 4,
'Critical': 4,
'Error': 3,
'Warning': 2,
'Notice': 1,
'Informational': 5,
'Debug': 5,
}

View File

@ -1,5 +1,6 @@
import os
import json
import http
from kubernetes import client, config
import requests
@ -72,3 +73,26 @@ class SlackClient:
def post_message(self, message):
requests.post(self._slack_webhook_url,
data=json.dumps(message))
class DemistoClient:
def __init__(self, api_key, base_url, verify_ssl=True):
self._api_key = api_key
self._base_url = base_url
self._verify_ssl = verify_ssl
def create_incident(self, incident):
response = requests.post(self._base_url + '/incident',
headers=self._headers(),
data=json.dumps(incident),
verify=self._verify_ssl)
if response.status_code != http.HTTPStatus.CREATED:
raise RuntimeError(response.text)
def _headers(self):
return {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': self._api_key,
}

View File

@ -0,0 +1,32 @@
from mamba import description, it, context, before
from expects import expect, raise_error
import os
from playbooks import infrastructure
with description(infrastructure.DemistoClient) as self:
with before.each:
self.demisto_client = infrastructure.DemistoClient(
os.environ['DEMISTO_API_KEY'],
os.environ['DEMISTO_BASE_URL'],
verify_ssl=False
)
with it('creates an incident'):
incident = {
"type": "Policy Violation",
"name": "Falco incident",
"severity": 2,
"details": "Some incident details"
}
self.demisto_client.create_incident(incident)
with context('when an error happens'):
with it('raises an exception'):
incident = {}
expect(lambda: self.demisto_client.create_incident(incident)).\
to(raise_error(RuntimeError))

View File

@ -0,0 +1,70 @@
from mamba import description, it, before, context
from expects import expect, have_key, have_keys, contain
from doublex import Spy
from doublex_expects import have_been_called_with
from playbooks import infrastructure
import playbooks
import os
with description(playbooks.CreateIncidentInDemisto) as self:
with before.each:
self.demisto_client = Spy(infrastructure.DemistoClient)
self.playbook = playbooks.CreateIncidentInDemisto(self.demisto_client)
with context('when publishing a message to slack'):
with before.each:
self.alert = {
"output": "10:22:15.576767292: Notice Unexpected setuid call by non-sudo, non-root program (user=bin cur_uid=2 parent=event_generator command=event_generator uid=root) k8s.pod=falco-event-generator-6fd89678f9-cdkvz container=1c76f49f40b4",
"output_fields": {
"container.id": "1c76f49f40b4",
"evt.arg.uid": "root",
"evt.time": 1527157335576767292,
"k8s.pod.name": "falco-event-generator-6fd89678f9-cdkvz",
"proc.cmdline": "event_generator ",
"proc.pname": "event_generator",
"user.name": "bin",
"user.uid": 2
},
"priority": "Notice",
"rule": "Non sudo setuid",
"time": "2018-05-24T10:22:15.576767292Z"
}
self.incident = self.playbook.run(self.alert)
with it('creates incident in demisto'):
expect(self.demisto_client.create_incident).to(have_been_called_with(self.incident))
with it('sets incident type as Policy Violation'):
expect(self.incident).to(have_key('type', 'Policy Violation'))
with it('includes rule name'):
expect(self.incident).to(have_key('name', 'Non sudo setuid'))
with it('includes falco output'):
falco_output = 'Unexpected setuid call by non-sudo, non-root program (user=bin cur_uid=2 parent=event_generator command=event_generator uid=root) k8s.pod=falco-event-generator-6fd89678f9-cdkvz container=1c76f49f40b4'
expect(self.incident).to(have_key('details', falco_output))
with it('includes severity'):
expect(self.incident).to(have_key('severity', 1))
with it('includes time when alert happened'):
expect(self.incident).to(have_key('occurred', "2018-05-24T10:22:15.576767292Z"))
with context('when adding labels'):
with it('includes Sysdig as Brand'):
expect(self.incident['labels']).to(contain(have_keys(type='Brand', value='Sysdig')))
with it('includes Falco as Application'):
expect(self.incident['labels']).to(contain(have_keys(type='Application', value='Falco')))
with it('includes container.id'):
expect(self.incident['labels']).to(contain(have_keys(type='container.id', value='1c76f49f40b4')))
with it('includes k8s.pod.name'):
expect(self.incident['labels']).to(contain(have_keys(type='k8s.pod.name', value='falco-event-generator-6fd89678f9-cdkvz')))