mirror of
https://github.com/falcosecurity/falco.git
synced 2025-08-21 07:43:27 +00:00
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:
parent
0499811762
commit
f746c4cd57
@ -162,3 +162,19 @@ Kubernetes.
|
|||||||
|
|
||||||
So as soon as we notice someone wrote under /bin (and additional binaries) or
|
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.
|
/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
|
||||||
|
22
integrations/kubernetes-response-engine/playbooks/demisto.py
Normal file
22
integrations/kubernetes-response-engine/playbooks/demisto.py
Normal 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'])
|
@ -23,7 +23,7 @@ class AddMessageToSlack:
|
|||||||
|
|
||||||
def _build_slack_message(self, alert):
|
def _build_slack_message(self, alert):
|
||||||
return {
|
return {
|
||||||
'text': self._output(alert),
|
'text': _output_from_alert(alert),
|
||||||
'attachments': [{
|
'attachments': [{
|
||||||
'color': self._color_from(alert['priority']),
|
'color': self._color_from(alert['priority']),
|
||||||
'fields': [
|
'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 = {
|
_COLORS = {
|
||||||
'Emergency': '#b12737',
|
'Emergency': '#b12737',
|
||||||
'Alert': '#f24141',
|
'Alert': '#f24141',
|
||||||
@ -77,6 +71,13 @@ class AddMessageToSlack:
|
|||||||
return self._COLORS.get(priority, '#eeeeee')
|
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:
|
class TaintNode:
|
||||||
def __init__(self, k8s_client, key, value, effect):
|
def __init__(self, k8s_client, key, value, effect):
|
||||||
self._k8s_client = k8s_client
|
self._k8s_client = k8s_client
|
||||||
@ -99,3 +100,40 @@ class NetworkIsolatePod:
|
|||||||
pod = alert['output_fields']['k8s.pod.name']
|
pod = alert['output_fields']['k8s.pod.name']
|
||||||
|
|
||||||
self._k8s_client.add_label_to_pod(pod, 'isolated', 'true')
|
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,
|
||||||
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
import http
|
||||||
|
|
||||||
from kubernetes import client, config
|
from kubernetes import client, config
|
||||||
import requests
|
import requests
|
||||||
@ -72,3 +73,26 @@ class SlackClient:
|
|||||||
def post_message(self, message):
|
def post_message(self, message):
|
||||||
requests.post(self._slack_webhook_url,
|
requests.post(self._slack_webhook_url,
|
||||||
data=json.dumps(message))
|
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,
|
||||||
|
}
|
||||||
|
@ -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))
|
@ -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')))
|
Loading…
Reference in New Issue
Block a user