Skip to content

Enforce user's security level for received trap messages #75

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

edmocosta
Copy link
Contributor

@edmocosta edmocosta commented Mar 14, 2025

This PR fixes the snmptrap input plugin to correctly enforce the configured user security level set by the security_level config. Received events that do not match the configured value are now dropped.

@robbavey
Copy link
Contributor

Testing:

AuthPriv

bin/logstash --log.level debug -e 'input { snmptrap { port => 5044 supported_versions => ["3"] security_name => "supersecret" auth_protocol => "md5" auth_pass => "authsecret" priv_protocol => "des" priv_pass => "privsecret" security_level => "authPriv" } } output { stdout {codec => rubydebug} }' --enable-local-plugin-development
Command-line options Expected Event Created Actual Event Created Error
-u supersecret
-A authsecret
-X privsecret
-l AuthPriv
-
-u supersecret
-A wrongauthsecret
-X privsecret
-l AuthPriv
[DEBUG] 2025-03-14 12:08:32.768 [SnmpTrapMessageDispatcherWorker.0] AuthorizedUSM - RFC3414 §3.2.5 - Unsupported security level: 2 by user supersecret authProtocol=null, privProtocol=null
[DEBUG] 2025-03-14 12:08:32.768 [SnmpTrapMessageDispatcherWorker.0] SnmpClient - SNMP authentication failed. source: 127.0.0.1/65404, reason: Unsupported security level (1403)
-u supersecret
-A authsecret
-X privsecret
-l NoAuthNoPriv
[DEBUG] 2025-03-14 12:07:41.980 [SnmpTrapMessageDispatcherWorker.0] AuthorizedUSM - RFC3414 §3.2.5 - Unsupported security level: 1 by user supersecret authProtocol=null, privProtocol=null
[DEBUG] 2025-03-14 12:07:41.997 [SnmpTrapMessageDispatcherWorker.0] SnmpClient - SNMP authentication failed. source: 127.0.0.1/52565, reason: Unsupported security level (1403)
-u supersecret
-A authsecret
-X privsecret
-l AuthNoPriv
[DEBUG] 2025-03-14 12:08:32.768 [SnmpTrapMessageDispatcherWorker.0] AuthorizedUSM - RFC3414 §3.2.5 - Unsupported security level: 2 by user supersecret authProtocol=null, privProtocol=null
[DEBUG] 2025-03-14 12:08:32.768 [SnmpTrapMessageDispatcherWorker.0] SnmpClient - SNMP authentication failed. source: 127.0.0.1/65404, reason: Unsupported security level (1403)
-u supersecret
-A authsecret
-X wrongprivsecret
-l AuthPriv
No Error in logs: attaching snmp logs produces:
Mar 14, 2025 11:39:09 AM org.snmp4j.log.JavaLogAdapter log WARNING: ASN.1 parse error: Data length > 4 bytes are not supported!
Mar 14, 2025 11:39:09 AM org.snmp4j.log.JavaLogAdapter log
INFO: Dispatching message canceled due to security issue: statusInfo=noError, status=-1408,tmStateReference=TransportStateReference[transport=org.snmp4j.transport.DefaultUdpTransportMapping@73bc3719, address=0.0.0.0/5044, securityName=null, requestedSecurityLevel=undefined, transportSecurityLevel=undefined, sameSecurity=false, sessionID=java.net.DatagramSocket@3f3dbb7c, target=null]

AuthNoPriv

