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:
parent
744076c2fe
commit
57c812e2f7
4 changed files with 131 additions and 17 deletions
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue