diff --git a/.editorconfig b/.editorconfig index e09c9dd..b632cc1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -22,3 +22,6 @@ indent_size = unset [*.vault] end_of_line = unset insert_final_newline = unset + +[*.json] +insert_final_newline = unset diff --git a/bundles/routeros/README.md b/bundles/routeros/README.md new file mode 100644 index 0000000..3b4ccf4 --- /dev/null +++ b/bundles/routeros/README.md @@ -0,0 +1,9 @@ +RouterOS +======== + +Pulls device configuration from netbox_dump.json and creates items accordingly. + +Notes +----- + +To add management IPs to a VLAN, you need to create a virtual interface in Netbox whose name matches the name of a VLAN. Then add the IP to that virtual interface. diff --git a/bundles/routeros/items.py b/bundles/routeros/items.py new file mode 100644 index 0000000..cd1ec29 --- /dev/null +++ b/bundles/routeros/items.py @@ -0,0 +1,172 @@ +routeros['/ip/dns'] = { + 'servers': '8.8.8.8', +} + +for service in ( + 'api-ssl', # slow :( + 'ftp', # we can download files via HTTP + 'telnet', + 'www-ssl', # slow :( + 'winbox', +): + routeros[f'/ip/service?name={service}'] = { + 'disabled': True, + } + +for service in ( + 'api', + 'ssh', + 'www', +): + routeros[f'/ip/service?name={service}'] = { + 'disabled': False, + } + +LOGGING_TOPICS = ( + 'critical', + 'error', + 'info', + 'stp', + 'warning', +) + +for topic in LOGGING_TOPICS: + routeros[f'/system/logging?action=memory&topics={topic}'] = {} + +if node.metadata.get('routeros/syslog-server', None): + routeros['/system/logging/action?name=remote'] = { + 'target': 'remote', + 'remote': node.metadata.get('routeros/syslog-server'), + 'remote-port': 514, + } + for topic in LOGGING_TOPICS: + routeros[f'/system/logging?action=remote&topics={topic}'] = {} + +routeros['/snmp'] = { + 'enabled': True, +} +routeros['/snmp/community?name=public'] = { + 'addresses': '::/0', + 'disabled': False, + 'read-access': True, + 'write-access': False, +} + +routeros['/system/clock'] = { + 'time-zone-autodetect': False, + 'time-zone-name': 'UTC', +} + +routeros['/system/identity'] = { + 'name': node.name, + # doing this first gives us some chance to notice an IP mixup + 'before': {'routeros:'}, +} + +routeros['/system/ntp/client'] = { + 'enabled': True, + 'server-dns-names': 'de.pool.ntp.org', +} + +if node.metadata.get('routeros/gateway'): + routeros['/ip/route?dst-address=0.0.0.0/0'] = { + 'gateway': node.metadata.get('routeros/gateway'), + } + +routeros['/interface/bridge?name=bridge'] = { + 'priority': node.metadata.get('routeros/bridge_priority', '0x8000'), + 'protocol-mode': 'rstp', + 'vlan-filtering': True, +} + +# assign bridge ports +for port_name, port_conf in node.metadata.get('routeros/ports').items(): + if port_conf.get('delete'): + routeros[f'/interface/bridge/port?interface={port_name}'] = { + 'delete': True, + 'tags': {'routeros-port'}, + 'needs': {f'routeros:/interface?name={port_name}'}, + } + else: + pvid = port_conf.get('pvid') + if not pvid: + for vlan_name, vlan_conf in node.metadata.get('routeros/vlans').items(): + if port_name in vlan_conf.get('untagged', []): + if pvid: + raise ValueError( + f"{node.name}: port {port_name} untagged " + f"in VLANs {pvid} and {vlan_conf['id']}" + ) + else: + pvid = vlan_conf['id'] + + # Field must not be present of some port types. + if port_conf.get('hw'): + hw = {'hw': port_conf['hw']} + else: + hw = {} + + routeros[f'/interface/bridge/port?interface={port_name}'] = { + 'bridge': 'bridge', + '_comment': port_conf.get('description', ''), + 'disabled': False, + **hw, + 'pvid': pvid or '1', + 'tags': {'routeros-port'}, + 'needs': { + f'routeros:/interface?name={port_name}', + 'routeros:/interface/bridge?name=bridge', + 'tag:routeros-bridge-vlan', # or we end up with dynamic VLANs after setting pvid to an unknown VLAN + }, + } + + routeros[f'/interface?name={port_name}'] = { + '_comment': port_conf.get('description', ''), + 'disabled': port_conf.get('disabled', False) + and not port_conf.get('delete', False), + } + + +# create IPs +for ip, ip_conf in node.metadata.get('routeros/ips').items(): + routeros[f'/ip/address?address={ip}'] = { + 'interface': ip_conf['interface'], + 'tags': {'routeros-ip'}, + 'needs': { + 'tag:routeros-vlan', + }, + } + +for vlan, conf in node.metadata.get('routeros/vlans').items(): + if conf['delete']: + # delete old VLANs + routeros[f'/interface/vlan?name={vlan}'] = { + 'delete': True, + } + + routeros[f"/interface/bridge/vlan?vlan-ids={conf['id']}"] = { + 'delete': True, + } + else: + # create vlans + routeros[f'/interface/vlan?name={vlan}'] = { + 'vlan-id': conf['id'], + 'interface': 'bridge', + 'tags': {'routeros-vlan'}, + 'needs': { + 'routeros:/interface/bridge?name=bridge', + }, + } + + # assign ports to vlans + routeros[f"/interface/bridge/vlan?vlan-ids={conf['id']}"] = { + 'bridge': 'bridge', + 'untagged': sorted(conf['untagged']), + 'tagged': sorted(conf['tagged']), + '_comment': vlan, + 'tags': {'routeros-bridge-vlan'}, + 'needs': { + 'routeros:/interface/bridge?name=bridge', + 'tag:routeros-vlan', + }, + } diff --git a/bundles/routeros/metadata.py b/bundles/routeros/metadata.py new file mode 100644 index 0000000..72bc063 --- /dev/null +++ b/bundles/routeros/metadata.py @@ -0,0 +1,123 @@ +import re +from json import load +from os.path import join + +defaults = { + 'icinga2_api': { + 'routeros': { + 'services': { + 'TEMPERATURE': { + 'check_command': 'snmp', + 'vars.snmp_oid': '1.3.6.1.4.1.14988.1.1.3.11.0', + 'vars.snmp_version': '2c', + 'vars.snmp_community': 'public', + 'vars.warn': '@750:799', # 1/10 °C + 'vars.crit': '@800:9999', + }, + }, + }, + }, +} + + +@metadata_reactor.provides( + 'routeros/ips', + 'routeros/ports', + 'routeros/vlans', +) +def get_ports_from_netbox_dump(metadata): + with open(join(repo.path, 'configs', f'netbox_device_{node.name}.json')) as f: + netbox = load(f) + + ips = {} + ports = {} + vlans = { + v['name']: { + 'id': v['vid'], + 'delete': False, + 'tagged': set(), + 'untagged': set(), + } + for v in netbox['vlans'] + } + + for port, conf in netbox['interfaces'].items(): + for ip in conf['ips']: + ips[ip] = {'interface': port} + + if conf['type'] == 'VIRTUAL': + # these are VLAN interfaces (for management IPs) + if conf['ips']: + # this makes management services available in the VLAN + try: + vlans[port]['tagged'].add('bridge') + except KeyError: + raise ValueError( + f'name of virtual interface "{port}" on {node.name} ' + f'matches none of the known VLANs: {list(vlans.keys())} ' + '(you probably need to rename the interface in Netbox ' + 'and/or run netbox-dump)' + ) + # We do not create the actual VLAN interface here, that + # happens automatically in items.py. + continue + elif not conf['enabled'] or not conf['mode']: + # disable unconfigured ports + ports[port] = { + 'disabled': True, + 'description': conf.get('description', ''), + } + # dont add vlans for this port + continue + else: + ports[port] = { + 'disabled': False, + 'description': conf.get('description', ''), + } + if conf.get('ips', []): + ports[port]['ips'] = set(conf['ips']) + if conf['type'] in ( + 'A_1000BASE_T', + 'A_10GBASE_X_SFPP', + ): + ports[port]['hw'] = True + + if conf['untagged_vlan']: + vlans[conf['untagged_vlan']]['untagged'].add(port) + if conf['ips']: + # this makes management services available in the VLAN + vlans[conf['untagged_vlan']]['tagged'].add('bridge') + + # tagged + + if conf['mode'] == 'TAGGED_ALL': + tagged = set(vlans.keys()) - {conf['untagged_vlan']} + else: + tagged = conf['tagged_vlans'] + + for vlan in tagged: + vlans[vlan]['tagged'].add(port) + + # this makes management services available in the VLAN + if conf['ips']: + vlans[vlan]['tagged'].add('bridge') + + return { + 'routeros': { + 'ips': ips, + 'ports': ports, + 'vlans': vlans, + } + } + + +@metadata_reactor.provides('routeros/gateway') +def gateway(metadata): + ip_pattern = re.compile(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.)\d{1,3}') + gateway = ip_pattern.match(node.hostname).group(1) + '1' + + return { + 'routeros': { + 'gateway': gateway, + }, + } diff --git a/configs/netbox_device_home.switch-rack.json b/configs/netbox_device_home.switch-rack.json new file mode 100644 index 0000000..1e84e4b --- /dev/null +++ b/configs/netbox_device_home.switch-rack.json @@ -0,0 +1,276 @@ +{ + "interfaces": { + "ether1": { + "description": "home.router (enp1s0)", + "enabled": true, + "ips": [], + "mode": "TAGGED_ALL", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": null + }, + "ether10": { + "description": "", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "ether11": { + "description": "", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "ether12": { + "description": "", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "ether13": { + "description": "", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "ether14": { + "description": "", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "ether15": { + "description": "", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "ether16": { + "description": "", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "ether17": { + "description": "", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "ether18": { + "description": "", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "ether19": { + "description": "", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "ether2": { + "description": "", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "ether20": { + "description": "", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "ether21": { + "description": "Patchpanel oben (4)", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "ether22": { + "description": "home.nas (eno1)", + "enabled": true, + "ips": [], + "mode": "TAGGED", + "tagged_vlans": [ + "ffwi.client", + "ffwi.mesh", + "home.clients", + "home.dmz" + ], + "type": "A_1000BASE_T", + "untagged_vlan": null + }, + "ether23": { + "description": "uplink", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.wan" + }, + "ether24": { + "description": "", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "ether3": { + "description": "", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "ether4": { + "description": "", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "ether5": { + "description": "", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "ether6": { + "description": "", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "ether7": { + "description": "", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "ether8": { + "description": "", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "ether9": { + "description": "", + "enabled": true, + "ips": [], + "mode": "ACCESS", + "tagged_vlans": [], + "type": "A_1000BASE_T", + "untagged_vlan": "home.clients" + }, + "home.clients": { + "description": "", + "enabled": true, + "ips": [ + "172.19.138.4/24" + ], + "mode": null, + "tagged_vlans": [], + "type": "VIRTUAL", + "untagged_vlan": null + }, + "sfp-sfpplus1": { + "description": "", + "enabled": true, + "ips": [], + "mode": null, + "tagged_vlans": [], + "type": "A_10GBASE_X_SFPP", + "untagged_vlan": null + }, + "sfp-sfpplus2": { + "description": "", + "enabled": true, + "ips": [], + "mode": null, + "tagged_vlans": [], + "type": "A_10GBASE_X_SFPP", + "untagged_vlan": null + } + }, + "vlans": [ + { + "name": "home.wan", + "vid": 7 + }, + { + "name": "home.clients", + "vid": 1138 + }, + { + "name": "home.dmz", + "vid": 1139 + }, + { + "name": "ffwi.mesh", + "vid": 3000 + }, + { + "name": "ffwi.client", + "vid": 3001 + } + ] +} \ No newline at end of file diff --git a/nodes/home.switch-rack.toml b/nodes/home.switch-rack.toml new file mode 100644 index 0000000..2f5dbda --- /dev/null +++ b/nodes/home.switch-rack.toml @@ -0,0 +1,5 @@ +bundles = ["routeros"] +hostname = "172.19.138.4" +os = "routeros" +username = "admin" +# TODO password