Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions config.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,36 @@ icinga_accounts:
secret: false
default: ""

wazuh_accounts:
type: "array"
required: true
description: Wazuh account
items:
type: "object"
required: true
properties:
name:
description: "Name of the wazuh account"
type: "string"
secret: false
required: true
username:
type: "string"
description: "Username for wazuh"
secret: false
required: true
password:
type: "string"
description: "Password for wazuh"
required: true
secret: true
wazuh_endpoint:
type: "string"
description: "Wazuh endpoint (URL and port). E.g. https://wazuh.matrix.net:55000/"
required: true
secret: false
default: ""

alertmanager_accounts:
type: "array"
required: true
Expand Down Expand Up @@ -137,6 +167,35 @@ alertmanager_accounts:
secret: false
default: ""

elog_accounts:
type: "array"
required: true
description: ELOG account
items:
type: "object"
required: true
properties:
name:
description: "Name of the ELOG account"
type: "string"
secret: false
required: true
username:
type: "string"
description: "Username for ELOG"
required: true
password:
type: "string"
description: "Password for ELOG"
required: true
secret: true
elog_endpoint:
type: "string"
description: "ELOG endpoint"
required: true
secret: false
default: ""

sensor_cloud_account:
description: Which cloud account sensors requiring an openstack connection will use
type: "string"
Expand Down
9 changes: 9 additions & 0 deletions lib/apis/elog_api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import logging

logger = logging.getLogger(__name__)

logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
35 changes: 35 additions & 0 deletions lib/apis/elog_api/elog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import logging
from apis.elog_api.structs.elog_account import ElogAccount

logger = logging.getLogger(__name__)


def add_record_to_elog(elog_account: ElogAccount, subject: str, body: str) -> None:
"""
adds a new record to the ELOG server

:param subject: the title of the new record
:type subject: str
:param body: the text that goes in the record
:type body: str
"""
logger.info("Adding a new record to ELOG")
logger.info("subject = %s", subject)
logger.info("body = %s", body)
entry_data = {
"cmd": "Submit",
"Category": "Routine",
"Subject": subject,
"Author": elog_account.username,
"Encoding": "plain",
"Text": body,
}
session = elog_account.authenticate()
submit_response = session.post(
elog_account.elog_endpoint,
files={k: (None, v) for k, v in entry_data.items()},
)
submit_response.raise_for_status()
logger.debug("HTTP Status: %s", submit_response.status_code)
logger.debug(submit_response.text)
logger.info("New record added successfully to ELOG")
Empty file.
82 changes: 82 additions & 0 deletions lib/apis/elog_api/structs/elog_account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from dataclasses import dataclass, fields
from typing import Dict
from requests import Session


@dataclass
class ElogAccount:
"""
Elog account Parameters.
:param username: Elog API username
:param password: Elog API password
:param elog_endpoint: Elog API endpoint url
"""

username: str
password: str
elog_endpoint: str

@staticmethod
def from_dict(dictionary: Dict):
"""
Returns instance of this dataclass from a dictionary (for loading from config)
"""
field_set = {field.name for field in fields(ElogAccount) if field.init}
filtered_arg_dict = {
key: value for key, value in dictionary.items() if key in field_set
}
return ElogAccount(**filtered_arg_dict)

@staticmethod
def from_pack_config(pack_config: dict, elog_account_name: str):
"""
Returns instance of this dataclass from StackStorm pack config
:param pack_config: The pack config
:param elog_account_name: The account name to get from the config
:raises ValueError: When the pack config does not have elog_accounts defined
:raises KeyError: When the account does not appear in the given config
:return: (Dictionary) Elog account names and properties
"""
elog_accounts_config = pack_config.get("elog_accounts", None)

if elog_accounts_config is None:
raise ValueError("Pack config must contain the 'elog_accounts' field")

try:
key_value = {config["name"]: config for config in elog_accounts_config}
account_data = key_value[elog_account_name]
except KeyError as exc:
raise KeyError(
f"The account {elog_account_name} does not appear in the configuration"
) from exc

return ElogAccount.from_dict(account_data)

def authenticate(self) -> Session:
"""
authenticate against the ELOG server

This is the equivalent of this curl command to get a cookie
curl -k -c cookies.txt \
--form 'cmd=Login' \
--form 'uname=<username>' \
--form 'upassword=<password>' \
'<ELOG server URL>'
Using "files" with (None, value) causes requests to send
multipart/form-data, matching curl --form.

:return: a session object
"""
login_data = {
"cmd": "Login",
"uname": self.username,
"upassword": self.password,
}
session = Session()
session.verify = False
login_response = session.post(
self.elog_endpoint,
files={k: (None, v) for k, v in login_data.items()},
)
login_response.raise_for_status()
return session
9 changes: 9 additions & 0 deletions lib/apis/wazuh_api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import logging

logger = logging.getLogger(__name__)

logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
Empty file.
73 changes: 73 additions & 0 deletions lib/apis/wazuh_api/structs/wazuh_account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from dataclasses import dataclass, fields
from typing import Dict
import requests


@dataclass
class WazuhAccount:
"""
Wazuh account Parameters.
:param username: Wazuh API username
:param password: Wazuh API password
:param wazuh_endpoint: Wazuh API endpoint url
"""

username: str
password: str
wazuh_endpoint: str

@staticmethod
def from_dict(dictionary: Dict):
"""
Returns instance of this dataclass from a dictionary (for loading from config)
"""
field_set = {field.name for field in fields(WazuhAccount) if field.init}
filtered_arg_dict = {
key: value for key, value in dictionary.items() if key in field_set
}
return WazuhAccount(**filtered_arg_dict)

@staticmethod
def from_pack_config(pack_config: dict, wazuh_account_name: str):
"""
Returns instance of this dataclass from StackStorm pack config
:param pack_config: The pack config
:param wazuh_account_name: The account name to get from the config
:raises ValueError: When the pack config does not have wazuh_accounts defined
:raises KeyError: When the account does not appear in the given config
:return: (Dictionary) Wazuh account names and properties
"""
wazuh_accounts_config = pack_config.get("wazuh_accounts", None)

if wazuh_accounts_config is None:
raise ValueError("Pack config must contain the 'wazuh_accounts' field")

try:
key_value = {config["name"]: config for config in wazuh_accounts_config}
account_data = key_value[wazuh_account_name]
except KeyError as exc:
raise KeyError(
f"The account {wazuh_account_name} does not appear in the configuration"
) from exc

return WazuhAccount.from_dict(account_data)

def get_wazuh_token(self) -> str:
"""
Authenticates with the Wazuh API and returns a JWT token.
returns: API JWT token
"""
url = f"{self.wazuh_endpoint}/security/user/authenticate"
try:
# Wazuh uses Basic Auth to fetch the initial token
response = requests.get(
url, auth=(self.username, self.password), verify=False, timeout=60
)
response.raise_for_status()
except requests.exceptions.RequestException as e:
raise RuntimeError("Unable to retrieve a valid token: %s") from e
try:
token = response.json()["data"]["token"]
except (KeyError, TypeError, requests.exceptions.JSONDecodeError) as e:
raise RuntimeError("Wazuh API response did not contain a token") from e
return token
Loading