Skip to content

feat: add SNMP Trap provider#5509

Open
natkon20043 wants to merge 4 commits intokeephq:mainfrom
natkon20043:main
Open

feat: add SNMP Trap provider#5509
natkon20043 wants to merge 4 commits intokeephq:mainfrom
natkon20043:main

Conversation

@natkon20043
Copy link

@natkon20043 natkon20043 commented Jan 14, 2026

Closes #2112

📑 Description

Implemented a new SNMP Provider that allows Keep to act as an SNMP Trap receiver. This enables real-time alert ingestion from network devices and servers.

  • Created SNMPProvider logic with asynchronous trap listening.
  • Implemented SNMPProviderAuthConfig using Pydantic for UI integration.
  • Added support for v1/v2c community-based authentication.
  • Included automatic alert fingerprinting (Source IP + OID).

✅ Checks

  • My pull request adheres to the code style of this project
  • My code requires changes to the documentation
  • I have updated the documentation as required
  • All the tests have passed

ℹ Additional Information

Demo Video

Technical Details

  • Library: Uses pysnmp-lextudio for modern Python 3.13 support.
  • Asyncio: The listener runs on the asyncio event loop using a non-blocking runDispatcher(0.1) pattern to ensure Keep remains responsive.
  • Testing: Verified manually on CachyOS using snmptrap commands.

Verification Command

To verify, I ran the provider and sent a test trap:
snmptrap -v 2c -c public localhost:1162 '' 1.3.6.1.4.1.8072.2.3.0.1 1.3.6.1.2.1.1.1.0 s "Keep-Bounty-Verification"

Video_2026-01-13_18-59-38.mp4

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 14, 2026

Target branch is not in the allowed branches list.

@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jan 14, 2026
@CLAassistant
Copy link

CLAassistant commented Jan 14, 2026

CLA assistant check
All committers have signed the CLA.

@vercel
Copy link

vercel bot commented Jan 14, 2026

@natkon20043 is attempting to deploy a commit to the KeepHQ Team on Vercel.

A member of the Team first needs to authorize it.

@dosubot dosubot bot added Feature A new feature Provider Providers related issues labels Jan 14, 2026
@natkon20043
Copy link
Author

I signed in and approved it did that work? Im sorry im really new to this and dont really know how to do it

@kittucandy
Copy link

@natkon20043 Thanks for the contribution. I have a requirement on snmp trap and want to send the alerts to KeepHQ. I used your code and able to push the trap message and alert is pushed (as it's saying from the logs) just like how you showed in the video demo. How can we see the alerts on the KeepUI? can you share some inputs

@natkon20043
Copy link
Author

Absolutely I will begin working on that now!

Copilot AI review requested due to automatic review settings February 28, 2026 21:23
@natkon20043 natkon20043 marked this pull request as draft February 28, 2026 21:23
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new SNMP trap receiver provider so Keep can ingest SNMP traps as alerts.

Changes:

  • Introduces an SNMP provider implementation intended to listen for traps and push them into Keep.
  • Adds a Pydantic dataclass for SNMP authentication/UI configuration.
  • Exposes the provider via the snmp_provider package __init__.py.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 16 comments.

File Description
keep/providers/snmp_provider/snmp_provider.py Implements SNMP trap receiving and alert construction/push logic.
keep/providers/snmp_provider/config.py Defines the SNMP provider authentication configuration dataclass for UI/config.
keep/providers/snmp_provider/init.py Exports the provider class from the package.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +4 to +6
from pysnmp.carrier.asyncio.dgram import udp
from pysnmp.entity import engine, config
from pysnmp.entity.rfc3413 import ntfrcv
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

This module imports pysnmp, but the repository dependency manifest doesn't currently include any pysnmp package. Without adding the dependency (and pinning an appropriate distribution, per the PR description), Keep will fail to import this provider and CI/docs validation will break. Add the required pysnmp dependency to pyproject.toml/poetry.lock (or guard imports similarly to other optional providers).

Copilot uses AI. Check for mistakes.
Comment on lines 52 to 56
"fingerprint": f"{source_ip}-{ next( iter( trap_data ), 'empty' ) }",
"trap_data": trap_data, # Structured data
"details": "\n".join( msg ), # Readable string
"host": source_ip,
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

Keys like 'trap_data', 'details', and 'host' are not part of AlertDto and will be dropped when BaseProvider._push_alert() builds the model. If you need varbinds stored/searchable in Keep, map them into supported fields such as 'labels' and/or include them in 'description'.

Copilot uses AI. Check for mistakes.
Comment on lines 9 to 17
port: int = pydantic.Field(
default=DEFAULTPORT, # SNMP default port
metadata={
"required": True,
"description": "SNMP port for listening for traps",
"hint": "SNMP defaults to 1162; elevate to port 162 for standard SNMP (requires root)"
}
)
community: str = pydantic.Field(
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

pydantic.Field does not accept a 'metadata' kwarg; this will raise a TypeError at import time. For pydantic dataclasses, use dataclasses.field(metadata={...}, default=...) (see other providers' *ProviderAuthConfig classes).

Suggested change
port: int = pydantic.Field(
default=DEFAULTPORT, # SNMP default port
metadata={
"required": True,
"description": "SNMP port for listening for traps",
"hint": "SNMP defaults to 1162; elevate to port 162 for standard SNMP (requires root)"
}
)
community: str = pydantic.Field(
port: int = dataclasses.field(
default=DEFAULTPORT, # SNMP default port
metadata={
"required": True,
"description": "SNMP port for listening for traps",
"hint": "SNMP defaults to 1162; elevate to port 162 for standard SNMP (requires root)"
}
)
community: str = dataclasses.field(

Copilot uses AI. Check for mistakes.
from keep.providers.snmp_provider.config import SNMPProviderAuthConfig


class SNMPProvider( BaseProvider ): # SNMP Provider that listens for SNMP traps and pushes them as alerts to Keep.
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

ProvidersFactory resolves provider classes by name (e.g., for provider_type='snmp' it imports keep.providers.snmp_provider.snmp_provider and looks for a class named 'SnmpProvider'). The class here is named 'SNMPProvider', so provider initialization will fail with AttributeError. Rename the provider class to 'SnmpProvider' (or adjust factory naming, but that would be a wider change) and ensure exports/use-sites follow the same name.

Suggested change
class SNMPProvider( BaseProvider ): # SNMP Provider that listens for SNMP traps and pushes them as alerts to Keep.
class SnmpProvider( BaseProvider ): # SNMP Provider that listens for SNMP traps and pushes them as alerts to Keep.

Copilot uses AI. Check for mistakes.
super().dispose()

def _process_trap( self, snmpEngine, stateReference, contextEngineId, contextName, varBinds, cbCtx ): # Callback function executed when an SNMP trap is received.
transportDomain, transportAddress = snmpEngine.msgAndPduDsp.getTransportInfo( stateReference )
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

transportDomain is assigned but never used, which will fail ruff/flake8 (F841) in CI. Use '_' for the unused value or remove the assignment.

Suggested change
transportDomain, transportAddress = snmpEngine.msgAndPduDsp.getTransportInfo( stateReference )
_, transportAddress = snmpEngine.msgAndPduDsp.getTransportInfo( stateReference )

Copilot uses AI. Check for mistakes.
"name": "SNMP Trap",
"source": ["snmp"],
"description": f"Trap received from { source_ip }",
"status": "info", # Default status could be mapped from specific OIDs
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

The emitted alert payload uses status='info', but Keep's AlertStatus only accepts values like 'firing', 'resolved', etc. This will cause AlertDto validation to fail inside BaseProvider._push_alert(). Use a valid status (typically 'firing' for traps) and keep 'info' in the severity field.

Suggested change
"status": "info", # Default status could be mapped from specific OIDs
"status": "firing", # Default status for traps; detailed level stays in severity

Copilot uses AI. Check for mistakes.
Comment on lines +73 to +75

self.logger.info( f"Starting SNMP Trap listener on { host }:{ port } with community '{ community }'" )

Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

The startup log line includes the SNMP community string. Community strings function as shared secrets and shouldn't be logged. Avoid logging it (or mask it), and mark the community field as sensitive in the auth config metadata so it’s not displayed in cleartext in the UI.

Suggested change
self.logger.info( f"Starting SNMP Trap listener on { host }:{ port } with community '{ community }'" )
# Do not log the community string in cleartext; mask it in logs
masked_community = "*" * max(len(str(community)), 8)
self.logger.info( f"Starting SNMP Trap listener on { host }:{ port } with community '{ masked_community }'" )

Copilot uses AI. Check for mistakes.
from keep.providers.models.provider_config import ProviderConfig
from keep.providers.snmp_provider.config import SNMPProviderAuthConfig


Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

ProvidersFactory.get_provider_required_config expects the auth config class to be named 'SnmpProviderAuthConfig' and to be defined in keep.providers.snmp_provider.snmp_provider (same module as the provider). Defining 'SNMPProviderAuthConfig' in a separate config.py means the UI/config discovery will return {}. Move/duplicate/re-export the auth config class into snmp_provider.py with the expected name, or adjust the factory if you want a different pattern.

Suggested change
class SnmpProviderAuthConfig(SNMPProviderAuthConfig):
"""
Compatibility wrapper so ProvidersFactory.get_provider_required_config can
discover the SNMP provider auth config in this module under the expected name.
"""
pass

Copilot uses AI. Check for mistakes.
@@ -0,0 +1 @@
from .snmp_provider import SNMPProvider No newline at end of file
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

keep/providers/snmp_provider/init.py exports SNMPProvider, but provider loading via ProvidersFactory expects 'SnmpProvider' for provider_type='snmp'. After renaming the class for factory compatibility, update this export accordingly.

Suggested change
from .snmp_provider import SNMPProvider
from .snmp_provider import SnmpProvider

Copilot uses AI. Check for mistakes.
class SNMPProvider( BaseProvider ): # SNMP Provider that listens for SNMP traps and pushes them as alerts to Keep.
def __init__( self, context_manager, provider_id: str, config: ProviderConfig ):
super().__init__( context_manager, provider_id, config )
self.logger = logging.getLogger( __name__ )
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

init overwrites BaseProvider's logger (which is configured with provider_id, log level env vars, and optional DB-backed ProviderLoggerAdapter). Reassigning self.logger to logging.getLogger(name) drops that context/config. Use the logger provided by BaseProvider instead of overwriting it.

Suggested change
self.logger = logging.getLogger( __name__ )

Copilot uses AI. Check for mistakes.
@natkon20043 natkon20043 marked this pull request as ready for review February 28, 2026 21:54
@natkon20043
Copy link
Author

2026-02-28.16-32-01.mp4

Here is the new verison of the snmp provider. Sorry it has taken me so long to complete, I am currently in college aswell as a teacher so my time is thin, I hope the newest version is good though!

@natkon20043
Copy link
Author

I had to change my pydantic.Field metadata to dataclass.field, this was why it would not show up on the GUI, I also updated the API since it seems pydantic switched from camel case to snake case. I also fixed my commenting style and removed excess ai comments in my functions

@natkon20043
Copy link
Author

I am just waiting on vercel auth now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature A new feature Provider Providers related issues size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[🔌 Provider]: SNMP provider

4 participants