mirror of
https://forgejo.altau.su/lego/lego-monitoring.git
synced 2026-03-10 04:41:10 +00:00
vulnix integration
This commit is contained in:
parent
758438382d
commit
436855d8c1
11 changed files with 172 additions and 2 deletions
|
|
@ -65,6 +65,15 @@
|
||||||
buildInputs = old.buildInputs or [ ] ++ [ _prev.setuptools ];
|
buildInputs = old.buildInputs or [ ] ++ [ _prev.setuptools ];
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
lego-monitoring = _prev.lego-monitoring.overrideAttrs (
|
||||||
|
old: {
|
||||||
|
postPatch = ''
|
||||||
|
substituteInPlace src/lego_monitoring/core/const.py \
|
||||||
|
--replace-fail 'VULNIX_PATH: str = ...' 'VULNIX_PATH = "${lib.getExe pkgs.vulnix}"'
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
# This example is only using x86_64-linux
|
# This example is only using x86_64-linux
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ package:
|
||||||
|
|
||||||
let
|
let
|
||||||
tempSensorOptions = (import ./submodules/tempSensorOptions.nix) { inherit lib; };
|
tempSensorOptions = (import ./submodules/tempSensorOptions.nix) { inherit lib; };
|
||||||
|
vulnixWhitelistRule = (import ./submodules/vulnixWhitelistRule.nix) { inherit lib; };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.services.lego-monitoring = {
|
options.services.lego-monitoring = {
|
||||||
|
|
@ -19,6 +20,7 @@ in
|
||||||
"start"
|
"start"
|
||||||
"stop"
|
"stop"
|
||||||
"temp"
|
"temp"
|
||||||
|
"vulnix"
|
||||||
]);
|
]);
|
||||||
default = [ ];
|
default = [ ];
|
||||||
description = "List of enabled check sets. Each check set is a module which checks something and generates alerts based on check results.";
|
description = "List of enabled check sets. Each check set is a module which checks something and generates alerts based on check results.";
|
||||||
|
|
@ -63,12 +65,40 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
vulnix = {
|
||||||
|
whitelist = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (lib.types.submodule vulnixWhitelistRule);
|
||||||
|
default = { };
|
||||||
|
description = "Whitelist rules for vulnix. Attr name is package with version, package name, or `*`.";
|
||||||
|
example = lib.literalExpression ''{
|
||||||
|
"ffmpeg-3.4.2" = {
|
||||||
|
cve = [ "CVE-2018-6912" "CVE-2018-7557" ];
|
||||||
|
until = "2018-05-01";
|
||||||
|
issueUrl = "https://issues.example.com/29952";
|
||||||
|
};
|
||||||
|
}'';
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = let
|
config = let
|
||||||
cfg = config.services.lego-monitoring;
|
cfg = config.services.lego-monitoring;
|
||||||
json = pkgs.formats.json {};
|
json = pkgs.formats.json {};
|
||||||
|
toml = pkgs.formats.toml {};
|
||||||
|
|
||||||
|
# This monstrous incantation has the effect of converting the options to snake_case
|
||||||
|
# and removing those that are null (because TOML does not support null values)
|
||||||
|
vulnixWhitelistFile = toml.generate "vulnix-whitelist.toml" (lib.attrsets.filterAttrsRecursive (
|
||||||
|
k: v: v != null
|
||||||
|
) (
|
||||||
|
lib.mapAttrs (_: rule: {
|
||||||
|
inherit (rule) cve until;
|
||||||
|
issue_url = rule.issueUrl;
|
||||||
|
}) cfg.checks.vulnix.whitelist
|
||||||
|
));
|
||||||
|
|
||||||
serviceConfigFile = json.generate "config.json" {
|
serviceConfigFile = json.generate "config.json" {
|
||||||
enabled_check_sets = cfg.enabledCheckSets;
|
enabled_check_sets = cfg.enabledCheckSets;
|
||||||
telegram = with cfg.telegram; {
|
telegram = with cfg.telegram; {
|
||||||
|
|
@ -88,6 +118,8 @@ in
|
||||||
}) sensorCfg.readings;
|
}) sensorCfg.readings;
|
||||||
|
|
||||||
}) cfg.checks.temp.sensors;
|
}) cfg.checks.temp.sensors;
|
||||||
|
|
||||||
|
vulnix.whitelist_path = vulnixWhitelistFile;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in lib.mkIf cfg.enable {
|
in lib.mkIf cfg.enable {
|
||||||
|
|
|
||||||
27
modules/submodules/vulnixWhitelistRule.nix
Normal file
27
modules/submodules/vulnixWhitelistRule.nix
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
}:
|
||||||
|
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
cve = lib.mkOption {
|
||||||
|
type = lib.types.nullOr (lib.types.listOf lib.types.str);
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
List of CVE identifiers to match. The whitelist rule is valid as long as the detected CVEs are a subset of the CVEs listed here.
|
||||||
|
If additional CVEs are detected, this whitelist rule is not effective anymore. If null, all CVEs are matched.'';
|
||||||
|
};
|
||||||
|
until = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Date in the form "YYYY-MM-DD" which confines this rule's lifetime. Null means forever.
|
||||||
|
On the specified date and later, this whitelist rule is not effective anymore.'';
|
||||||
|
};
|
||||||
|
issueUrl = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = "URL or list of URLs that point to any issue tracker. Informational only.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -56,6 +56,7 @@ async def async_main():
|
||||||
],
|
],
|
||||||
"stop": [], # this is checked later
|
"stop": [], # this is checked later
|
||||||
"temp": [interval_checker(checks.temp_check, datetime.timedelta(minutes=5))],
|
"temp": [interval_checker(checks.temp_check, datetime.timedelta(minutes=5))],
|
||||||
|
"vulnix": [interval_checker(checks.vulnix_check, datetime.timedelta(days=3))],
|
||||||
}
|
}
|
||||||
|
|
||||||
checkers = []
|
checkers = []
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ class AlertType(StrEnum):
|
||||||
BOOT = "BOOT"
|
BOOT = "BOOT"
|
||||||
TEMP = "TEMP"
|
TEMP = "TEMP"
|
||||||
TEST = "TEST"
|
TEST = "TEST"
|
||||||
# ERROR = "ERROR"
|
VULN = "VULN"
|
||||||
|
ERROR = "ERROR"
|
||||||
# RAM = "RAM"
|
# RAM = "RAM"
|
||||||
# CPU = "CPU"
|
# CPU = "CPU"
|
||||||
# VULN = "VULN"
|
|
||||||
# LOGIN = "LOGIN"
|
# LOGIN = "LOGIN"
|
||||||
# SMART = "SMART" # TODO
|
# SMART = "SMART" # TODO
|
||||||
# RAID = "RAID"
|
# RAID = "RAID"
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
from .temp import temp_check
|
from .temp import temp_check
|
||||||
|
from .vulnix import vulnix_check
|
||||||
|
|
|
||||||
48
src/lego_monitoring/checks/vulnix/__init__.py
Normal file
48
src/lego_monitoring/checks/vulnix/__init__.py
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
from lego_monitoring.alerting import alerts
|
||||||
|
from lego_monitoring.alerting.enum import AlertType, Severity
|
||||||
|
|
||||||
|
from .vulnix import get_vulnix_output
|
||||||
|
|
||||||
|
IS_TESTING = False
|
||||||
|
|
||||||
|
|
||||||
|
def vulnix_check() -> list[alerts.Alert]:
|
||||||
|
alert_list = []
|
||||||
|
try:
|
||||||
|
vulnix_output = get_vulnix_output(IS_TESTING)
|
||||||
|
except Exception as e:
|
||||||
|
alerts.send_alert(
|
||||||
|
alerts.Alert(
|
||||||
|
alert_type=AlertType.ERROR,
|
||||||
|
message=f"Exception {type(e).__name__} while calling vulnix: {e}",
|
||||||
|
severity=Severity.CRITICAL,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
for finding in vulnix_output:
|
||||||
|
if not IS_TESTING:
|
||||||
|
non_whitelisted_cves = [k for k in finding.description if k not in finding.whitelisted]
|
||||||
|
else:
|
||||||
|
non_whitelisted_cves = finding.description.keys()
|
||||||
|
if len(non_whitelisted_cves) == 0:
|
||||||
|
continue
|
||||||
|
message = f"New findings in derivation <code>{finding.derivation}</code>:"
|
||||||
|
for cve in non_whitelisted_cves:
|
||||||
|
if cve in finding.cvssv3_basescore:
|
||||||
|
score_str = f"(CVSSv3 = {finding.cvssv3_basescore[cve]})"
|
||||||
|
else:
|
||||||
|
score_str = "(not scored by CVSSv3)"
|
||||||
|
message += f'\n* <a href="https://nvd.nist.gov/vuln/detail/{cve}">{cve}</a> - {finding.description[cve]} {score_str}'
|
||||||
|
|
||||||
|
alert = alerts.Alert(
|
||||||
|
alert_type=AlertType.VULN,
|
||||||
|
message=message,
|
||||||
|
severity=Severity.WARNING,
|
||||||
|
)
|
||||||
|
alert_list.append(alert)
|
||||||
|
|
||||||
|
if IS_TESTING:
|
||||||
|
alert_list[0].message += "\n(just testing)"
|
||||||
|
return [alert_list[0]]
|
||||||
|
else:
|
||||||
|
return alert_list
|
||||||
41
src/lego_monitoring/checks/vulnix/vulnix.py
Normal file
41
src/lego_monitoring/checks/vulnix/vulnix.py
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from lego_monitoring.core import cvars
|
||||||
|
from lego_monitoring.core.const import VULNIX_PATH
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class VulnixPackageFindings:
|
||||||
|
pname: str
|
||||||
|
version: str
|
||||||
|
derivation: str
|
||||||
|
whitelisted: list[str]
|
||||||
|
cvssv3_basescore: dict[str, float]
|
||||||
|
description: dict[str, float]
|
||||||
|
|
||||||
|
|
||||||
|
def get_vulnix_output(is_testing=False) -> list[VulnixPackageFindings]:
|
||||||
|
whitelist_path = cvars.config.get().checks.vulnix.whitelist_path
|
||||||
|
cmd = [VULNIX_PATH, "--system", "-w", whitelist_path, "--json"]
|
||||||
|
if is_testing:
|
||||||
|
cmd.append("-s")
|
||||||
|
vulnix_run = subprocess.run(cmd, capture_output=True, check=False)
|
||||||
|
if vulnix_run.returncode not in range(0, 3):
|
||||||
|
logging.error(f"Vulnix returned error code {vulnix_run.returncode}, stderr: {vulnix_run.stderr}")
|
||||||
|
raise Exception(f"vulnix return error code {vulnix_run.returncode}, check logs")
|
||||||
|
vulnix_findings_json = json.loads(vulnix_run.stdout)
|
||||||
|
vulnix_findings = [
|
||||||
|
VulnixPackageFindings(
|
||||||
|
pname=f["pname"],
|
||||||
|
version=f["version"],
|
||||||
|
derivation=f["derivation"],
|
||||||
|
whitelisted=f["whitelisted"],
|
||||||
|
cvssv3_basescore=f["cvssv3_basescore"],
|
||||||
|
description=f["description"],
|
||||||
|
)
|
||||||
|
for f in vulnix_findings_json
|
||||||
|
]
|
||||||
|
return vulnix_findings
|
||||||
|
|
@ -4,11 +4,13 @@ from dataclasses import dataclass
|
||||||
from alt_utils import NestedDeserializableDataclass
|
from alt_utils import NestedDeserializableDataclass
|
||||||
|
|
||||||
from .checks.temp import TempCheckConfig
|
from .checks.temp import TempCheckConfig
|
||||||
|
from .checks.vulnix import VulnixCheckConfig
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ChecksConfig(NestedDeserializableDataclass):
|
class ChecksConfig(NestedDeserializableDataclass):
|
||||||
temp: TempCheckConfig
|
temp: TempCheckConfig
|
||||||
|
vulnix: VulnixCheckConfig
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
|
||||||
8
src/lego_monitoring/config/checks/vulnix.py
Normal file
8
src/lego_monitoring/config/checks/vulnix.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from alt_utils import NestedDeserializableDataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class VulnixCheckConfig(NestedDeserializableDataclass):
|
||||||
|
whitelist_path: str
|
||||||
1
src/lego_monitoring/core/const.py
Normal file
1
src/lego_monitoring/core/const.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
VULNIX_PATH: str = ... # path to vulnix executable
|
||||||
Loading…
Add table
Add a link
Reference in a new issue