bin/logstash --log.level debug -e 'input { snmptrap { port => 5044 supported_versions => ["3"] security_name => "supersecret" auth_protocol => "md5" auth_pass => "authsecret" priv_protocol => "des" priv_pass => "privsecret" security_level => "authNoPriv" } } output { stdout {codec => rubydebug} }' --enable-local-plugin-development
Command-line options Expected Event Created Actual Event Created Error
-u supersecret
-A authsecret
-X privsecret
-l AuthPriv
-
-u supersecret
-A authsecret
-X privsecret
-l AuthNoPriv
-
-u supersecret
-A authsecret
-l AuthNoPriv
-
-u supersecret
-A authsecret
-X wrongprivsecret
-l AuthNoPriv
-
-u supersecret
-A authsecret
-X privsecret
-l NoAuthNoPriv
[DEBUG] 2025-03-14 12:22:03.904 [SnmpTrapMessageDispatcherWorker.0] AuthorizedUSM - RFC3414 §3.2.5 - Unsupported security level: 1 by user supersecret authProtocol=null, privProtocol=null
[DEBUG] 2025-03-14 12:22:03.905 [SnmpTrapMessageDispatcherWorker.0] SnmpClient - SNMP authentication failed. source: 127.0.0.1/53455, reason: Unsupported security level (1403)
-u supersecret
-A wrongauthsecret
-X privsecret
-l AuthNoPriv
[DEBUG] 2025-03-14 12:22:58.411 [SnmpTrapMessageDispatcherWorker.0] SnmpClient - SNMP authentication failed. source: 127.0.0.1/61323, reason: Authentication failure (1408)

NoAuthNoPriv

bin/logstash --log.level debug -e 'input { snmptrap { port => 5044 supported_versions => ["3"] security_name => "supersecret" auth_protocol => "md5" auth_pass => "authsecret" priv_protocol => "des" priv_pass => "privsecret" security_level => "noAuthNoPriv" } } output { stdout {codec => rubydebug} }' --enable-local-plugin-development
Command-line options Expected Event Creation Actual Event Creation Error
-u supersecret
-A authsecret
-X privsecret
-l NoAuthNoPriv
-
-u supersecret
-A authsecret
-X privsecret
-l AuthNoPriv
-
-u supersecret
-A authsecret -X privsecret
-l AuthPriv
-
-u supersecret
-A wrongauthsecret -X privsecret
-l AuthNoPriv
-
-u supersecret
-A authsecret
-l AuthNoPriv

Everything appears to work as expected with one caveat:

When there are issues with the format of the message - an incorrect privilege passphrase or protocol, for example - the event does not get created, and there is no logging to indicate that an event has been received, and has been dropped. This was super tricky to debug, and required me to attach a debugger to the Logstash process to figure out what was going on.
It would be helpful to have a way to configure the snmp4j.LogFactory, ideally with the Log4jLogFactory to give us some insight into what the library is actually doing if it decides not to dispatch events into Logstash

@robbavey
Copy link
Contributor

Tried with the logging library - logs are showing up in the log file:

[2025-03-17T17:20:41,384][INFO ][org.snmp4j.MessageDispatcherImpl][main] Dispatching message canceled due to security issue: statusInfo=1.3.6.1.6.3.15.1.1.5.0 = 0, status=1408,tmStateReference=TransportStateReference[transport=org.snmp4j.transport.DefaultUdpTransportMapping@118065a4, address=0.0.0.0/5044, securityName=null, requestedSecurityLevel=undefined, transportSecurityLevel=undefined, sameSecurity=false, sessionID=java.net.DatagramSocket@1370eb62, target=null]

Copy link

@yaauie yaauie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the snmptrap effectively allows us to have zero or one USM's, this feels like a lot of overhead for a check that could be done in the CommandResponder#processPdu (that already contains logic for dropping non-matching events) in the existing SnmpClient#doTrap.

Is the idea that we want to be able to emit SNMPv3_USM_UNSUPPORTED_SECURITY_LEVEL, and allow SNMP4j to build a rejection response? From what I can see in a fork of SNMP4j, that is used as feedback when a payload includes a security_level that requires additional auth fields without actually providing said auth fields (e.g.,: >= AUTH_NOPRIV missing auth protocol, AUTH_PRIV missing privacy protocol), or a payload requests a level of auth that is not supported by the user (e.g., AUTH_PRIV but USM for the user has no auth or privacy protocols); it appears to be more an indication that the request is malformed than the contents of the payload being non-actionable.

In the contexts of an SNMP trap, I would view security_level as more of a post-receive filter than a pre-receive override to the USM.

