1
0
Fork 1
mirror of https://github.com/thatmattlove/hyperglass.git synced 2026-01-17 08:48:05 +00:00

fix webhook handling, add slack support

This commit is contained in:
checktheroads 2020-04-16 11:12:37 -07:00
parent 744076c2fe
commit 57c812e2f7
4 changed files with 131 additions and 17 deletions

View file

@ -43,14 +43,22 @@ If syslogging is enabled, all of the same log messages written to the file and/o
If http logging is enabled, an HTTP POST will be sent to the configured target every time a query is submitted, _after_ it is validated.
| Parameter | Type | Default | Description |
| :----------- | :-----: | :------ | :-------------------------------------------------------------------------------------------------------- |
| `enable` | Boolean | `true` | Optionally disable webhooks even if configured. |
| `host` | String | | HTTP URL to webhook target. |
| `headers` | Mapping | | Any arbitrary mappings, which will be sent as HTTP headers. |
| `params` | Mapping | | Any arbitrary mappings, which will be sent as URL parameters (e.g. `http://example.com/log?param=value`). |
| `verify_ssl` | Boolean | `true` | Verify SSL certificate of target. |
| `timeout` | Integer | `5` | Time in seconds before request times out. |
| Parameter | Type | Default | Description |
| :----------- | :-----: | :---------- | :-------------------------------------------------------------------------------------------------------- |
| `enable` | Boolean | `true` | Optionally disable webhooks even if configured. |
| `host` | String | | HTTP URL to webhook target. |
| `headers` | Mapping | | Any arbitrary mappings, which will be sent as HTTP headers. |
| `params` | Mapping | | Any arbitrary mappings, which will be sent as URL parameters (e.g. `http://example.com/log?param=value`). |
| `verify_ssl` | Boolean | `true` | Verify SSL certificate of target. |
| `timeout` | Integer | `5` | Time in seconds before request times out. |
| `provider` | String | `'generic'` | Webhook provider. |
### Supported Providers
| Provider | Parameter Value |
| :-------------------------- | --------------: |
| [Slack](https://slack.com/) | `'slack'` |
| Generic | `'generic'` |
### Authentication
@ -68,7 +76,7 @@ If `api_key` is used, the header `X-API-Key: {key}` is added to the request, whe
### Webhook Data Structure
The webhook will POST JSON data in the following format:
If the `provider` field is set to `'generic'`, the webhook will POST JSON data in the following format:
```json
{

View file

@ -21,8 +21,8 @@ from pydantic import (
)
# Project
from hyperglass.constants import __version__
from hyperglass.models import HyperglassModel, HyperglassModelExtra
from hyperglass.constants import __version__
class Syslog(HyperglassModel):
@ -53,11 +53,11 @@ class Http(HyperglassModelExtra):
"""HTTP logging parameters."""
enable: StrictBool = True
provider: constr(regex=r"(slack|generic)") = "generic"
host: AnyHttpUrl
authentication: Optional[HttpAuth]
headers: Dict[StrictStr, Union[StrictStr, StrictInt, StrictBool, None]] = {}
params: Dict[StrictStr, Union[StrictStr, StrictInt, StrictBool, None]] = {}
key: Optional[StrictStr]
verify_ssl: StrictBool = True
timeout: Union[StrictFloat, StrictInt] = 5.0

View file

@ -106,16 +106,19 @@ async def query_hook(query, http_logging, log):
"""Log a query to an http server."""
import httpx
from hyperglass.models import Webhook
from hyperglass.util import parse_exception
if http_logging.key is not None:
query = {http_logging.key: query}
valid_webhook = Webhook(**query)
log.debug("Sending query data to webhook:\n{}", query)
format_map = {"generic": valid_webhook.export_dict, "slack": valid_webhook.slack}
format_func = format_map[http_logging.provider]
async with httpx.AsyncClient(**http_logging.decoded()) as client:
payload = format_func()
log.debug("Sending query data to webhook:\n{}", payload)
try:
response = await client.post(str(http_logging.host), json=query)
response = await client.post(str(http_logging.host), json=payload)
if response.status_code not in range(200, 300):
log.error(f"{response.status_code} error: {response.text}")

View file

@ -1,11 +1,14 @@
"""Data models used throughout hyperglass."""
# Standard Library
import re
from typing import TypeVar
from typing import TypeVar, Optional
# Third Party
from pydantic import HttpUrl, BaseModel, StrictInt, StrictFloat
from pydantic import HttpUrl, BaseModel, StrictInt, StrictStr, StrictFloat
# Project
from hyperglass.log import log
from hyperglass.util import clean_name
IntFloat = TypeVar("IntFloat", StrictInt, StrictFloat)
@ -132,3 +135,103 @@ class StrictBytes(bytes):
{str} -- Representation
"""
return f"StrictBytes({super().__repr__()})"
class WebhookHeaders(HyperglassModel):
"""Webhook data model."""
content_length: Optional[StrictStr]
accept: Optional[StrictStr]
user_agent: Optional[StrictStr]
content_type: Optional[StrictStr]
referer: Optional[StrictStr]
accept_encoding: Optional[StrictStr]
accept_language: Optional[StrictStr]
class Config:
"""Pydantic model config."""
fields = {
"content_length": "content-length",
"user_agent": "user-agent",
"content_type": "content-type",
"accept_encoding": "accept-encoding",
"accept_language": "accept-language",
}
class WebhookNetwork(HyperglassModel):
"""Webhook data model."""
prefix: Optional[StrictStr]
asn: Optional[StrictStr]
class Webhook(HyperglassModel):
"""Webhook data model."""
query_location: StrictStr
query_type: StrictStr
query_vrf: StrictStr
query_target: StrictStr
headers: WebhookHeaders
source: StrictStr
network: WebhookNetwork
def slack(self):
"""Format the webhook data as a Slack message."""
def make_field(key, value, code=False):
if code:
value = f"`{value}`"
return f"*{key}*\n{value}"
try:
header_data = []
for k, v in self.headers.dict(by_alias=True).items():
field = make_field(k, v, code=True)
header_data.append(field)
query_details = (
("Query Location", self.query_location),
("Query Type", self.query_type),
("Query VRF", self.query_vrf),
("Query Target", self.query_target),
)
query_data = []
for k, v in query_details:
field = make_field(k, v)
query_data.append({"type": "mrkdwn", "text": field})
source_details = (
("Source IP", self.source),
("Source Prefix", self.network.prefix),
("Source ASN", self.network.asn),
)
source_data = []
for k, v in source_details:
field = make_field(k, v, code=True)
source_data.append({"type": "mrkdwn", "text": field})
payload = {
"text": "hyperglass received a valid query with the following data",
"blocks": [
{"type": "section", "fields": query_data},
{"type": "divider"},
{"type": "section", "fields": source_data},
{"type": "divider"},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Headers*\n" + "\n".join(header_data),
},
},
],
}
log.debug("Created Slack webhook: {}", str(payload))
except Exception as err:
log.error("Error while creating webhook: {}", str(err))
payload = {}
return payload