Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Falco webhook #403

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ Webhooks
* [Slack](https://github.com/alerta/alerta/blob/master/alerta/webhooks/slack.py)
* [Stackdriver](https://github.com/alerta/alerta/blob/master/alerta/webhooks/stackdriver.py)
* [Telegram](https://github.com/alerta/alerta/blob/master/alerta/webhooks/telegram.py)
* [Falco](webhooks/falco)

Tests
-----
Expand Down
95 changes: 95 additions & 0 deletions webhooks/falco/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
Falco Webhook
==============

Receive [falco](https://falco.org/) alerts via `falcosidekick` webhook.

For help, join [![Slack chat](https://img.shields.io/badge/chat-on%20slack-blue?logo=slack)](https://slack.alerta.dev)

Falco webhook version support
-------------------------------

[TBD]

Installation
------------

Clone the GitHub repo and run:

$ python setup.py install

Or, to install remotely from GitHub run:

$ pip install git+https://github.com/alerta/alerta-contrib.git#subdirectory=webhooks/falco

Note: If Alerta is installed in a python virtual environment then plugins
need to be installed into the same environment for Alerta to dynamically
discover them.

Configuration
-------------

`falcosidekick` custom outputs are used here.

Specifically, the `webhook` output. Read more [here](https://github.com/falcosecurity/falcosidekick/blob/master/docs/outputs/webhook.md).

First, an Alerta Api Key has to be set. It can be created in the Alerta's UI, under "Api Keys" menu.

Then, note a custom field is being used to identify environments. This is set in the installation with `customfields="environment:Development"`. Set the right environment for your Falco deployment here.

Finally, if you are using Helm to install Falco, the webhook can be set like this:

``` shell
helm install falco -n falco --set driver.kind=modern_ebpf --set tty=true falcosecurity/falco \
--set falcosidekick.enabled=true \
--set falcosidekick.config.webhook.address="<alerta-url-protocol-here>://<alerta-url-here>/api/webhooks/falco" \
--set falcosidekick.config.webhook.minimumpriority=notice \
--set falcosidekick.config.webhook.customHeaders="X-Api-Key: <alerta-api-key-here>" \
--set falcosidekick.config.webhook.mutualtls=false \
--set falcosidekick.config.webhook.checkcert=false \
--set falcosidekick.config.customfields="environment:Development"
```

FalcoSidekick payload example
-----------------------------

This is an example of a payload sent by `falcosidekick`:

``` json
{
"uuid": "06f73663-b1d4-42e4-b236-eacbd2b96998",
"output": "20:39:17.500016161: Notice A shell was spawned in a container with an attached terminal (evt_type=execve user=root user_uid=0 user_loginuid=-1 process=sh proc_exepath=/bin/busybox parent=containerd-shim command=sh -c uptime terminal=34816 exe_flags=EXE_WRITABLE container_id=9273d0110c4e container_image=<NA> container_image_tag=<NA> container_name=<NA> k8s_ns=<NA> k8s_pod_name=<NA>)",
"priority": "Notice",
"rule": "Terminal shell in container",
"time": "2024-07-16T20:39:17.500016161Z",
"output_fields": {
"container.id": "9273d0110c4e",
"container.image.repository": "None",
"container.image.tag": "None",
"container.name": "None",
"evt.arg.flags": "EXE_WRITABLE",
"evt.time": 1721162357500016161,
"evt.type": "execve",
"k8s.ns.name": "None",
"k8s.pod.name": "None",
"proc.cmdline": "sh -c uptime",
"proc.exepath": "/bin/busybox",
"proc.name": "sh",
"proc.pname": "containerd-shim",
"proc.tty": 34816,
"user": "jdelacamara",
"user.loginuid": -1,
"user.name": "root",
"user.uid": 0,
"environment": "Development"
},
"source": "syscall",
"tags": [
"T1059",
"container",
"maturity_stable",
"mitre_execution",
"shell"
],
"hostname": "nixos"
}
```
127 changes: 127 additions & 0 deletions webhooks/falco/alerta_falco.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
from flask import current_app
from alerta.app import alarm_model
from alerta.models.alert import Alert
from alerta.webhooks import WebhookBase


class FalcoWebhook(WebhookBase):
"""
Falco webhook
"""

def incoming(self, query_string, payload):

additional_tags = []
additional_attributes = {}

# checking fields
#
expected_fields = [
'priority',
'hostname',
'rule',
'output_fields',
'source',
'output'
]
for field in expected_fields:
if field not in payload:
raise Exception(f'{field} not found in payload')
expected_fields_in_outputfields = ['environment']
for field in expected_fields_in_outputfields:
if field not in payload['output_fields']:
raise Exception(f'{field} not found in payload->output_fields')

# resource+event
#
# these are the fields used for de duplication,
# so we fill their values here
resource = f"{payload['hostname']}"
event = payload['rule']

# priority
#
# falco priorities:
# emergency, alert, critical, error, warning, notice,
# informational, debug
if payload['priority'].lower() in [
'emergency',
'alert',
'critical',
'error'
]:
severity = 'critical'
elif payload['priority'].lower() in [
'warning',
'notice',
'informational',
'debug'
]:
severity = 'warning'
else:
severity = alarm_model.DEFAULT_NORMAL_SEVERITY
additional_attributes['falco_priority'] = payload['priority']

# environment
#
# we set a custom field environment in our setup
environment = current_app.config['DEFAULT_ENVIRONMENT']
if 'output_fields' in payload and 'environment' in payload['output_fields']:
environment = payload['output_fields']['environment']

# attributes
#
attributes = additional_attributes

# tags
tags = []
if 'tags' in payload and isinstance(payload['tags'], list):
tags = additional_tags.extend(payload['tags'])
else:
tags = additional_tags

# group
#
# how to group
group = f"{payload['rule']}-{payload['source']}"

# value
#
value = payload['output']

# service
#
# service is a List
service = [payload['source']]

# origin
#
origin = f"{payload['hostname']}-{payload['source']}"

# event type
#
# in this case is a Falco Alert
event_type = 'falcoAlert'

# text
#
# the alert text
text = f"{severity}: {payload['output_fields']}"

return Alert(
# alerta will group by these
resource=resource,
event=event,
# ################
environment=environment,
severity=severity,
service=service,
group=group,
value=value,
text=text,
tags=tags,
origin=origin,
attributes=attributes,
event_type=event_type,
raw_data=payload
)
24 changes: 24 additions & 0 deletions webhooks/falco/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from setuptools import find_packages, setup

version = '0.0.4'

setup(
name='alerta-falco',
version=version,
description='Alerta webhook for Falco',
url='https://github.com/alerta/alerta-contrib',
license='MIT',
author='Juan Kungfoo @ binbash',
author_email='[email protected]',
packages=find_packages(),
py_modules=['alerta_falco'],
install_requires=[
],
include_package_data=True,
zip_safe=True,
entry_points={
'alerta.webhooks': [
'falco = alerta_falco:FalcoWebhook'
]
}
)
117 changes: 117 additions & 0 deletions webhooks/falco/test_falco.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import json
import unittest

import alerta_falco
from alerta.app import create_app, custom_webhooks


class FalcoWebhookTestCase(unittest.TestCase):

def setUp(self):

test_config = {
'TESTING': True,
'AUTH_REQUIRED': False
}
self.app = create_app(test_config)
self.client = self.app.test_client()
custom_webhooks.webhooks['falco'] = alerta_falco.FalcoWebhook(
)

self.headers = {
'Content-Type': 'application/json'
}

self.alert_id = 'f0c55228-c61d-462a-9aeb-f6048d37fdf6'

def test_falcowebhook(self):

payload_cmd = r"""
{
"uuid": "06f73663-b1d4-42e4-b236-eacbd2b96998",
"output": "20:39:17.500016161: Notice A shell was spawned in a container with an attached terminal (evt_type=execve user=root user_uid=0 user_loginuid=-1 process=sh proc_exepath=/bin/busybox parent=containerd-shim command=sh -c uptime terminal=34816 exe_flags=EXE_WRITABLE container_id=9273d0110c4e container_image=<NA> container_image_tag=<NA> container_name=<NA> k8s_ns=<NA> k8s_pod_name=<NA>)",
"priority": "Notice",
"rule": "Terminal shell in container",
"time": "2024-07-16T20:39:17.500016161Z",
"output_fields": {
"container.id": "9273d0110c4e",
"container.image.repository": "None",
"container.image.tag": "None",
"container.name": "None",
"evt.arg.flags": "EXE_WRITABLE",
"evt.time": 1721162357500016161,
"evt.type": "execve",
"k8s.ns.name": "None",
"k8s.pod.name": "None",
"proc.cmdline": "sh -c uptime",
"proc.exepath": "/bin/busybox",
"proc.name": "sh",
"proc.pname": "containerd-shim",
"proc.tty": 34816,
"user": "jdelacamara",
"user.loginuid": -1,
"user.name": "root",
"user.uid": 0,
"environment": "Development"
},
"source": "syscall",
"tags": [
"T1059",
"container",
"maturity_stable",
"mitre_execution",
"shell"
],
"hostname": "nixos"
}
"""

# Missing fields
payload_invalidcmd = r"""
{
"uuid": "06f73663-b1d4-42e4-b236-eacbd2b96998",
"time": "2024-07-16T20:39:17.500016161Z",
"output_fields": {
"container.id": "9273d0110c4e",
"container.image.repository": "None",
"container.image.tag": "None",
"container.name": "None",
"evt.arg.flags": "EXE_WRITABLE",
"evt.time": 1721162357500016161,
"evt.type": "execve",
"k8s.ns.name": "None",
"k8s.pod.name": "None",
"proc.cmdline": "sh -c uptime",
"proc.exepath": "/bin/busybox",
"proc.name": "sh",
"proc.pname": "containerd-shim",
"proc.tty": 34816,
"user": "jdelacamara",
"user.loginuid": -1,
"user.name": "root",
"user.uid": 0,
"environment": "Development"
},
"source": "syscall",
"tags": [
"T1059",
"container",
"maturity_stable",
"mitre_execution",
"shell"
],
"hostname": "nixos"
}
"""

# ack with missing fields
response = self.client.post('/webhooks/falco', data=payload_invalidcmd,
content_type='application/json', headers=self.headers)
self.assertEqual(response.status_code, 500)
data = json.loads(response.data.decode('utf-8'))

# ack
response = self.client.post(
'/webhooks/falco', data=payload_cmd, content_type='application/json', headers=self.headers)
self.assertEqual(response.status_code, 201)
data = json.loads(response.data.decode('utf-8'))
Loading