diff --git a/lib/towerops/snmp/client.ex b/lib/towerops/snmp/client.ex index 1a959faa..5765eee9 100644 --- a/lib/towerops/snmp/client.ex +++ b/lib/towerops/snmp/client.ex @@ -10,11 +10,17 @@ defmodule Towerops.Snmp.Client do @type connection_opts :: [ ip: String.t(), - community: String.t(), version: String.t(), port: non_neg_integer(), transport: String.t(), - timeout: non_neg_integer() + timeout: non_neg_integer(), + community: String.t(), + security_name: String.t(), + security_level: String.t(), + auth_protocol: String.t(), + auth_password: String.t(), + priv_protocol: String.t(), + priv_password: String.t() ] @type oid :: String.t() | [non_neg_integer()] @@ -417,25 +423,62 @@ defmodule Towerops.Snmp.Client do defp build_snmp_opts(opts) do version = parse_version(Keyword.fetch!(opts, :version)) - community = Keyword.fetch!(opts, :community) port = Keyword.get(opts, :port, 161) timeout = Keyword.get(opts, :timeout, @default_timeout) - [ - community: community, + base_opts = [ version: version, port: port, timeout: timeout, retries: @default_retries ] + + # Add version-specific authentication options + case version do + :v3 -> + base_opts ++ + [ + security_name: Keyword.fetch!(opts, :security_name), + security_level: parse_security_level(Keyword.fetch!(opts, :security_level)), + auth_protocol: parse_auth_proto(Keyword.get(opts, :auth_protocol)), + auth_password: Keyword.get(opts, :auth_password), + priv_protocol: parse_priv_proto(Keyword.get(opts, :priv_protocol)), + priv_password: Keyword.get(opts, :priv_password) + ] + + _ -> + base_opts ++ [community: Keyword.fetch!(opts, :community)] + end end defp parse_version("1"), do: :v1 defp parse_version("2c"), do: :v2c + defp parse_version("3"), do: :v3 defp parse_version(:v1), do: :v1 defp parse_version(:v2c), do: :v2c + defp parse_version(:v3), do: :v3 defp parse_version(_other), do: :v2c + defp parse_security_level("noAuthNoPriv"), do: :no_auth_no_priv + defp parse_security_level("authNoPriv"), do: :auth_no_priv + defp parse_security_level("authPriv"), do: :auth_priv + defp parse_security_level(_), do: :no_auth_no_priv + + defp parse_auth_proto("MD5"), do: :md5 + defp parse_auth_proto("SHA"), do: :sha + defp parse_auth_proto("SHA-224"), do: :sha224 + defp parse_auth_proto("SHA-256"), do: :sha256 + defp parse_auth_proto("SHA-384"), do: :sha384 + defp parse_auth_proto("SHA-512"), do: :sha512 + defp parse_auth_proto(_), do: :sha256 + + defp parse_priv_proto("DES"), do: :des + defp parse_priv_proto("AES"), do: :aes + defp parse_priv_proto("AES-192"), do: :aes192 + defp parse_priv_proto("AES-256"), do: :aes256 + defp parse_priv_proto("AES-256-C"), do: :aes256c + defp parse_priv_proto(_), do: :aes + @doc """ Resolve MIB name to numeric OID using SnmpKit. Handles MODULE-NAME::objectName format by stripping module prefix. diff --git a/lib/towerops/snmp/discovery.ex b/lib/towerops/snmp/discovery.ex index 049a2c76..4b713333 100644 --- a/lib/towerops/snmp/discovery.ex +++ b/lib/towerops/snmp/discovery.ex @@ -382,12 +382,28 @@ defmodule Towerops.Snmp.Discovery do # Get SNMP config with hierarchical fallback (device -> site -> organization) snmp_config = Devices.get_snmp_config(device) - [ + base_opts = [ ip: device.ip_address, - community: snmp_config.community, version: snmp_config.version, port: device.snmp_port || 161 ] + + # Add version-specific credentials + if snmp_config.version == "3" do + v3_config = Devices.get_snmpv3_config(device) + + base_opts ++ + [ + security_name: v3_config.username, + security_level: v3_config.security_level, + auth_protocol: v3_config.auth_protocol, + auth_password: v3_config.auth_password, + priv_protocol: v3_config.priv_protocol, + priv_password: v3_config.priv_password + ] + else + base_opts ++ [community: snmp_config.community] + end end @spec discover_system(Client.connection_opts()) :: {:ok, system_info()} | {:error, term()}