mirror of
https://github.com/TheNetworkGuy/netbox-zabbix-sync.git
synced 2025-07-16 04:02:56 -06:00
Merge pull request #5 from TheNetworkGuy/secrets
Add Netbox secrets functionality
This commit is contained in:
commit
64c41564e1
13
README.md
13
README.md
@ -10,6 +10,8 @@ A script to sync the Netbox device inventory to Zabbix.
|
|||||||
* ZABBIX_PASS="Password"
|
* ZABBIX_PASS="Password"
|
||||||
* NETBOX_HOST="https://netbox.local"
|
* NETBOX_HOST="https://netbox.local"
|
||||||
* NETBOX_TOKEN="secrettoken"
|
* NETBOX_TOKEN="secrettoken"
|
||||||
|
Optional:
|
||||||
|
* NETBOX_KEY="Private user key"
|
||||||
|
|
||||||
#### Flags
|
#### Flags
|
||||||
| Flag | Option | Description |
|
| Flag | Option | Description |
|
||||||
@ -17,7 +19,8 @@ A script to sync the Netbox device inventory to Zabbix.
|
|||||||
| -c | cluster | For clustered devices: only add the primary node of a cluster and use the cluster name as hostname. |
|
| -c | cluster | For clustered devices: only add the primary node of a cluster and use the cluster name as hostname. |
|
||||||
| -H | hostgroup | Create non-existing hostgroups in Zabbix. Usefull for a first run to add all required hostgroups. |
|
| -H | hostgroup | Create non-existing hostgroups in Zabbix. Usefull for a first run to add all required hostgroups. |
|
||||||
| -t | tenant | Add the tenant name to the hostgroup format (Tenant/Site/Manufacturer/Role) |
|
| -t | tenant | Add the tenant name to the hostgroup format (Tenant/Site/Manufacturer/Role) |
|
||||||
| -v | Verbose | Log with debugging on. |
|
| -s | secret | Use Netbox secrets if present on device for SNMP parameters
|
||||||
|
| -v | verbose | Log with debugging on. |
|
||||||
|
|
||||||
|
|
||||||
#### Logging
|
#### Logging
|
||||||
@ -30,7 +33,7 @@ In case of omitting the -H flag, manual hostgroup creation is required for devic
|
|||||||
This is in the format:
|
This is in the format:
|
||||||
{Site name}/{Manufacturer name}/{Device role name}
|
{Site name}/{Manufacturer name}/{Device role name}
|
||||||
And with tenants (-t flag):
|
And with tenants (-t flag):
|
||||||
{Tenant name}/{Site name}/{Manufacturer name}/{Device role name}
|
{Site name}/{Tenant name}/{Manufacturer name}/{Device role name}
|
||||||
|
|
||||||
Make sure that the Zabbix user has proper permissions to create hosts.
|
Make sure that the Zabbix user has proper permissions to create hosts.
|
||||||
The hostgroups are in a nested format. This means that proper permissions only need to be applied to the site name hostgroup and cascaded to any child hostgroups.
|
The hostgroups are in a nested format. This means that proper permissions only need to be applied to the site name hostgroup and cascaded to any child hostgroups.
|
||||||
@ -109,6 +112,12 @@ To configure the interface parameters you'll need to use custom context. Custom
|
|||||||
```
|
```
|
||||||
Note: Not all SNMP data is required for a working configuration. [The following parameters are allowed ](https://www.zabbix.com/documentation/current/manual/api/reference/hostinterface/object#details_tag "The following parameters are allowed ")but are not all required, depending on your environment.
|
Note: Not all SNMP data is required for a working configuration. [The following parameters are allowed ](https://www.zabbix.com/documentation/current/manual/api/reference/hostinterface/object#details_tag "The following parameters are allowed ")but are not all required, depending on your environment.
|
||||||
|
|
||||||
|
##### Secrets
|
||||||
|
Instead of having the password in plain-text in the config context, you can also set the password as secret in the Netbox device configuration.
|
||||||
|
You will need to use the -s option for this. Keep in mind that you will need a Netbox private user key for this functionality.
|
||||||
|
|
||||||
|
This method of setting device SNMP parameters is working, but i would recommend going for a "secret macro" implementation to keep your environment more predictable. Refer to the macro from the config context and set the macro inside of Zabbix to the actual community string / authentication secret etc.
|
||||||
|
|
||||||
#### Permissions
|
#### Permissions
|
||||||
Make sure that the user has proper permissions for device read and modify (modify to set the Zabbix HostID custom field) operations.
|
Make sure that the user has proper permissions for device read and modify (modify to set the Zabbix HostID custom field) operations.
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import logging
|
|||||||
import argparse
|
import argparse
|
||||||
from pynetbox import api
|
from pynetbox import api
|
||||||
from pyzabbix import ZabbixAPI, ZabbixAPIException
|
from pyzabbix import ZabbixAPI, ZabbixAPIException
|
||||||
|
from pynetbox.core.query import RequestError as NetboxRequestError
|
||||||
|
|
||||||
# Set logging
|
# Set logging
|
||||||
log_format = logging.Formatter('%(asctime)s - %(name)s - '
|
log_format = logging.Formatter('%(asctime)s - %(name)s - '
|
||||||
@ -46,7 +47,13 @@ def main(arguments):
|
|||||||
zabbix_pass = environ.get("ZABBIX_PASS")
|
zabbix_pass = environ.get("ZABBIX_PASS")
|
||||||
netbox_host = environ.get("NETBOX_HOST")
|
netbox_host = environ.get("NETBOX_HOST")
|
||||||
netbox_token = environ.get("NETBOX_TOKEN")
|
netbox_token = environ.get("NETBOX_TOKEN")
|
||||||
|
netbox_key = environ.get("NETBOX_KEY")
|
||||||
|
|
||||||
|
if(arguments.secret and not netbox_key):
|
||||||
|
e = ("You need a private user key to"
|
||||||
|
" use the Netbox secrets functionality.")
|
||||||
|
logger.warning(e)
|
||||||
|
EnvironmentVarError(e)
|
||||||
# Set Zabbix API
|
# Set Zabbix API
|
||||||
try:
|
try:
|
||||||
zabbix = ZabbixAPI(zabbix_host)
|
zabbix = ZabbixAPI(zabbix_host)
|
||||||
@ -55,7 +62,8 @@ def main(arguments):
|
|||||||
e = f"Zabbix returned the following error: {str(e)}."
|
e = f"Zabbix returned the following error: {str(e)}."
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
# Set Netbox API
|
# Set Netbox API
|
||||||
netbox = api(netbox_host, netbox_token, threading=True)
|
netbox = api(netbox_host, token=netbox_token,
|
||||||
|
private_key=netbox_key, threading=True)
|
||||||
# Get all Zabbix and Netbox data
|
# Get all Zabbix and Netbox data
|
||||||
netbox_devices = netbox.dcim.devices.all()
|
netbox_devices = netbox.dcim.devices.all()
|
||||||
zabbix_groups = zabbix.hostgroup.get(output=['name'])
|
zabbix_groups = zabbix.hostgroup.get(output=['name'])
|
||||||
@ -89,6 +97,9 @@ def main(arguments):
|
|||||||
else:
|
else:
|
||||||
logger.debug(f"{device.name} is not linked to a tenant. "
|
logger.debug(f"{device.name} is not linked to a tenant. "
|
||||||
f"Using HG format '{device.hostgroup}'.")
|
f"Using HG format '{device.hostgroup}'.")
|
||||||
|
# -s flag: collect secrets from this device
|
||||||
|
if(arguments.secret):
|
||||||
|
device.getNetboxSecrets(netbox)
|
||||||
# Checks if device is in cleanup state
|
# Checks if device is in cleanup state
|
||||||
if(device.status != "Active"):
|
if(device.status != "Active"):
|
||||||
if(device.zabbix_id):
|
if(device.zabbix_id):
|
||||||
@ -163,6 +174,7 @@ class NetworkDevice():
|
|||||||
self.zabbix = zabbix
|
self.zabbix = zabbix
|
||||||
self.tenant = nb.tenant
|
self.tenant = nb.tenant
|
||||||
self.hostgroup = None
|
self.hostgroup = None
|
||||||
|
self.secrets = None
|
||||||
self.hg_format = [self.nb.site.name,
|
self.hg_format = [self.nb.site.name,
|
||||||
self.nb.device_type.manufacturer.name,
|
self.nb.device_type.manufacturer.name,
|
||||||
self.nb.device_role.name]
|
self.nb.device_role.name]
|
||||||
@ -237,11 +249,25 @@ class NetworkDevice():
|
|||||||
f"Modifying hostname from {self.name} to " +
|
f"Modifying hostname from {self.name} to " +
|
||||||
f"{self.nb.virtual_chassis.name}.")
|
f"{self.nb.virtual_chassis.name}.")
|
||||||
self.name = self.nb.virtual_chassis.name
|
self.name = self.nb.virtual_chassis.name
|
||||||
|
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
logger.debug(f"Device {self.name} is non-primary cluster member.")
|
logger.debug(f"Device {self.name} is non-primary cluster member.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def getNetboxSecrets(self, nbapi):
|
||||||
|
"""
|
||||||
|
Get secrets from this Netbox host
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.secrets = nbapi.secrets.secrets.filter(device=self.nb.name)
|
||||||
|
if(self.secrets):
|
||||||
|
logger.debug(f"Got {len(self.secrets)} secret(s)"
|
||||||
|
f" for host {self.name}.")
|
||||||
|
except NetboxRequestError as e:
|
||||||
|
e = f"Device {self.name}: unable to get Netbox secrets, error: {e}"
|
||||||
|
logger.warning(e)
|
||||||
|
|
||||||
def getZabbixTemplate(self, templates):
|
def getZabbixTemplate(self, templates):
|
||||||
"""
|
"""
|
||||||
Returns Zabbix template ID
|
Returns Zabbix template ID
|
||||||
@ -319,7 +345,8 @@ class NetworkDevice():
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Initiate interface class
|
# Initiate interface class
|
||||||
interface = ZabbixInterface(self.nb.config_context, self.ip)
|
interface = ZabbixInterface(self.nb.config_context,
|
||||||
|
self.ip, self.secrets)
|
||||||
# Check if Netbox has device context.
|
# Check if Netbox has device context.
|
||||||
# If not fall back to old config.
|
# If not fall back to old config.
|
||||||
if(interface.get_context()):
|
if(interface.get_context()):
|
||||||
@ -489,8 +516,6 @@ class NetworkDevice():
|
|||||||
raise InterfaceConfigError(e)
|
raise InterfaceConfigError(e)
|
||||||
# Set interfaceID for Zabbix config
|
# Set interfaceID for Zabbix config
|
||||||
updates["interfaceid"] = host["interfaces"][0]['interfaceid']
|
updates["interfaceid"] = host["interfaces"][0]['interfaceid']
|
||||||
logger.debug(f"{self.name}: Updating interface with "
|
|
||||||
f"config {updates}")
|
|
||||||
try:
|
try:
|
||||||
# API call to Zabbix
|
# API call to Zabbix
|
||||||
self.zabbix.hostinterface.update(updates)
|
self.zabbix.hostinterface.update(updates)
|
||||||
@ -513,8 +538,9 @@ class NetworkDevice():
|
|||||||
|
|
||||||
|
|
||||||
class ZabbixInterface():
|
class ZabbixInterface():
|
||||||
def __init__(self, context, ip):
|
def __init__(self, context, ip, secrets=None):
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.secrets = secrets
|
||||||
self.type = None
|
self.type = None
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.skelet = {"main": "1", "useip": "1", "dns": "", "ip": self.ip}
|
self.skelet = {"main": "1", "useip": "1", "dns": "", "ip": self.ip}
|
||||||
@ -542,6 +568,15 @@ class ZabbixInterface():
|
|||||||
# Checks if SNMP settings are defined in Netbox
|
# Checks if SNMP settings are defined in Netbox
|
||||||
if("snmp" in self.context["zabbix"]):
|
if("snmp" in self.context["zabbix"]):
|
||||||
snmp = self.context["zabbix"]["snmp"]
|
snmp = self.context["zabbix"]["snmp"]
|
||||||
|
# Check if matching SNMP Netbox secret is found for host.
|
||||||
|
supported_secrets = ["community", "authpassphrase",
|
||||||
|
"privpassphrase"]
|
||||||
|
# Check if Netbox host has secrets
|
||||||
|
if(self.secrets):
|
||||||
|
for secret in self.secrets:
|
||||||
|
# If secret is supported, add to SNMP details
|
||||||
|
if(secret.name in supported_secrets):
|
||||||
|
snmp[secret.name] = secret.plaintext
|
||||||
self.interface["details"] = {}
|
self.interface["details"] = {}
|
||||||
# Checks if bulk config has been defined
|
# Checks if bulk config has been defined
|
||||||
if(snmp.get("bulk")):
|
if(snmp.get("bulk")):
|
||||||
@ -610,6 +645,8 @@ if(__name__ == "__main__"):
|
|||||||
parser.add_argument("-t", "--tenant", action="store_true",
|
parser.add_argument("-t", "--tenant", action="store_true",
|
||||||
help=("Add Tenant name to the Zabbix "
|
help=("Add Tenant name to the Zabbix "
|
||||||
"hostgroup name scheme."))
|
"hostgroup name scheme."))
|
||||||
|
parser.add_argument("-s", "--secret", action="store_true",
|
||||||
|
help=("Use Netbox secrets for SNMP interfaces."))
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
main(args)
|
main(args)
|
||||||
|
Loading…
Reference in New Issue
Block a user