diff --git a/configs/junos-template.conf b/configs/junos-template.conf new file mode 100644 index 0000000..0f4012e --- /dev/null +++ b/configs/junos-template.conf @@ -0,0 +1,141 @@ +version ${'.'.join(node.metadata.get('junos/version'))}; +system { + host-name ${node.name.split('.')[-1]}; + time-zone GMT; + root-authentication { + encrypted-password "$5$1hGrR8Kk$lx3CIdxqvesBrZUtDftROEoyXQuMENEu62JVtHw6WGD"; ## SECRET-DATA + } + name-server { +% for srv in repo.libs.defaults.nameservers_ipv4: + ${srv}; +% endfor + } + login { +% for uid, (uname, uconfig) in enumerate(sorted(users.items())): + user ${uname} { + full-name ${uname}; + uid ${1000+uid}; + class super-user; + authentication { +% for pubkey in sorted(uconfig['ssh_pubkey']): + ${pubkey.split(' ', 1)[0]} "${pubkey}"; +% endfor + } + } +% endfor + } + services { + ssh { + protocol-version v2; + } + netconf { + ssh; + } +# web-management { +# http; +# } + } + syslog { + user * { + any emergency; + } + file messages { + any notice; + authorization info; + } + file interactive-commands { + interactive-commands any; + } + } + ntp { +% for srv in sorted(ntp_servers): + server ${srv}; +% endfor; + } +} +interfaces { +% for iface, config in sorted(interfaces.items()): + ${iface} { + unit 0 { +% if not config['enabled']: + disable; +% endif +% if config['mode'] == 'trunk': + family ethernet-switching { + port-mode trunk; + vlan { + members [ ${' '.join(sorted(config['tagged_vlans']))} ]; + } +% if config['untagged_vlan']: + native-vlan-id ${config['untagged_vlan']}; +% endif + } +% else: + family ethernet-switching; +% endif + } + } +% endfor + vlan { +% for idx, (vlan, vconfig) in enumerate(sorted(vlans.items())): +% if vconfig['ip_address']: + unit ${idx} { + family inet { + address ${vconfig['ip_address']}; + } + } +% endif +% endfor + } +} +snmp { + contact "${repo.libs.defaults.hostmaster_email}"; + community public { + authorization read-only; + } +} +routing-options { + static { + route 0.0.0.0/0 next-hop ${gateway}; + } +} +protocols { + igmp-snooping { + vlan all; + } + rstp; + lldp { + interface all; + } + lldp-med { + interface all; + } +} +ethernet-switching-options { + voip; + storm-control { + interface all; + } +} +vlans { +% for idx, (vlan, vconfig) in enumerate(sorted(vlans.items())): + ${vlan} { +% if vconfig['id']: + vlan-id ${vconfig['id']}; +% endif + interface { +% for iface, iconfig in sorted(interfaces.items()): +% if iconfig['untagged_vlan'] == vlan: + ${iface}.0; +% endif +% endfor + } +% if vconfig['ip_address']: + l3-interface vlan.${idx}; +% endif + } +% endfor +} +poe { + interface all; +} diff --git a/groups/os.py b/groups/os.py index 4fa97f7..21d4a60 100644 --- a/groups/os.py +++ b/groups/os.py @@ -88,3 +88,10 @@ groups['debian-bullseye'] = { groups['debian-sid'] = { 'os_version': (99,) } + +groups['junos'] = { + 'dummy': True, + 'cmd_wrapper_outer': '{}', + 'cmd_wrapper_inner': '{}', + 'os': 'freebsd', +} diff --git a/netbox_dump.json b/netbox_dump.json index 30eb1e7..c013f00 100644 --- a/netbox_dump.json +++ b/netbox_dump.json @@ -1190,6 +1190,20 @@ "untagged": null } }, + "home.clients": { + "description": "", + "enabled": true, + "ip_addresses": [ + "172.19.138.4/24" + ], + "lag": null, + "mode": null, + "type": "virtual", + "vlans": { + "tagged": [], + "untagged": null + } + }, "xe-0/1/0": { "description": "nas", "enabled": true, diff --git a/nodes/home/home.sw02.toml b/nodes/home/home.sw02.toml new file mode 100644 index 0000000..8e4520a --- /dev/null +++ b/nodes/home/home.sw02.toml @@ -0,0 +1,5 @@ +hostname = "172.19.138.4" +groups = ["junos"] + +[metadata.junos] +version = ["15", "1R5", "5"] diff --git a/scripts/junos-update-config b/scripts/junos-update-config new file mode 100755 index 0000000..74191cf --- /dev/null +++ b/scripts/junos-update-config @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 + +from json import load +from os import environ +from os.path import join +from sys import argv, exit +from tempfile import gettempdir + +from mako.template import Template + +from bundlewrap.repo import Repository +from bundlewrap.utils.text import bold +from bundlewrap.utils.ui import io + +NTP_SERVERS = { +# pool.ntp.org + '148.251.54.81', + '162.159.200.123', + '213.209.109.44', + '54.36.110.36', +} + +try: + node_name = argv[1] +except Exception: + print(f'Usage: {argv[0]} ') + exit(1) + +path = environ.get('BW_REPO_PATH', '.') +repo = Repository(path) +node = repo.get_node(node_name) + +try: + io.activate() + + interfaces = {} + users = {} + vlans = { + 'default': { + 'id': None, + 'ip_address': '169.254.254.254/24', + }, + } + + tmpfile = join(gettempdir(), f'{node.name}.conf') + + gw_split = node.hostname.split('.') + gw_split[3] = '1' + gateway = '.'.join(gw_split) + + with io.job('reading netbox_dump.json'): + with open(join(repo.path, 'netbox_dump.json'), 'r') as f: + json = load(f)[node.metadata.get('location')] + + for vlan, vid in json['vlans'].items(): + vlans[vlan] = { + 'id': vid, + 'ip_address': None, + } + + for iface, iconfig in json['devices'][node.name].items(): + if iface in vlans: + # If the interface name is the same as a vlan name, this + # means the ip assigned to this interface should get + # assigned to that vlan. + vlans[iface]['ip_address'] = iconfig['ip_addresses'][0] + else: + interfaces[iface] = { + 'enabled': bool( + iconfig['enabled'] + and iconfig['mode'] + and ( + iconfig['vlans']['tagged'] + or iconfig['vlans']['untagged'] + ) + ), + 'description': iconfig['description'], + 'untagged_vlan': iconfig['vlans']['untagged'], + } + + if iconfig['mode'] and iconfig['mode'].startswith('tagged'): + interfaces[iface]['mode'] = 'trunk' + else: + interfaces[iface]['mode'] = 'access' + + tagged_vlans = set() + for vlan in iconfig['vlans']['tagged']: + tagged_vlans.add(str(vlans[vlan]['id'])) + interfaces[iface]['tagged_vlans'] = tagged_vlans + + with io.job('reading users.json'): + with open(join(repo.path, 'users.json'), 'r') as f: + json = load(f) + + users = {} + for uname, config in json.items(): + if config.get('is_admin', False): + users[uname] = { + 'password': repo.vault.password_for(f'{node.name} {uname} login'), + 'ssh_pubkey': set(config['ssh_pubkey']), + } + + + with io.job(f'{bold(node.name)} rendering config template to {tmpfile}'): + with open(join(repo.path, 'configs', 'junos-template.conf')) as f: + template = Template( + f.read().encode('utf-8'), + input_encoding='utf-8', + output_encoding='utf-8', + ) + content = template.render( + gateway=gateway, + interfaces=interfaces, + node=node, + ntp_servers=NTP_SERVERS, + repo=repo, + users=users, + vlans=vlans, + ) + with open(tmpfile, 'w+') as f: + f.write(content.decode('utf-8')) + + with io.job(f'{bold(node.name)} updating configuration on device'): + node.upload(tmpfile, '/tmp/bundlewrap.conf') + + result = node.run( + 'configure exclusive ; load override /tmp/bundlewrap.conf ; commit', + log_output=True, + ) + + if 'commit complete' in result.stdout.decode(): + node.run( + 'request system configuration rescue save', + log_output=True, + ) +finally: + io.deactivate()