towerops/test/snmpkit/snmp_lib/snmpv3_integration_test.exs
2026-01-23 18:21:40 -06:00

593 lines
19 KiB
Elixir

defmodule SnmpKit.SnmpLib.SNMPv3IntegrationTest do
use ExUnit.Case, async: false
alias SnmpKit.SnmpLib.PDU.V3Encoder
alias SnmpKit.SnmpLib.Security.Auth
alias SnmpKit.SnmpLib.Security.Priv
@moduletag :snmpv3
@moduletag :integration
@moduletag :snmpv3
describe "End-to-end SNMPv3 scenarios" do
test "complete discovery and authentication flow" do
# Step 1: Engine Discovery
discovery_msg = V3Encoder.create_discovery_message(1001)
assert {:ok, _discovery_packet} = V3Encoder.encode_message(discovery_msg, nil)
# Simulate response with engine ID
mock_response = create_mock_discovery_response(discovery_msg.msg_id, "remote_engine_123")
assert {:ok, decoded_response} = V3Encoder.decode_message(mock_response, nil)
engine_id = decoded_response.msg_data.context_engine_id
# Step 2: Create user with discovered engine
user = create_authenticated_user(engine_id)
# Step 3: Send authenticated request
auth_request = create_authenticated_request(2002, user)
assert {:ok, auth_packet} = V3Encoder.encode_message(auth_request, user)
# Step 4: Verify we can decode our own authenticated message
assert {:ok, decoded_auth} = V3Encoder.decode_message(auth_packet, user)
assert decoded_auth.msg_flags.auth == true
assert decoded_auth.msg_flags.priv == false
end
test "complete privacy-enabled communication flow" do
engine_id = "secure_engine_456"
user = create_encrypted_user(engine_id)
# Create encrypted request
encrypted_request = create_encrypted_request(3003, user)
assert {:ok, encrypted_packet} = V3Encoder.encode_message(encrypted_request, user)
# Verify encrypted packet is different from plaintext
plaintext_request = create_authenticated_request(3003, user)
{:ok, plaintext_packet} = V3Encoder.encode_message(plaintext_request, user)
assert encrypted_packet != plaintext_packet
assert byte_size(encrypted_packet) > byte_size(plaintext_packet)
# Decode encrypted message
assert {:ok, decoded_encrypted} = V3Encoder.decode_message(encrypted_packet, user)
assert decoded_encrypted.msg_flags.auth == true
assert decoded_encrypted.msg_flags.priv == true
assert decoded_encrypted.msg_data.pdu.type == :get_request
end
test "cross-protocol compatibility" do
engine_id = "compat_engine_789"
# Test different auth/priv combinations
test_combinations = [
{:sha256, :aes128},
{:sha384, :aes192},
{:sha512, :aes256},
{:md5, :des},
{:sha1, :aes128}
]
for {auth_proto, priv_proto} <- test_combinations do
user = create_user_with_protocols(engine_id, auth_proto, priv_proto)
request = create_encrypted_request(4004, user)
assert {:ok, packet} = V3Encoder.encode_message(request, user)
assert {:ok, decoded} = V3Encoder.decode_message(packet, user)
assert decoded.msg_data.pdu.request_id == 4004
assert decoded.msg_flags.auth == true
assert decoded.msg_flags.priv == true
end
end
test "bulk request with privacy" do
engine_id = "bulk_engine_abc"
user = create_encrypted_user(engine_id)
# Create bulk request
bulk_pdu = %{
type: :get_bulk_request,
request_id: 5005,
error_status: 0,
error_index: 0,
non_repeaters: 0,
max_repetitions: 20,
varbinds: [
{[1, 3, 6, 1, 2, 1, 2, 2, 1, 1], :null, :null},
{[1, 3, 6, 1, 2, 1, 2, 2, 1, 2], :null, :null}
]
}
bulk_request = create_v3_message(5005, bulk_pdu, user, :auth_priv)
assert {:ok, bulk_packet} = V3Encoder.encode_message(bulk_request, user)
assert {:ok, decoded_bulk} = V3Encoder.decode_message(bulk_packet, user)
assert decoded_bulk.msg_data.pdu.type == :get_bulk_request
assert decoded_bulk.msg_data.pdu.non_repeaters == 0
assert decoded_bulk.msg_data.pdu.max_repetitions == 20
assert length(decoded_bulk.msg_data.pdu.varbinds) == 2
end
test "large message handling with encryption" do
engine_id = "large_engine_def"
user = create_encrypted_user(engine_id)
# Create request with many varbinds
large_varbinds =
for i <- 1..100 do
{[1, 3, 6, 1, 2, 1, 1, i, 0], :null, :null}
end
large_pdu = %{
type: :get_request,
request_id: 6006,
error_status: 0,
error_index: 0,
varbinds: large_varbinds
}
large_request = create_v3_message(6006, large_pdu, user, :auth_priv)
# Should handle large messages
assert {:ok, large_packet} = V3Encoder.encode_message(large_request, user)
assert byte_size(large_packet) > 1500
assert {:ok, decoded_large} = V3Encoder.decode_message(large_packet, user)
assert length(decoded_large.msg_data.pdu.varbinds) == 100
end
test "context engine and context name handling" do
engine_id = "context_engine_ghi"
user = create_encrypted_user(engine_id)
# Test with different context configurations
context_configs = [
{"", ""},
{"target_engine_xyz", ""},
{"target_engine_xyz", "network_context"},
{"", "management_context"}
]
for {context_engine, context_name} <- context_configs do
pdu = create_simple_pdu(7007)
request = %{
version: 3,
msg_id: 7007,
msg_max_size: 65_507,
msg_flags: %{auth: true, priv: true, reportable: true},
msg_security_model: 3,
msg_security_parameters: <<>>,
msg_data: %{
context_engine_id: context_engine,
context_name: context_name,
pdu: pdu
}
}
assert {:ok, packet} = V3Encoder.encode_message(request, user)
assert {:ok, decoded} = V3Encoder.decode_message(packet, user)
assert decoded.msg_data.context_engine_id == context_engine
assert decoded.msg_data.context_name == context_name
end
end
test "error response handling" do
engine_id = "error_engine_jkl"
user = create_authenticated_user(engine_id)
# Create error response
error_pdu = %{
type: :get_response,
request_id: 8008,
# noSuchName
error_status: 2,
error_index: 1,
varbinds: [{[1, 3, 6, 1, 2, 1, 99, 99, 0], :null, :null}]
}
error_response = create_v3_message(8008, error_pdu, user, :auth_no_priv)
assert {:ok, error_packet} = V3Encoder.encode_message(error_response, user)
assert {:ok, decoded_error} = V3Encoder.decode_message(error_packet, user)
assert decoded_error.msg_data.pdu.type == :get_response
assert decoded_error.msg_data.pdu.error_status == 2
assert decoded_error.msg_data.pdu.error_index == 1
end
test "time synchronization simulation" do
engine_id = "time_engine_mno"
# Simulate initial time sync request (no auth)
sync_user = %{
security_name: "",
auth_protocol: :none,
priv_protocol: :none,
auth_key: <<>>,
priv_key: <<>>,
engine_id: engine_id,
engine_boots: 0,
engine_time: 0
}
time_sync_pdu = %{
type: :get_request,
request_id: 9009,
error_status: 0,
error_index: 0,
# snmpEngineTime
varbinds: [{[1, 3, 6, 1, 6, 3, 10, 2, 1, 3, 0], :null, :null}]
}
time_sync_request = create_v3_message(9009, time_sync_pdu, sync_user, :no_auth_no_priv)
assert {:ok, sync_packet} = V3Encoder.encode_message(time_sync_request, sync_user)
assert {:ok, decoded_sync} = V3Encoder.decode_message(sync_packet, sync_user)
# Should be unauthenticated time sync request
assert decoded_sync.msg_flags.auth == false
assert decoded_sync.msg_flags.priv == false
assert decoded_sync.msg_data.pdu.varbinds == time_sync_pdu.varbinds
end
test "reportable flag handling" do
engine_id = "report_engine_pqr"
user = create_authenticated_user(engine_id)
# Test with reportable flag variations
reportable_values = [true, false]
for reportable <- reportable_values do
pdu = create_simple_pdu(10_010)
request = %{
version: 3,
msg_id: 10_010,
msg_max_size: 65_507,
msg_flags: %{auth: true, priv: false, reportable: reportable},
msg_security_model: 3,
msg_security_parameters: <<>>,
msg_data: %{
context_engine_id: engine_id,
context_name: "",
pdu: pdu
}
}
assert {:ok, packet} = V3Encoder.encode_message(request, user)
assert {:ok, decoded} = V3Encoder.decode_message(packet, user)
assert decoded.msg_flags.reportable == reportable
end
end
test "multiple users with same engine" do
engine_id = "shared_engine_stu"
# Create multiple users for the same engine
user1 = create_user_with_name(engine_id, "user1", :auth_no_priv)
user2 = create_user_with_name(engine_id, "user2", :auth_priv)
user3 = create_user_with_name(engine_id, "user3", :no_auth_no_priv)
users = [user1, user2, user3]
security_levels = [:auth_no_priv, :auth_priv, :no_auth_no_priv]
for {user, level} <- Enum.zip(users, security_levels) do
pdu = create_simple_pdu(11_000 + :rand.uniform(999))
request = create_v3_message(pdu.request_id, pdu, user, level)
assert {:ok, packet} = V3Encoder.encode_message(request, user)
assert {:ok, _decoded} = V3Encoder.decode_message(packet, user)
# Each user should only be able to decode their own messages
# Exception: no-auth messages can be decoded by anyone
for other_user <- users -- [user] do
if level != :no_auth_no_priv and other_user.auth_protocol != :none do
# Should fail authentication for other users (except for no-auth messages)
assert {:error, _} = V3Encoder.decode_message(packet, other_user)
else
# No-auth messages can be decoded by anyone, or no-auth users can't verify others
# This is expected behavior
_result = V3Encoder.decode_message(packet, other_user)
end
end
end
end
end
describe "Real-world simulation scenarios" do
test "network discovery and monitoring flow" do
# Simulate discovering multiple engines
engines = [
"router_engine_001",
"switch_engine_002",
"server_engine_003"
]
discovered_engines =
for {engine_id, index} <- Enum.with_index(engines, 1) do
discovery_msg = V3Encoder.create_discovery_message(12_000 + index)
{:ok, _discovery_packet} = V3Encoder.encode_message(discovery_msg, nil)
# Simulate response
mock_response = create_mock_discovery_response(discovery_msg.msg_id, engine_id)
{:ok, decoded} = V3Encoder.decode_message(mock_response, nil)
decoded.msg_data.context_engine_id
end
assert length(discovered_engines) == 3
assert "router_engine_001" in discovered_engines
assert "switch_engine_002" in discovered_engines
assert "server_engine_003" in discovered_engines
end
test "bulk table walking simulation" do
engine_id = "table_engine_vwx"
user = create_encrypted_user(engine_id)
# Simulate walking interface table
interface_table_oid = [1, 3, 6, 1, 2, 1, 2, 2, 1]
# First request
bulk_pdu1 = %{
type: :get_bulk_request,
request_id: 13_001,
error_status: 0,
error_index: 0,
non_repeaters: 0,
max_repetitions: 10,
varbinds: [{interface_table_oid, :null, :null}]
}
request1 = create_v3_message(13_001, bulk_pdu1, user, :auth_priv)
assert {:ok, packet1} = V3Encoder.encode_message(request1, user)
assert {:ok, decoded1} = V3Encoder.decode_message(packet1, user)
# Simulate continuation with next OID
# Simulate getting next part of table
next_oid = interface_table_oid ++ [10, 1]
bulk_pdu2 = %{
type: :get_bulk_request,
request_id: 13_002,
error_status: 0,
error_index: 0,
non_repeaters: 0,
max_repetitions: 10,
varbinds: [{next_oid, :null, :null}]
}
request2 = create_v3_message(13_002, bulk_pdu2, user, :auth_priv)
assert {:ok, packet2} = V3Encoder.encode_message(request2, user)
assert {:ok, decoded2} = V3Encoder.decode_message(packet2, user)
# Both requests should be valid bulk requests
assert decoded1.msg_data.pdu.type == :get_bulk_request
assert decoded2.msg_data.pdu.type == :get_bulk_request
assert decoded1.msg_data.pdu.max_repetitions == 10
assert decoded2.msg_data.pdu.max_repetitions == 10
end
test "configuration change scenario" do
engine_id = "config_engine_yz1"
user = create_encrypted_user(engine_id)
# Simulate configuration change with SET request
set_pdu = %{
type: :set_request,
request_id: 14_001,
error_status: 0,
error_index: 0,
varbinds: [
{[1, 3, 6, 1, 2, 1, 1, 5, 0], :octet_string, "New System Name"},
{[1, 3, 6, 1, 2, 1, 1, 6, 0], :octet_string, "New Location"}
]
}
set_request = create_v3_message(14_001, set_pdu, user, :auth_priv)
assert {:ok, set_packet} = V3Encoder.encode_message(set_request, user)
assert {:ok, decoded_set} = V3Encoder.decode_message(set_packet, user)
assert decoded_set.msg_data.pdu.type == :set_request
assert length(decoded_set.msg_data.pdu.varbinds) == 2
[{_, _, name_value}, {_, _, location_value}] = decoded_set.msg_data.pdu.varbinds
assert name_value == "New System Name"
assert location_value == "New Location"
end
test "security level migration scenario" do
engine_id = "migration_engine_234"
# Start with no security
user_v1 = create_user_with_name(engine_id, "migrating_user", :no_auth_no_priv)
pdu = create_simple_pdu(15_001)
request_v1 = create_v3_message(15_001, pdu, user_v1, :no_auth_no_priv)
assert {:ok, packet_v1} = V3Encoder.encode_message(request_v1, user_v1)
assert {:ok, decoded_v1} = V3Encoder.decode_message(packet_v1, user_v1)
assert decoded_v1.msg_flags.auth == false
assert decoded_v1.msg_flags.priv == false
# Migrate to authentication
user_v2 = create_user_with_name(engine_id, "migrating_user", :auth_no_priv)
request_v2 = create_v3_message(15_002, pdu, user_v2, :auth_no_priv)
assert {:ok, packet_v2} = V3Encoder.encode_message(request_v2, user_v2)
assert {:ok, decoded_v2} = V3Encoder.decode_message(packet_v2, user_v2)
assert decoded_v2.msg_flags.auth == true
assert decoded_v2.msg_flags.priv == false
# Migrate to full security
user_v3 = create_user_with_name(engine_id, "migrating_user", :auth_priv)
request_v3 = create_v3_message(15_003, pdu, user_v3, :auth_priv)
assert {:ok, packet_v3} = V3Encoder.encode_message(request_v3, user_v3)
assert {:ok, decoded_v3} = V3Encoder.decode_message(packet_v3, user_v3)
assert decoded_v3.msg_flags.auth == true
assert decoded_v3.msg_flags.priv == true
# Each version should have different packet sizes
assert byte_size(packet_v1) < byte_size(packet_v2)
assert byte_size(packet_v2) < byte_size(packet_v3)
end
end
# Helper functions
defp create_mock_discovery_response(request_id, engine_id) do
response_pdu = %{
type: :get_response,
request_id: request_id,
error_status: 0,
error_index: 0,
varbinds: [{[1, 3, 6, 1, 6, 3, 10, 2, 1, 1, 0], :octet_string, engine_id}]
}
response_msg = %{
version: 3,
msg_id: request_id,
msg_max_size: 65_507,
msg_flags: %{auth: false, priv: false, reportable: false},
msg_security_model: 3,
msg_security_parameters: <<>>,
msg_data: %{
context_engine_id: engine_id,
context_name: "",
pdu: response_pdu
}
}
{:ok, encoded} = V3Encoder.encode_message(response_msg, nil)
encoded
end
defp create_simple_pdu(request_id) do
%{
type: :get_request,
request_id: request_id,
error_status: 0,
error_index: 0,
varbinds: [{[1, 3, 6, 1, 2, 1, 1, 1, 0], :null, :null}]
}
end
defp create_authenticated_user(engine_id) do
%{
security_name: "auth_user",
auth_protocol: :sha256,
priv_protocol: :none,
auth_key: :crypto.strong_rand_bytes(32),
priv_key: <<>>,
engine_id: engine_id,
engine_boots: 1,
engine_time: System.system_time(:second)
}
end
defp create_encrypted_user(engine_id) do
%{
security_name: "priv_user",
auth_protocol: :sha256,
priv_protocol: :aes128,
auth_key: :crypto.strong_rand_bytes(32),
priv_key: :crypto.strong_rand_bytes(16),
engine_id: engine_id,
engine_boots: 1,
engine_time: System.system_time(:second)
}
end
defp create_user_with_protocols(engine_id, auth_protocol, priv_protocol) do
%{
security_name: "protocol_user",
auth_protocol: auth_protocol,
priv_protocol: priv_protocol,
auth_key: :crypto.strong_rand_bytes(auth_key_size(auth_protocol)),
priv_key: :crypto.strong_rand_bytes(priv_key_size(priv_protocol)),
engine_id: engine_id,
engine_boots: 1,
engine_time: System.system_time(:second)
}
end
defp create_user_with_name(engine_id, name, security_level) do
{auth_protocol, auth_key} =
case security_level do
:no_auth_no_priv -> {:none, <<>>}
_ -> {:sha256, :crypto.strong_rand_bytes(32)}
end
{priv_protocol, priv_key} =
case security_level do
:auth_priv -> {:aes128, :crypto.strong_rand_bytes(16)}
_ -> {:none, <<>>}
end
%{
security_name: name,
auth_protocol: auth_protocol,
priv_protocol: priv_protocol,
auth_key: auth_key,
priv_key: priv_key,
engine_id: engine_id,
engine_boots: 1,
engine_time: System.system_time(:second)
}
end
defp create_authenticated_request(request_id, user) do
pdu = create_simple_pdu(request_id)
create_v3_message(request_id, pdu, user, :auth_no_priv)
end
defp create_encrypted_request(request_id, user) do
pdu = create_simple_pdu(request_id)
create_v3_message(request_id, pdu, user, :auth_priv)
end
defp create_v3_message(msg_id, pdu, user, security_level) do
flags =
case security_level do
:no_auth_no_priv -> %{auth: false, priv: false, reportable: true}
:auth_no_priv -> %{auth: true, priv: false, reportable: true}
:auth_priv -> %{auth: true, priv: true, reportable: true}
end
%{
version: 3,
msg_id: msg_id,
msg_max_size: 65_507,
msg_flags: flags,
msg_security_model: 3,
msg_security_parameters: <<>>,
msg_data: %{
context_engine_id: user.engine_id,
context_name: "",
pdu: pdu
}
}
end
# Helper functions for key size lookup
defp auth_key_size(auth_protocol) do
case Auth.protocol_info(auth_protocol) do
%{digest_size: size} -> size
# Default fallback
nil -> 16
end
end
defp priv_key_size(priv_protocol) do
case Priv.protocol_info(priv_protocol) do
%{key_size: size} -> size
# Default fallback
nil -> 16
end
end
end