Co-authored-by: Rye Biesemeyer <yaauie@users.noreply.github.com>
@yaauie
Copy link

yaauie commented Mar 19, 2025

I think that things may be a bit conflated here.

In the snmp input, the security_level effectively instructs the client which of the auth_* and priv_* configs to include in requests, and validation ensures that the minimum of required configuration is present.

But the snmptrap, it is the incoming TRAP that provides its own security level.

While we could use it as a filter, the meaning isn't clear. Is it a minimum threshold? Or are we expecting an exact match?

For example, both plugins allow a configuration that has security_level => authNoPriv without the priv_* configuration:

  • the snmptrap input will reject incoming traps that have a higher security level, since it cannot validate their priv_* claim.
  • the snmp input will make requests that simply don't include the provided priv_* configuration (I believe this is also a bug).

In both plugins, the auth_* and priv_* settings are registered for a single security_name user in the client's USM.

In the snmp input, the security_level effectively determines which of those settings are used by snmp4j when making a request (e.g, if the snmp input's security_level is authNoPriv or noAuthNoPriv its priv_* settings will be silently ignored, or if its security_level is noAuthNoPriv its auth_* settings will be silently ignored).

In the snmptrap input, that single-user USM is used by snmp4j to reject incoming payloads that cannot be satisfied by that USM. But in this case, it is the incoming TRAP that includes its security level, which is a hint at which auth and priv parameters it supplied. If our USM doesn't have the appropriate settings to validate it, the TRAP is rejected (e.g, if the incoming TRAP has authPriv and we failed to provide both auth_* and priv_*, then our USM cannot validate regardless of the security_level setting).

Your assumption is wrong, that the USM has to check whether the security level of the sender matches the maximum possible security level of an USM entry and then reject incoming requests if that is not the case.
That is not the task of the USM. As I have written before, this check is done by the VACM. If you do not use a VACM on top of USM, then you will see the trap in your application.

-- AGENTPP, maintainer of SNMP

In this sense, we are expecting the snmptrap input's security_level to act a filter, and should not try to absorb that responsibility into an overriding USM implementation.

I still think that we should be able to intercept it in the CommandResponder#processPdu (that already contains logic for dropping non-matching events) in the existing SnmpClient#doTrap.

@edmocosta
Copy link
Contributor Author

Thank you Ry for digging into this, I saw that maintainer message as well while investigating it, and although I agree that it's not part of the USM responsibility to verify authorization, I don't think we should strictly apply that to us, given we're not providing a SNMP library, and there's no need to be compliant (in terms of responsibilities) with the RFC spec.

As I mentioned, the idea of overriding the USM was providing a proper response to the clients, without having to implement a VACM system neither paying for their commercial version. Anyway, It doesn't really matter for traps, considering it's fire-and-forget, but it would be useful for supporting INFORM for example, which responses may be used by the clients to implement errors flows.

I still think that we should be able to intercept it in the CommandResponder#processPdu (that already contains logic for dropping non-matching events) in the existing SnmpClient#doTrap.

I don't have a strong opinion on that, and I'm fine changing it as suggested considering we don't officially support INFORM messages.

Regarding having a single security_name user in the USM, that's a limitation inherited from the old plugins (unfortunately), and it's not the reality of most SNMP environments where devices might have different credentials (security reasons). There's an open issue for extending that support, so users don't need to run a plugin per credential.


I'll push the changes soon. Thanks!

@edmocosta edmocosta changed the title [WIP] Enforce minimum security level for traps Enforce minimum security level for traps Mar 21, 2025
@edmocosta edmocosta changed the title Enforce minimum security level for traps Enforce user's security level for received trap messages Mar 21, 2025
@edmocosta edmocosta force-pushed the fix-traps-security-level branch from 1d85c8f to fca421d Compare March 21, 2025 16:52
@edmocosta edmocosta marked this pull request as ready for review March 24, 2025 22:07
@edmocosta edmocosta requested review from yaauie and robbavey March 24, 2025 22:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants