Merge pull request #5 from TheNetworkGuy/secrets

Add Netbox secrets functionality
This commit is contained in:
Twan K 2021-04-29 09:43:08 +02:00 committed by GitHub
commit 64c41564e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 53 additions and 7 deletions

View File

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

View File

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