LibreNMS uses leading dot format (.1.3.6.1...) but SNMP returns OIDs without leading dot (1.3.6.1...). Normalize by prepending dot to sys_object_id before matching, and use starts_with instead of contains for more accurate prefix matching.
219 lines
5.6 KiB
Elixir
219 lines
5.6 KiB
Elixir
defmodule Towerops.DeviceProfiles do
|
|
@moduledoc """
|
|
Context for managing device SNMP profiles.
|
|
|
|
Provides functions for querying profiles, matching devices to profiles based on
|
|
detection rules, and retrieving sensor/processor/mempool definitions.
|
|
"""
|
|
|
|
import Ecto.Query
|
|
|
|
alias Towerops.DeviceProfiles.DetectionRule
|
|
alias Towerops.DeviceProfiles.DeviceProfile
|
|
alias Towerops.DeviceProfiles.MemPoolDefinition
|
|
alias Towerops.DeviceProfiles.OSDefinition
|
|
alias Towerops.DeviceProfiles.ProcessorDefinition
|
|
alias Towerops.DeviceProfiles.SensorDefinition
|
|
alias Towerops.DeviceProfiles.SensorState
|
|
alias Towerops.Repo
|
|
|
|
@doc """
|
|
Lists all enabled device profiles.
|
|
"""
|
|
def list_profiles do
|
|
Repo.all(from p in DeviceProfile, where: p.enabled == true, order_by: [asc: p.priority])
|
|
end
|
|
|
|
@doc """
|
|
Gets a single device profile by OS name.
|
|
"""
|
|
def get_profile(os) when is_binary(os) do
|
|
Repo.get_by(DeviceProfile, os: os)
|
|
end
|
|
|
|
@doc """
|
|
Gets a profile with all associations preloaded.
|
|
"""
|
|
def get_profile_with_associations(os) when is_binary(os) do
|
|
Repo.one(
|
|
from p in DeviceProfile,
|
|
where: p.os == ^os,
|
|
preload: [
|
|
:detection_rules,
|
|
:os_definitions,
|
|
:processor_definitions,
|
|
:mempool_definitions,
|
|
sensor_definitions: [:states]
|
|
]
|
|
)
|
|
end
|
|
|
|
@doc """
|
|
Matches a device to a profile based on system information.
|
|
|
|
Takes a map with :sys_descr and :sys_object_id keys and returns the
|
|
matching profile or nil.
|
|
"""
|
|
def match_profile(system_info) do
|
|
profiles = list_profiles_with_detection_rules()
|
|
|
|
Enum.find(profiles, fn profile ->
|
|
match_detection_rules?(profile.detection_rules, system_info)
|
|
end)
|
|
end
|
|
|
|
@doc """
|
|
Lists all sensor definitions for a given profile and sensor class.
|
|
"""
|
|
def list_sensor_definitions(profile_id, sensor_class) do
|
|
Repo.all(
|
|
from s in SensorDefinition,
|
|
where: s.device_profile_id == ^profile_id and s.sensor_class == ^sensor_class,
|
|
preload: [:states]
|
|
)
|
|
end
|
|
|
|
@doc """
|
|
Creates a new device profile.
|
|
"""
|
|
def create_profile(attrs \\ %{}) do
|
|
%DeviceProfile{}
|
|
|> DeviceProfile.changeset(attrs)
|
|
|> Repo.insert()
|
|
end
|
|
|
|
@doc """
|
|
Updates a device profile.
|
|
"""
|
|
def update_profile(%DeviceProfile{} = profile, attrs) do
|
|
profile
|
|
|> DeviceProfile.changeset(attrs)
|
|
|> Repo.update()
|
|
end
|
|
|
|
@doc """
|
|
Deletes a device profile and all associated definitions.
|
|
"""
|
|
def delete_profile(%DeviceProfile{} = profile) do
|
|
Repo.delete(profile)
|
|
end
|
|
|
|
@doc """
|
|
Creates a detection rule for a profile.
|
|
"""
|
|
def create_detection_rule(attrs \\ %{}) do
|
|
%DetectionRule{}
|
|
|> DetectionRule.changeset(attrs)
|
|
|> Repo.insert()
|
|
end
|
|
|
|
@doc """
|
|
Creates a sensor definition for a profile.
|
|
"""
|
|
def create_sensor_definition(attrs \\ %{}) do
|
|
%SensorDefinition{}
|
|
|> SensorDefinition.changeset(attrs)
|
|
|> Repo.insert()
|
|
end
|
|
|
|
@doc """
|
|
Creates a sensor state for a sensor definition.
|
|
"""
|
|
def create_sensor_state(attrs \\ %{}) do
|
|
%SensorState{}
|
|
|> SensorState.changeset(attrs)
|
|
|> Repo.insert()
|
|
end
|
|
|
|
@doc """
|
|
Creates a processor definition for a profile.
|
|
"""
|
|
def create_processor_definition(attrs \\ %{}) do
|
|
%ProcessorDefinition{}
|
|
|> ProcessorDefinition.changeset(attrs)
|
|
|> Repo.insert()
|
|
end
|
|
|
|
@doc """
|
|
Creates a memory pool definition for a profile.
|
|
"""
|
|
def create_mempool_definition(attrs \\ %{}) do
|
|
%MemPoolDefinition{}
|
|
|> MemPoolDefinition.changeset(attrs)
|
|
|> Repo.insert()
|
|
end
|
|
|
|
@doc """
|
|
Creates an OS definition for a profile.
|
|
"""
|
|
def create_os_definition(attrs \\ %{}) do
|
|
%OSDefinition{}
|
|
|> OSDefinition.changeset(attrs)
|
|
|> Repo.insert()
|
|
end
|
|
|
|
# Private functions
|
|
|
|
defp list_profiles_with_detection_rules do
|
|
Repo.all(
|
|
from p in DeviceProfile,
|
|
where: p.enabled == true,
|
|
order_by: [asc: p.priority],
|
|
preload: [:detection_rules]
|
|
)
|
|
end
|
|
|
|
defp match_detection_rules?(rules, system_info) do
|
|
Enum.any?(rules, &match_rule?(&1, system_info))
|
|
end
|
|
|
|
defp match_rule?(%DetectionRule{rule_type: "sysObjectID"} = rule, system_info) do
|
|
sys_object_id = Map.get(system_info, :sys_object_id, "")
|
|
|
|
# Normalize OID - prepend dot if not present for pattern matching
|
|
# LibreNMS uses ".1.3.6.1..." format, SNMP returns "1.3.6.1..." format
|
|
normalized_oid = if String.starts_with?(sys_object_id, "."), do: sys_object_id, else: "." <> sys_object_id
|
|
|
|
cond do
|
|
rule.pattern && rule.pattern != "" ->
|
|
String.starts_with?(normalized_oid, rule.pattern)
|
|
|
|
rule.value && rule.value != "" ->
|
|
normalized_value = if String.starts_with?(rule.value, "."), do: rule.value, else: "." <> rule.value
|
|
normalized_oid == normalized_value
|
|
|
|
true ->
|
|
false
|
|
end
|
|
end
|
|
|
|
defp match_rule?(%DetectionRule{rule_type: "sysDescr"} = rule, system_info) do
|
|
sys_descr = Map.get(system_info, :sys_descr, "")
|
|
|
|
cond do
|
|
rule.pattern && rule.pattern != "" ->
|
|
# Handle regex patterns
|
|
case Regex.compile(rule.pattern) do
|
|
{:ok, regex} -> Regex.match?(regex, sys_descr)
|
|
{:error, _} -> String.contains?(sys_descr, rule.pattern)
|
|
end
|
|
|
|
rule.value && rule.value != "" ->
|
|
String.contains?(sys_descr, rule.value)
|
|
|
|
true ->
|
|
false
|
|
end
|
|
end
|
|
|
|
defp match_rule?(%DetectionRule{rule_type: "sysDescr_regex"} = rule, system_info) do
|
|
sys_descr = Map.get(system_info, :sys_descr, "")
|
|
|
|
case Regex.compile(rule.pattern || "") do
|
|
{:ok, regex} -> Regex.match?(regex, sys_descr)
|
|
{:error, _} -> false
|
|
end
|
|
end
|
|
|
|
defp match_rule?(_rule, _system_info), do: false
|
|
end
|