Skip to content

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
Contributor

@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
Contributor

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 robbavey and yaauie March 24, 2025 22:07
@edmocosta
Copy link
Contributor Author

@robbavey @yaauie could you please review so we move forward with this PR?

Copy link
Contributor

@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.

I apologize for the delay in getting to a re-review.

I'm still concerned that we're not really defining what the security_level means in the context of the snmptrap plugin. It appears to default to noAuthNoPriv when security_name is specified, and it requires a precise match; even if trapped messages match provided auth_* and priv_* config they will be rejected.

I would expect the security_level config to act as a minimum. If a user configures security_name, auth_*, and priv_*, and sets security_level => authNoPriv, I would expect both authNoPriv and matching authPriv events to be emitted.

If we do intend to mandate an exact match, then I would expect not setting security_level equivalent to having no preference (not dropping events that correctly match the provided auth_* and/or priv_* params). Enforcing noAuthNoPriv when security_level is unspecified is a breaking change

context 'when receiving a request from an unknown user' do
it 'should not process the message' do
queue = run_plugin_and_get_queue(plugin, timeout: 3) do
@trap_sender.send_trap_v3(target_address, "foo", auth_protocol, auth_pass, priv_protocol, priv_pass, 'noAuthNoPriv', {'1.1' => 'foo'})
Copy link
Contributor

Choose a reason for hiding this comment

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

🤔 we have another test that uses the "correct" security_name with an "invalid security level", and this one has the same security level with an "incorrect" security_name.

We also had situations where providing noAuthNoPriv caused our own code to avoid sending the supplied auth and priv parameters; are we confident that they are supplied here, and that the test is passing for the right reasons?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Dropped this test as unknown user shouldn't be a thing: #75 (comment)

logger.debug("Received trap message from an unknown user: '{}'. Skipping", securityName);
return false;
}
if (!Objects.equals(userSecurityLevel, securityLevel)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we require security_level to be an exact match? If a trapped message is authPriv, and matches the auth_* and priv_* params, but security_level is either unspecified (resulting in an effective-default noAuthNoPriv) or authNoPriv, do we reject the trapped event?

This still feels weird to me, and isn't really specified in our docs. From how I read this code to behave I think our docs need a change like:

diff --git a/docs/input-snmptrap.asciidoc b/docs/input-snmptrap.asciidoc index 8874583..e341137 100644 --- a/docs/input-snmptrap.asciidoc +++ b/docs/input-snmptrap.asciidoc @@ -350,11 +350,13 @@ The `priv_protocol` option specifies the SNMPv3 privacy/encryption protocol. [id="plugins-{type}s-{plugin}-security_level"] ===== `security_level` - * Value can be any of: `noAuthNoPriv`, `authNoPriv`, `authPriv` - * There is no default value for this setting + * Value can be any of: + - `noAuthNoPriv`: trapped messages must be neither authenticated nor privatized + - `authNoPriv`: trapped messages must be authenticated according to <<plugins-{type}s-{plugin}-security_name>>/<<plugins-{type}s-{plugin}-auth_protocol>>/<<plugins-{type}s-{plugin}-auth_pass>> but must not be privatized + - `authPriv`: trapped messages must be both authenticated according to <<plugins-{type}s-{plugin}-security_name>>/<<plugins-{type}s-{plugin}-auth_protocol>>/<<plugins-{type}s-{plugin}-auth_pass>> and privatized according to <<plugins-{type}s-{plugin}-priv_protocol>>/<<plugins-{type}s-{plugin}-priv_pass>> + * The default value is `noAuthNoPriv`. -The `security_level` option specifies the SNMPv3 security level between -Authentication, No Privacy; Authentication, Privacy; or no Authentication, no Privacy. +The `security_level` option specifies the _precise_ SNMPv3 security level of trapped messages that will be accepted when <<plugins-{type}s-{plugin}-security_name>> is specified. [id="plugins-{type}s-{plugin}-security_name"] ===== `security_name` 
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do we require security_level to be an exact match?

Not a requirement, I've changed it to accept higher security levels as well. I guess it also solves #75 (comment).

@edmocosta edmocosta requested a review from yaauie July 3, 2025 09:23
Copy link
Contributor

@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.

👍

Thanks for following through, and my apologies for the delays in getting this back to you. The behavior is as I expect.

@edmocosta edmocosta merged commit 8d863fb into main Jul 17, 2025
2 of 3 checks passed
@edmocosta edmocosta deleted the fix-traps-security-level branch July 17, 2025 11:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants