mirror of
https://github.com/bb-Ricardo/netbox-sync.git
synced 2026-01-27 19:39:13 -06:00
WIP: adds permitted subnet class and finishes redfish import
This commit is contained in:
@@ -20,35 +20,38 @@ class CommonConfig(ConfigBase):
|
||||
|
||||
section_name = common_config_section_name
|
||||
|
||||
options = [
|
||||
ConfigOption("log_level",
|
||||
str,
|
||||
description="""\
|
||||
Logs will always be printed to stdout/stderr.
|
||||
Logging can be set to following log levels:
|
||||
ERROR: Fatal Errors which stops regular a run
|
||||
WARNING: Warning messages won't stop the syncing process but mostly worth
|
||||
to have a look at.
|
||||
INFO: Information about objects that will be create/updated/deleted in NetBox
|
||||
DEBUG: Will log information about retrieved information, changes in internal
|
||||
content structure and parsed config
|
||||
DEBUG2: Will also log information about how/why content is parsed or skipped.
|
||||
DEBUG3: Logs all source and NetBox queries/results to stdout. Very useful for
|
||||
troubleshooting, but will log any sensitive content contained within a query.
|
||||
""",
|
||||
default_value="INFO"),
|
||||
def __init__(self):
|
||||
self.options = [
|
||||
ConfigOption("log_level",
|
||||
str,
|
||||
description="""\
|
||||
Logs will always be printed to stdout/stderr.
|
||||
Logging can be set to following log levels:
|
||||
ERROR: Fatal Errors which stops regular a run
|
||||
WARNING: Warning messages won't stop the syncing process but mostly worth
|
||||
to have a look at.
|
||||
INFO: Information about objects that will be create/updated/deleted in NetBox
|
||||
DEBUG: Will log information about retrieved information, changes in internal
|
||||
content structure and parsed config
|
||||
DEBUG2: Will also log information about how/why content is parsed or skipped.
|
||||
DEBUG3: Logs all source and NetBox queries/results to stdout. Very useful for
|
||||
troubleshooting, but will log any sensitive content contained within a query.
|
||||
""",
|
||||
default_value="INFO"),
|
||||
|
||||
ConfigOption("log_to_file",
|
||||
bool,
|
||||
description="""Enabling this options will write all
|
||||
logs to a log file defined in 'log_file'
|
||||
""",
|
||||
default_value=True),
|
||||
ConfigOption("log_to_file",
|
||||
bool,
|
||||
description="""Enabling this options will write all
|
||||
logs to a log file defined in 'log_file'
|
||||
""",
|
||||
default_value=True),
|
||||
|
||||
ConfigOption("log_file",
|
||||
str,
|
||||
description="""Destination of the log file if "log_to_file" is enabled.
|
||||
Log file will be rotated maximum 5 times once the log file reaches size of 10 MB
|
||||
""",
|
||||
default_value="log/netbox_sync.log")
|
||||
]
|
||||
ConfigOption("log_file",
|
||||
str,
|
||||
description="""Destination of the log file if "log_to_file" is enabled.
|
||||
Log file will be rotated maximum 5 times once the log file reaches size of 10 MB
|
||||
""",
|
||||
default_value="log/netbox_sync.log")
|
||||
]
|
||||
|
||||
super().__init__()
|
||||
|
||||
@@ -36,6 +36,9 @@ class ConfigBase:
|
||||
def validate_options(self):
|
||||
pass
|
||||
|
||||
def set_validation_failed(self):
|
||||
self._parsing_failed = True
|
||||
|
||||
def parse(self, do_log: bool = True):
|
||||
|
||||
def _log(handler, message):
|
||||
@@ -113,7 +116,8 @@ class ConfigBase:
|
||||
|
||||
options[config_object.key] = config_object.value
|
||||
|
||||
config_options = get_value() or dict()
|
||||
# check for unknown config options
|
||||
config_options = get_value()
|
||||
if not isinstance(config_options, dict):
|
||||
config_options = dict()
|
||||
|
||||
|
||||
@@ -22,114 +22,117 @@ class NetBoxConfig(ConfigBase):
|
||||
|
||||
section_name = netbox_config_section_name
|
||||
|
||||
options = [
|
||||
ConfigOption("api_token",
|
||||
str,
|
||||
description="""Requires an NetBox API token with full permissions on all objects except
|
||||
'auth', 'secrets' and 'users'
|
||||
""",
|
||||
config_example="XYZ",
|
||||
mandatory=True,
|
||||
sensitive=True),
|
||||
def __init__(self):
|
||||
self.options = [
|
||||
ConfigOption("api_token",
|
||||
str,
|
||||
description="""Requires an NetBox API token with full permissions on all objects except
|
||||
'auth', 'secrets' and 'users'
|
||||
""",
|
||||
config_example="XYZ",
|
||||
mandatory=True,
|
||||
sensitive=True),
|
||||
|
||||
ConfigOption("host_fqdn",
|
||||
str,
|
||||
description="Requires a hostname or IP which points to your NetBox instance",
|
||||
config_example="netbox.example.com",
|
||||
mandatory=True),
|
||||
ConfigOption("host_fqdn",
|
||||
str,
|
||||
description="Requires a hostname or IP which points to your NetBox instance",
|
||||
config_example="netbox.example.com",
|
||||
mandatory=True),
|
||||
|
||||
ConfigOption("port",
|
||||
int,
|
||||
description="""Define the port your NetBox instance is listening on.
|
||||
If 'disable_tls' is set to "true" this option might be set to 80
|
||||
""",
|
||||
default_value=443),
|
||||
ConfigOption("port",
|
||||
int,
|
||||
description="""Define the port your NetBox instance is listening on.
|
||||
If 'disable_tls' is set to "true" this option might be set to 80
|
||||
""",
|
||||
default_value=443),
|
||||
|
||||
ConfigOption("disable_tls",
|
||||
bool,
|
||||
description="Whether TLS encryption is enabled or disabled",
|
||||
default_value=False),
|
||||
ConfigOption("disable_tls",
|
||||
bool,
|
||||
description="Whether TLS encryption is enabled or disabled",
|
||||
default_value=False),
|
||||
|
||||
ConfigOption("validate_tls_certs",
|
||||
bool,
|
||||
description="""Enforces TLS certificate validation. If this system doesn't trust the NetBox
|
||||
web server certificate then this option needs to be changed
|
||||
""",
|
||||
default_value=True),
|
||||
ConfigOption("validate_tls_certs",
|
||||
bool,
|
||||
description="""Enforces TLS certificate validation. If this system doesn't trust the NetBox
|
||||
web server certificate then this option needs to be changed
|
||||
""",
|
||||
default_value=True),
|
||||
|
||||
ConfigOption("proxy",
|
||||
str,
|
||||
description="""Defines a proxy which will be used to connect to NetBox.
|
||||
Proxy setting needs to include the schema.
|
||||
Proxy basic auth example: http://user:pass@10.10.1.10:312
|
||||
""",
|
||||
config_example="http://example.com:3128"),
|
||||
ConfigOption("proxy",
|
||||
str,
|
||||
description="""Defines a proxy which will be used to connect to NetBox.
|
||||
Proxy setting needs to include the schema.
|
||||
Proxy basic auth example: http://user:pass@10.10.1.10:312
|
||||
""",
|
||||
config_example="http://example.com:3128"),
|
||||
|
||||
ConfigOption("client_cert",
|
||||
str,
|
||||
description="Specify a client certificate which can be used to authenticate to NetBox",
|
||||
config_example="client.pem"),
|
||||
ConfigOption("client_cert",
|
||||
str,
|
||||
description="Specify a client certificate which can be used to authenticate to NetBox",
|
||||
config_example="client.pem"),
|
||||
|
||||
ConfigOption("client_cert_key",
|
||||
str,
|
||||
description="Specify the client certificate private key belonging to the client cert",
|
||||
config_example="client.key"),
|
||||
ConfigOption("client_cert_key",
|
||||
str,
|
||||
description="Specify the client certificate private key belonging to the client cert",
|
||||
config_example="client.key"),
|
||||
|
||||
ConfigOption("prune_enabled",
|
||||
bool,
|
||||
description="""Whether items which were created by this program but
|
||||
can't be found in any source anymore will be deleted or not
|
||||
""",
|
||||
default_value=False),
|
||||
ConfigOption("prune_enabled",
|
||||
bool,
|
||||
description="""Whether items which were created by this program but
|
||||
can't be found in any source anymore will be deleted or not
|
||||
""",
|
||||
default_value=False),
|
||||
|
||||
ConfigOption("prune_delay_in_days",
|
||||
int,
|
||||
description="""Orphaned objects will first be tagged before they get deleted.
|
||||
Once the amount of days passed the object will actually be deleted
|
||||
""",
|
||||
default_value=30),
|
||||
ConfigOption("prune_delay_in_days",
|
||||
int,
|
||||
description="""Orphaned objects will first be tagged before they get deleted.
|
||||
Once the amount of days passed the object will actually be deleted
|
||||
""",
|
||||
default_value=30),
|
||||
|
||||
ConfigOption("ignore_unknown_source_object_pruning",
|
||||
bool,
|
||||
description="""This will tell netbox-sync to ignore objects in NetBox
|
||||
with tag 'NetBox-synced' from pruning if the source is not defined in
|
||||
this config file (https://github.com/bb-Ricardo/netbox-sync/issues/176)
|
||||
""",
|
||||
default_value=False),
|
||||
ConfigOption("ignore_unknown_source_object_pruning",
|
||||
bool,
|
||||
description="""This will tell netbox-sync to ignore objects in NetBox
|
||||
with tag 'NetBox-synced' from pruning if the source is not defined in
|
||||
this config file (https://github.com/bb-Ricardo/netbox-sync/issues/176)
|
||||
""",
|
||||
default_value=False),
|
||||
|
||||
ConfigOption("default_netbox_result_limit",
|
||||
int,
|
||||
description="""The maximum number of objects returned in a single request.
|
||||
If a NetBox instance is very quick responding the value should be raised
|
||||
""",
|
||||
default_value=200),
|
||||
ConfigOption("default_netbox_result_limit",
|
||||
int,
|
||||
description="""The maximum number of objects returned in a single request.
|
||||
If a NetBox instance is very quick responding the value should be raised
|
||||
""",
|
||||
default_value=200),
|
||||
|
||||
ConfigOption("timeout",
|
||||
int,
|
||||
description="""The maximum time a query is allowed to execute before being
|
||||
killed and considered failed
|
||||
""",
|
||||
default_value=30),
|
||||
ConfigOption("timeout",
|
||||
int,
|
||||
description="""The maximum time a query is allowed to execute before being
|
||||
killed and considered failed
|
||||
""",
|
||||
default_value=30),
|
||||
|
||||
ConfigOption("max_retry_attempts",
|
||||
int,
|
||||
description="""The amount of times a failed request will be reissued.
|
||||
Once the maximum is reached the syncing process will be stopped completely.
|
||||
""",
|
||||
default_value=4),
|
||||
ConfigOption("max_retry_attempts",
|
||||
int,
|
||||
description="""The amount of times a failed request will be reissued.
|
||||
Once the maximum is reached the syncing process will be stopped completely.
|
||||
""",
|
||||
default_value=4),
|
||||
|
||||
ConfigOption("use_caching",
|
||||
bool,
|
||||
description="""Defines if caching of NetBox objects is used or not.
|
||||
If problems with unresolved dependencies occur, switching off caching might help.
|
||||
""",
|
||||
default_value=True),
|
||||
ConfigOption("use_caching",
|
||||
bool,
|
||||
description="""Defines if caching of NetBox objects is used or not.
|
||||
If problems with unresolved dependencies occur, switching off caching might help.
|
||||
""",
|
||||
default_value=True),
|
||||
|
||||
ConfigOption("cache_directory_location",
|
||||
str,
|
||||
description="The location of the directory where the cache files should be stored",
|
||||
default_value="cache")
|
||||
]
|
||||
ConfigOption("cache_directory_location",
|
||||
str,
|
||||
description="The location of the directory where the cache files should be stored",
|
||||
default_value="cache")
|
||||
]
|
||||
|
||||
super().__init__()
|
||||
|
||||
def validate_options(self):
|
||||
|
||||
@@ -140,4 +143,4 @@ class NetBoxConfig(ConfigBase):
|
||||
(not option.value.startswith("http") and not option.value.startswith("socks5")):
|
||||
log.error(f"Config option 'proxy' in '{NetBoxConfig.section_name}' must contain the schema "
|
||||
f"http, https, socks5 or socks5h")
|
||||
self._parsing_failed = True
|
||||
self.set_validation_failed()
|
||||
|
||||
@@ -7,10 +7,16 @@
|
||||
# For a copy, see file LICENSE.txt included in this
|
||||
# repository or visit: <https://opensource.org/licenses/MIT>.
|
||||
|
||||
import os
|
||||
|
||||
from module.config import source_config_section_name
|
||||
from module.config.base import ConfigBase
|
||||
from module.config.option import ConfigOption
|
||||
from module.sources.common.source_base import config_option_enabled, config_option_permitted_subnets
|
||||
from module.sources.common.conifg import *
|
||||
from module.common.logging import get_logger
|
||||
from module.sources.common.permitted_subnets import PermittedSubnets
|
||||
|
||||
log = get_logger()
|
||||
|
||||
|
||||
class CheckRedfishConfig(ConfigBase):
|
||||
@@ -18,49 +24,69 @@ class CheckRedfishConfig(ConfigBase):
|
||||
section_name = source_config_section_name
|
||||
source_name = None
|
||||
|
||||
options = [
|
||||
config_option_enabled,
|
||||
def __init__(self):
|
||||
self.options = [
|
||||
ConfigOption(**config_option_enabled_definition),
|
||||
|
||||
ConfigOption("type",
|
||||
str,
|
||||
description="type of source. This defines which source handler to use",
|
||||
config_example="check_redfish",
|
||||
mandatory=True),
|
||||
ConfigOption("type",
|
||||
str,
|
||||
description="type of source. This defines which source handler to use",
|
||||
config_example="check_redfish",
|
||||
mandatory=True),
|
||||
|
||||
ConfigOption("inventory_file_path",
|
||||
str,
|
||||
description="define the full path where the check_redfish inventory json files are located",
|
||||
mandatory=True),
|
||||
ConfigOption("inventory_file_path",
|
||||
str,
|
||||
description="define the full path where the check_redfish inventory json files are located",
|
||||
mandatory=True),
|
||||
|
||||
config_option_permitted_subnets,
|
||||
ConfigOption(**config_option_permitted_subnets_definition),
|
||||
|
||||
ConfigOption("overwrite_host_name",
|
||||
bool,
|
||||
description="""define if the host name discovered via check_redfish
|
||||
overwrites the device host name in NetBox""",
|
||||
default_value=False),
|
||||
ConfigOption("overwrite_host_name",
|
||||
bool,
|
||||
description="""define if the host name discovered via check_redfish
|
||||
overwrites the device host name in NetBox""",
|
||||
default_value=False),
|
||||
|
||||
ConfigOption("overwrite_power_supply_name",
|
||||
bool,
|
||||
description="""define if the name of the power supply discovered via check_redfish
|
||||
overwrites the power supply name in NetBox""",
|
||||
default_value=False),
|
||||
ConfigOption("overwrite_power_supply_name",
|
||||
bool,
|
||||
description="""define if the name of the power supply discovered via check_redfish
|
||||
overwrites the power supply name in NetBox""",
|
||||
default_value=False),
|
||||
|
||||
ConfigOption("overwrite_power_supply_attributes",
|
||||
bool,
|
||||
description="""define if existing power supply attributes are overwritten with data discovered
|
||||
via check_redfish if False only data which is not preset in NetBox will be added""",
|
||||
default_value=True),
|
||||
ConfigOption("overwrite_power_supply_attributes",
|
||||
bool,
|
||||
description="""define if existing power supply attributes are overwritten with data discovered
|
||||
via check_redfish if False only data which is not preset in NetBox will be added""",
|
||||
default_value=True),
|
||||
|
||||
ConfigOption("overwrite_interface_name",
|
||||
bool,
|
||||
description="""define if the name of the interface discovered via check_redfish
|
||||
overwrites the interface name in NetBox""",
|
||||
default_value=False),
|
||||
ConfigOption("overwrite_interface_name",
|
||||
bool,
|
||||
description="""define if the name of the interface discovered via check_redfish
|
||||
overwrites the interface name in NetBox""",
|
||||
default_value=False),
|
||||
|
||||
ConfigOption("overwrite_interface_attributes",
|
||||
bool,
|
||||
description="""define if existing interface attributes are overwritten with data discovered
|
||||
via check_redfish if False only data which is not preset in NetBox will be added""",
|
||||
default_value=True)
|
||||
]
|
||||
ConfigOption("overwrite_interface_attributes",
|
||||
bool,
|
||||
description="""define if existing interface attributes are overwritten with data discovered
|
||||
via check_redfish if False only data which is not preset in NetBox will be added""",
|
||||
default_value=True)
|
||||
]
|
||||
|
||||
super().__init__()
|
||||
|
||||
def validate_options(self):
|
||||
|
||||
for option in self.options:
|
||||
|
||||
if option.key == "inventory_file_path":
|
||||
if not os.path.exists(option.value):
|
||||
log.error(f"Inventory file path '{option.value}' not found.")
|
||||
self.set_validation_failed()
|
||||
|
||||
if os.path.isfile(option.value):
|
||||
log.error(f"Inventory file path '{option.value}' needs to be a directory.")
|
||||
self.set_validation_failed()
|
||||
|
||||
if not os.access(option.value, os.X_OK | os.R_OK):
|
||||
log.error(f"Inventory file path '{option.value}' not readable.")
|
||||
self.set_validation_failed()
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
# For a copy, see file LICENSE.txt included in this
|
||||
# repository or visit: <https://opensource.org/licenses/MIT>.
|
||||
|
||||
from ipaddress import ip_network
|
||||
import os
|
||||
import glob
|
||||
import json
|
||||
@@ -18,8 +17,9 @@ from module.sources.common.source_base import SourceBase
|
||||
from module.sources.check_redfish.config import CheckRedfishConfig
|
||||
from module.common.logging import get_logger
|
||||
from module.common.misc import grab, get_string_or_none
|
||||
from module.common.support import normalize_mac_address, ip_valid_to_add_to_netbox
|
||||
from module.common.support import normalize_mac_address
|
||||
from module.netbox.inventory import NetBoxInventory
|
||||
from module.sources.common.permitted_subnets import PermittedSubnets
|
||||
from module.netbox.object_classes import (
|
||||
NetBoxInterfaceType,
|
||||
NBTag,
|
||||
@@ -103,81 +103,15 @@ class CheckRedfish(SourceBase):
|
||||
log.info(f"Source '{name}' is currently disabled. Skipping")
|
||||
return
|
||||
|
||||
self.permitted_subnets = PermittedSubnets(self.settings.permitted_subnets)
|
||||
if self.permitted_subnets.validation_failed is True:
|
||||
log.error(f"Config parsing for source '{self.name}' failed.")
|
||||
return
|
||||
|
||||
self.init_successful = True
|
||||
|
||||
self.interface_adapter_type_dict = dict()
|
||||
|
||||
def parse_config_settings(self, config_settings):
|
||||
"""
|
||||
Validate parsed settings from config file
|
||||
|
||||
Parameters
|
||||
----------
|
||||
config_settings: dict
|
||||
dict of config settings
|
||||
|
||||
"""
|
||||
|
||||
validation_failed = False
|
||||
|
||||
for setting in ["inventory_file_path"]:
|
||||
if config_settings.get(setting) is None:
|
||||
log.error(f"Config option '{setting}' in 'source/{self.name}' can't be empty/undefined")
|
||||
validation_failed = True
|
||||
|
||||
inv_path = config_settings.get("inventory_file_path")
|
||||
if not os.path.exists(inv_path):
|
||||
log.error(f"Inventory file path '{inv_path}' not found.")
|
||||
validation_failed = True
|
||||
|
||||
if os.path.isfile(inv_path):
|
||||
log.error(f"Inventory file path '{inv_path}' needs to be a directory.")
|
||||
validation_failed = True
|
||||
|
||||
if not os.access(inv_path, os.X_OK | os.R_OK):
|
||||
log.error(f"Inventory file path '{inv_path}' not readable.")
|
||||
validation_failed = True
|
||||
|
||||
# check permitted ip subnets
|
||||
permitted_subnets = list()
|
||||
excluded_subnets = list()
|
||||
self.settings["excluded_subnets"] = excluded_subnets
|
||||
|
||||
if config_settings.get("permitted_subnets") is None:
|
||||
log.info(f"Config option 'permitted_subnets' in 'source/{self.name}' is undefined. "
|
||||
f"No IP addresses will be populated to NetBox!")
|
||||
else:
|
||||
config_settings["permitted_subnets"] = \
|
||||
[x.strip() for x in config_settings.get("permitted_subnets").split(",") if x.strip() != ""]
|
||||
|
||||
# add "invisible" config option
|
||||
self.settings["excluded_subnets"] = None
|
||||
|
||||
for subnet in config_settings["permitted_subnets"]:
|
||||
excluded = False
|
||||
if subnet[0] == "!":
|
||||
excluded = True
|
||||
subnet = subnet[1:].strip()
|
||||
|
||||
try:
|
||||
if excluded is True:
|
||||
excluded_subnets.append(ip_network(subnet))
|
||||
else:
|
||||
permitted_subnets.append(ip_network(subnet))
|
||||
except Exception as e:
|
||||
log.error(f"Problem parsing permitted subnet: {e}")
|
||||
validation_failed = True
|
||||
|
||||
config_settings["permitted_subnets"] = permitted_subnets
|
||||
config_settings["excluded_subnets"] = excluded_subnets
|
||||
|
||||
if validation_failed is True:
|
||||
log.error("Config validation failed. Exit!")
|
||||
exit(1)
|
||||
|
||||
for setting in self.settings.keys():
|
||||
setattr(self, setting, config_settings.get(setting))
|
||||
|
||||
def apply(self):
|
||||
"""
|
||||
Main source handler method. This method is called for each source from "main" program
|
||||
@@ -870,17 +804,13 @@ class CheckRedfish(SourceBase):
|
||||
# collect ip addresses
|
||||
nic_ips[port_name] = list()
|
||||
for ipv4_address in grab(nic_port, "ipv4_addresses", fallback=list()):
|
||||
if ip_valid_to_add_to_netbox(ipv4_address, self.settings.permitted_subnets,
|
||||
excluded_subnets=self.settings.excluded_subnets,
|
||||
interface_name=port_name) is False:
|
||||
if self.permitted_subnets.permitted(ipv4_address, interface_name=port_name) is False:
|
||||
continue
|
||||
|
||||
nic_ips[port_name].append(ipv4_address)
|
||||
|
||||
for ipv6_address in grab(nic_port, "ipv6_addresses", fallback=list()):
|
||||
if ip_valid_to_add_to_netbox(ipv6_address, self.settings.permitted_subnets,
|
||||
excluded_subnets=self.settings.excluded_subnets,
|
||||
interface_name=port_name) is False:
|
||||
if self.permitted_subnets.permitted(ipv6_address, interface_name=port_name) is False:
|
||||
continue
|
||||
|
||||
nic_ips[port_name].append(ipv6_address)
|
||||
|
||||
25
module/sources/common/conifg.py
Normal file
25
module/sources/common/conifg.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020 - 2022 Ricardo Bartels. All rights reserved.
|
||||
#
|
||||
# netbox-sync.py
|
||||
#
|
||||
# This work is licensed under the terms of the MIT license.
|
||||
# For a copy, see file LICENSE.txt included in this
|
||||
# repository or visit: <https://opensource.org/licenses/MIT>.
|
||||
|
||||
config_option_enabled_definition = {
|
||||
"key": "enabled",
|
||||
"value_type": bool,
|
||||
"description": "Defines if this source is enabled or not",
|
||||
"default_value": True
|
||||
}
|
||||
|
||||
config_option_permitted_subnets_definition = {
|
||||
"key": "permitted_subnets",
|
||||
"value_type": str,
|
||||
"description": """IP networks eligible to be synced to NetBox. If an IP address is not part of
|
||||
this networks then it WON'T be synced to NetBox. To excluded small blocks from bigger IP blocks
|
||||
a leading '!' has to be added
|
||||
""",
|
||||
"config_example": "10.0.0.0/8, !10.23.42.0/24"
|
||||
}
|
||||
113
module/sources/common/permitted_subnets.py
Normal file
113
module/sources/common/permitted_subnets.py
Normal file
@@ -0,0 +1,113 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020 - 2022 Ricardo Bartels. All rights reserved.
|
||||
#
|
||||
# netbox-sync.py
|
||||
#
|
||||
# This work is licensed under the terms of the MIT license.
|
||||
# For a copy, see file LICENSE.txt included in this
|
||||
# repository or visit: <https://opensource.org/licenses/MIT>.
|
||||
|
||||
from ipaddress import ip_address, ip_network, ip_interface
|
||||
|
||||
from module.common.logging import get_logger
|
||||
|
||||
log = get_logger()
|
||||
|
||||
|
||||
class PermittedSubnets:
|
||||
"""
|
||||
initializes and verifies if an IP address is part of an permitted subnet
|
||||
"""
|
||||
|
||||
def __init__(self, config_string: str):
|
||||
|
||||
self._validation_failed = False
|
||||
|
||||
self.included_subnets = list()
|
||||
self.excluded_subnets = list()
|
||||
|
||||
if config_string is None:
|
||||
log.info(f"Config option 'permitted_subnets' is undefined. No IP addresses will be populated to NetBox!")
|
||||
self._validation_failed = True
|
||||
|
||||
if not isinstance(config_string, str):
|
||||
raise ValueError("permitted subnets need to be of type string")
|
||||
|
||||
subnet_list = [x.strip() for x in config_string.split(",") if x.strip() != ""]
|
||||
|
||||
for subnet in subnet_list:
|
||||
excluded = False
|
||||
if subnet[0] == "!":
|
||||
excluded = True
|
||||
subnet = subnet[1:].strip()
|
||||
|
||||
try:
|
||||
if excluded is True:
|
||||
self.excluded_subnets.append(ip_network(subnet))
|
||||
else:
|
||||
self.included_subnets.append(ip_network(subnet))
|
||||
except Exception as e:
|
||||
log.error(f"Problem parsing permitted subnet: {e}")
|
||||
self._validation_failed = True
|
||||
|
||||
@property
|
||||
def validation_failed(self) -> bool:
|
||||
return self._validation_failed
|
||||
|
||||
def permitted(self, ip, interface_name=None) -> bool:
|
||||
"""
|
||||
performs a couple of checks to see if an IP address is valid and allowed
|
||||
to be added to NetBox
|
||||
|
||||
IP address must always be passed as interface notation
|
||||
* 192.168.0.1/24
|
||||
* fd00::0/64
|
||||
* 192.168.23.24/255.255.255.24
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ip: str
|
||||
IP address to validate
|
||||
interface_name: str
|
||||
name of the interface this IP shall be added. Important for meaningful log messages
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool: if IP address is valid
|
||||
"""
|
||||
|
||||
if ip is None:
|
||||
log.warning("No IP address passed to validate if this IP belongs to a permitted subnet")
|
||||
return False
|
||||
|
||||
ip_text = f"'{ip}'"
|
||||
if interface_name is not None:
|
||||
ip_text = f"{ip_text} for {interface_name}"
|
||||
|
||||
try:
|
||||
if "/" in ip:
|
||||
ip_a = ip_interface(ip).ip
|
||||
else:
|
||||
ip_a = ip_address(ip)
|
||||
except ValueError:
|
||||
log.error(f"IP address {ip_text} invalid!")
|
||||
return False
|
||||
|
||||
if ip_a.is_link_local is True:
|
||||
log.debug(f"IP address {ip_text} is a link local address. Skipping.")
|
||||
return False
|
||||
|
||||
if ip_a.is_loopback is True:
|
||||
log.debug(f"IP address {ip_text} is a loopback address. Skipping.")
|
||||
return False
|
||||
|
||||
for excluded_subnet in self.excluded_subnets:
|
||||
if ip_a in excluded_subnet:
|
||||
return False
|
||||
|
||||
for permitted_subnet in self.included_subnets:
|
||||
if ip_a in permitted_subnet:
|
||||
return True
|
||||
|
||||
log.debug(f"IP address {ip_text} not part of any permitted subnet. Skipping.")
|
||||
return False
|
||||
@@ -25,25 +25,9 @@ from module.netbox.inventory import (
|
||||
)
|
||||
from module.common.logging import get_logger
|
||||
from module.common.misc import grab
|
||||
from module.config.option import ConfigOption
|
||||
|
||||
log = get_logger()
|
||||
|
||||
config_option_enabled = \
|
||||
ConfigOption("enabled",
|
||||
bool,
|
||||
description="Defines if this source is enabled or not",
|
||||
default_value=True)
|
||||
|
||||
config_option_permitted_subnets = \
|
||||
ConfigOption("permitted_subnets",
|
||||
str,
|
||||
description="""IP networks eligible to be synced to NetBox. If an IP address is not part of
|
||||
this networks then it WON'T be synced to NetBox. To excluded small blocks from bigger IP blocks
|
||||
a leading '!' has to be added
|
||||
""",
|
||||
config_example="10.0.0.0/8, !10.23.42.0/24")
|
||||
|
||||
|
||||
class SourceBase:
|
||||
"""
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
from module.config import source_config_section_name
|
||||
from module.config.base import ConfigBase
|
||||
from module.config.option import ConfigOption
|
||||
from module.sources.common.source_base import config_option_enabled, config_option_permitted_subnets
|
||||
from module.sources.common.conifg import *
|
||||
|
||||
|
||||
class VMWareConfig(ConfigBase):
|
||||
@@ -18,58 +18,70 @@ class VMWareConfig(ConfigBase):
|
||||
section_name = source_config_section_name
|
||||
source_name = None
|
||||
|
||||
options = [
|
||||
config_option_enabled,
|
||||
def __init__(self):
|
||||
self.options = [
|
||||
ConfigOption(**config_option_enabled_definition),
|
||||
|
||||
ConfigOption("type",
|
||||
str,
|
||||
description="type of source. This defines which source handler to use",
|
||||
config_example="vmware",
|
||||
mandatory=True),
|
||||
ConfigOption("type",
|
||||
str,
|
||||
description="type of source. This defines which source handler to use",
|
||||
config_example="vmware",
|
||||
mandatory=True),
|
||||
|
||||
ConfigOption("host_fqdn",
|
||||
str,
|
||||
description="host name / IP address of the vCenter",
|
||||
config_example="my-netbox.local",
|
||||
mandatory=True),
|
||||
ConfigOption("host_fqdn",
|
||||
str,
|
||||
description="host name / IP address of the vCenter",
|
||||
config_example="my-netbox.local",
|
||||
mandatory=True),
|
||||
|
||||
ConfigOption("port",
|
||||
int,
|
||||
description="TCP port to connect to",
|
||||
default_value=443,
|
||||
mandatory=True),
|
||||
ConfigOption("port",
|
||||
int,
|
||||
description="TCP port to connect to",
|
||||
default_value=443,
|
||||
mandatory=True),
|
||||
|
||||
ConfigOption("username",
|
||||
str,
|
||||
description="username to use to log into vCenter",
|
||||
config_example="vcenter-admin",
|
||||
mandatory=True),
|
||||
ConfigOption("username",
|
||||
str,
|
||||
description="username to use to log into vCenter",
|
||||
config_example="vcenter-admin",
|
||||
mandatory=True),
|
||||
|
||||
ConfigOption("password",
|
||||
str,
|
||||
description="password to use to log into vCenter",
|
||||
config_example="super-secret",
|
||||
sensitive=True,
|
||||
mandatory=True),
|
||||
ConfigOption("password",
|
||||
str,
|
||||
description="password to use to log into vCenter",
|
||||
config_example="super-secret",
|
||||
sensitive=True,
|
||||
mandatory=True),
|
||||
|
||||
ConfigOption("validate_tls_certs",
|
||||
bool,
|
||||
description="""Enforces TLS certificate validation.
|
||||
If vCenter uses a valid TLS certificate then this option should be set
|
||||
to 'true' to ensure a secure connection."""),
|
||||
ConfigOption("validate_tls_certs",
|
||||
bool,
|
||||
description="""Enforces TLS certificate validation.
|
||||
If vCenter uses a valid TLS certificate then this option should be set
|
||||
to 'true' to ensure a secure connection."""),
|
||||
|
||||
ConfigOption("proxy_host",
|
||||
str,
|
||||
description="""EXPERIMENTAL: Connect to a vCenter using a proxy server
|
||||
(socks proxies are not supported). define a host name or an IP address""",
|
||||
config_example="10.10.1.10"),
|
||||
ConfigOption("proxy_host",
|
||||
str,
|
||||
description="""EXPERIMENTAL: Connect to a vCenter using a proxy server
|
||||
(socks proxies are not supported). define a host name or an IP address""",
|
||||
config_example="10.10.1.10"),
|
||||
|
||||
ConfigOption("proxy_port",
|
||||
int,
|
||||
description="""EXPERIMENTAL: Connect to a vCenter using a proxy server
|
||||
(socks proxies are not supported).
|
||||
define proxy server port number""",
|
||||
config_example=3128),
|
||||
ConfigOption("proxy_port",
|
||||
int,
|
||||
description="""EXPERIMENTAL: Connect to a vCenter using a proxy server
|
||||
(socks proxies are not supported).
|
||||
define proxy server port number""",
|
||||
config_example=3128),
|
||||
|
||||
config_option_permitted_subnets
|
||||
]
|
||||
ConfigOption(**config_option_permitted_subnets_definition),
|
||||
|
||||
ConfigOption("cluster_exclude_filter",
|
||||
str),
|
||||
]
|
||||
|
||||
super().__init__()
|
||||
|
||||
def validate_options(self):
|
||||
|
||||
for option in self.options:
|
||||
|
||||
pass
|
||||
|
||||
@@ -31,6 +31,7 @@ from module.common.logging import get_logger, DEBUG3
|
||||
from module.common.misc import grab, dump, get_string_or_none, plural, quoted_split
|
||||
from module.common.support import normalize_mac_address, ip_valid_to_add_to_netbox
|
||||
from module.netbox.inventory import NetBoxInventory
|
||||
from module.sources.common.permitted_subnets import PermittedSubnets
|
||||
from module.netbox.object_classes import (
|
||||
NetBoxInterfaceType,
|
||||
NBTag,
|
||||
@@ -95,14 +96,6 @@ class VMWareHandler(SourceBase):
|
||||
]
|
||||
|
||||
settings = {
|
||||
"enabled": True,
|
||||
"host_fqdn": None,
|
||||
"port": 443,
|
||||
"username": None,
|
||||
"password": None,
|
||||
"validate_tls_certs": False,
|
||||
"proxy_host": None,
|
||||
"proxy_port": None,
|
||||
"cluster_exclude_filter": None,
|
||||
"cluster_include_filter": None,
|
||||
"host_exclude_filter": None,
|
||||
|
||||
@@ -78,7 +78,7 @@ def main():
|
||||
inventory = NetBoxInventory()
|
||||
|
||||
# establish NetBox connection
|
||||
#nb_handler = NetBoxHandler(nb_sync_version=__version__)
|
||||
nb_handler = NetBoxHandler(nb_sync_version=__version__)
|
||||
|
||||
# if purge was selected we go ahead and remove all items which were managed by this tools
|
||||
if args.purge is True:
|
||||
@@ -94,7 +94,6 @@ def main():
|
||||
# instantiate source handlers and get attributes
|
||||
log.info("Initializing sources")
|
||||
sources = instantiate_sources()
|
||||
exit(0)
|
||||
|
||||
# all sources are unavailable
|
||||
if len(sources) == 0:
|
||||
|
||||
Reference in New Issue
Block a user