From 9894c0f2402dc2564a46731e8cf64d09f8fd5fba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Paulon?= Date: Fri, 14 Aug 2020 13:34:51 +0200 Subject: [PATCH] vacances --- backup-all.py | 80 ++++++++++++ configs/config.j2 | 130 ++++++++++++++++++++ configs/master.yml | 22 ++++ configs/profiles/backup.yml | 5 + configs/profiles/chambre.yml | 6 + configs/profiles/defaults.yml | 9 ++ configs/profiles/downlink.yml | 2 + configs/profiles/open.yml | 8 ++ configs/profiles/unknown.yml | 3 + configs/profiles/uplink.yml | 6 + configs/switches/bata-0.yml | 3 + configs/switches/bata-1.yml | 4 +- configs/switches/bata-2.yml | 6 +- configs/switches/batm-0.yml | 7 +- configs/switches/batz-0.yml | 10 +- configs/switches/manoir-0.yml | 8 ++ configs/switches/script | 12 ++ configs/vlans/1.yml | 7 ++ configs/vlans/10.yml | 7 ++ configs/vlans/11.yml | 7 ++ configs/vlans/12.yml | 1 + configs/vlans/13.yml | 1 + configs/vlans/14.yml | 1 + configs/vlans/15.yml | 7 ++ configs/vlans/2.yml | 7 ++ configs/vlans/254.yml | 1 + configs/vlans/26.yml | 7 ++ configs/vlans/3.yml | 7 ++ configs/vlans/4.yml | 7 ++ core.py | 90 ++++++++++++++ generate-conf.py | 224 ++++++++++++++++++++++++++++++++++ 31 files changed, 685 insertions(+), 10 deletions(-) create mode 100644 backup-all.py create mode 100644 configs/config.j2 create mode 100644 configs/master.yml create mode 100644 configs/profiles/backup.yml create mode 100644 configs/profiles/chambre.yml create mode 100644 configs/profiles/defaults.yml create mode 100644 configs/profiles/downlink.yml create mode 100644 configs/profiles/open.yml create mode 100644 configs/profiles/unknown.yml create mode 100644 configs/profiles/uplink.yml create mode 100644 configs/switches/manoir-0.yml create mode 100755 configs/switches/script create mode 100644 configs/vlans/1.yml create mode 100644 configs/vlans/10.yml create mode 100644 configs/vlans/11.yml create mode 100644 configs/vlans/12.yml create mode 100644 configs/vlans/13.yml create mode 100644 configs/vlans/14.yml create mode 100644 configs/vlans/15.yml create mode 100644 configs/vlans/2.yml create mode 100644 configs/vlans/254.yml create mode 100644 configs/vlans/26.yml create mode 100644 configs/vlans/3.yml create mode 100644 configs/vlans/4.yml create mode 100644 core.py create mode 100644 generate-conf.py diff --git a/backup-all.py b/backup-all.py new file mode 100644 index 0000000..1f857ba --- /dev/null +++ b/backup-all.py @@ -0,0 +1,80 @@ +from pathlib import Path + +from core import connect_to_switch, sftp_read_file + +list_switches = [ + "bata-0", + "bata-1", + "bata-2", + "bata-3", + "bata-4", + + "backbone", + "batb-0", + "batb-1", + "batb-2", + "batb-3", + "batb-4", + "batb-5", + # "batb-6", ça doit être le switch de la baie + # "batb-7", c'était le switch de la med + + "batc-0", + "batc-1", + "batc-2", + "batc-3", + "batc-4", + + "batg-0", + "batg-1", + "batg-2", + "batg-3", + "batg-4", + "batg-5", + "batg-6", + # "batg-7", # il existe pas + "batg-8", + "batg-9", + + "bath-0", + "bath-1", + "bath-2", + "bath-3", + + "bati-0", + "bati-1", + # "bati-2", # il existe pas + "bati-3", + + "batj-0", + "batj-1", + "batj-2", + "batj-3", + "batj-4", + + # "batk-0", RIP la Kfet + + "batm-0", + "batm-1", + "batm-2", + "batm-3", + "batm-4", + "batm-5", + "batm-6", + "batm-7", + # "batm-8", il a disparu + + "manoir-0", + +] + + +if __name__ == "__main__": + for switch in list_switches: + print("Backing up {}".format(switch)) + session = connect_to_switch("{}.switches.crans.org".format(switch), key="/root/.ssh/id_rsa") + config = sftp_read_file(session, "cfg/running-config") + Path("configs-backup").mkdir(exist_ok=True) + with open("configs-backup/{}.bak".format(switch), "wb") as config_bak: + config_bak.write(config) + diff --git a/configs/config.j2 b/configs/config.j2 new file mode 100644 index 0000000..ea645be --- /dev/null +++ b/configs/config.j2 @@ -0,0 +1,130 @@ +{{ header }} +hostname "{{ hostname }}" +console idle-timeout 1800 +console idle-timeout serial-usb 1800 +no cdp run +{%- if dhcp_snooping_vlans %} +dhcp-snooping +{%- for s in dhcp_servers %} +dhcp-snooping authorized-server {{ s }} +{%- endfor %} +dhcp-snooping vlan {{ dhcp_snooping_vlans }} +{%- endif %} +{%- if dhcpv6_snooping_vlans %} +dhcpv6-snooping +dhcpv6-snooping vlan {{ dhcpv6_snooping_vlans }} +{%- endif %} +{%- for m in multicast_filter %} +filter multicast {{ m.mac_addr }} drop {{ m.ports }} +{%- endfor %} +{%- for l in logging %} +logging {{ l }} +{%- endfor %} +{%- if radius_servers %} +{%- for r in radius_servers %} +radius-server host {{ r.ip }} dyn-authorization +radius-server host {{ r.ip }} key {{ r.secret }} +{%- endfor %} +radius-server dead-time 2 +{%- endif %} +timesync sntp +sntp unicast +{%- for s in sntp %} +sntp server priority {{ loop.index }} {{ s }} +{%- endfor %} +no telnet-server +time daylight-time-rule western-europe +time timezone 60 +{%- for i4 in ipv4_managers.values() %} +ip authorized-managers {{ i4.ip }} {{ i4.subnet }} access manager +{%- endfor %} +{%- for d in dns %} +ip dns server-address priority {{ loop.index }} {{ d }} +{%- endfor %} +ip ssh filetransfer +{%- for i6 in ipv6_managers.values() %} +ipv6 authorized-managers {{ i6.ip }} {{ i6.subnet }} access manager +{%- endfor %} +{%- if ra_guard_ports %} +ipv6 ra-guard ports {{ ra_guard_ports }} +{%- endif %} +{%- for iface in interfaces %} +interface {{ iface.number }} + name "{{ iface.name }}" +{%- if iface.dhcp_trust %} + dhcp-snooping trust +{%- endif %} +{%- if iface.dhcpv6_trust %} + dhcpv6-snooping trust +{%- endif %} +{%- if iface.flowcontrol %} + flow-control +{% endif %} +{%- if iface.arp_trust %} + arp-protect trust +{%- endif %} + exit +{%- endfor %} +snmp-server community "public" operator +snmp-server location "{{ location }}" +snmpv3 enable +snmpv3 restricted-access +snmpv3 group managerpriv user "{{ snmp_user }}" sec-model ver3 +snmpv3 user "{{ snmp_user }}" +aaa accounting update periodic 240 +aaa accounting network start-stop radius +aaa authentication ssh login public-key +aaa authentication ssh enable public-key +{%- if mac_based_ports %} +aaa port-access mac-based {{ mac_based_ports }} +{%- for iface in interfaces %} +{%- if iface.mac_based %} +aaa port-access mac-based {{ iface.number }} addr-limit {{ iface.addr_limit }} +aaa port-access mac-based {{ iface.number }} logoff-period {{ iface.logoff }} +{%- endif %} +{%- endfor %} +aaa port-access mac-based addr-format multi-colon +aaa port-access mac-based unauth-redirect "{{ unauth_redirect }}" +{%- endif %} +{%- for number, vlan in vlans.items() %} +vlan {{ number }} + name "{{ vlan.name }}" + {%- if vlan.untagged %} + untagged {{ vlan.untagged }} + {%- endif %} + {%- if vlan.tagged %} + tagged {{ vlan.tagged }} + {%- endif %} + {%- if vlan.ip %} + ip address {{ vlan.ip.addr }} {{ vlan.ip.subnet }} + {%- if vlan.ip.addr6 %} + ipv6 address {{ vlan.ip.addr6 }}/{{ vlan.ip.subnet6 }} + {%- endif %} + {%- else %} + no ip address + {%- endif %} + {%- if vlan.igmp %} + ip igmp + {%- endif %} + {%- if vlan.ipv6_mld %} + ipv6 mld enable + {%- endif %} + exit +{%- endfor %} +allow-unsupported-transceiver +{%- if loop_protect %} +loop-protect {{ loop_protect.ports }} +loop-protect transmit-interval 3 disable-timer 30 +{%- endif %} +{%- if arp_protect %} +arp-protect +arp-protect validate src-mac dest-mac +arp-protect vlan {{ arp_protect.vlans }} +{%- endif %} +device-profile name "default-ap-profile" + cos 0 + exit +activate software-update disable +activate provision disable +password manager +password operator diff --git a/configs/master.yml b/configs/master.yml new file mode 100644 index 0000000..6a1f0d7 --- /dev/null +++ b/configs/master.yml @@ -0,0 +1,22 @@ +ssh_private_key: "/root/.ssh/id_rsa" +dhcp_servers: + - 100.64.0.99 + - 172.16.14.99 + - 185.230.78.99 + # TODO vlans open et federez +# multicast_macs: +logging_servers: + - 172.16.32.31 +sntp_servers: + - 172.16.32.30 +managers_v4: + infra: + ip: 172.16.32.0 + subnet: 255.255.252.0 +dns: + - 172.16.32.99 +radius_servers: + - ip: 172.16.32.99 + secret: ploptotoswitch1 +snmp_user: re2o +unauth_redirect: http://intranet.crans.org/users/initial_register diff --git a/configs/profiles/backup.yml b/configs/profiles/backup.yml new file mode 100644 index 0000000..2b3d42f --- /dev/null +++ b/configs/profiles/backup.yml @@ -0,0 +1,5 @@ +arp-trust: true +dhcp-trust: true +dhcpv6-trust: true +vlans: + tagged: 10 diff --git a/configs/profiles/chambre.yml b/configs/profiles/chambre.yml new file mode 100644 index 0000000..a765b1c --- /dev/null +++ b/configs/profiles/chambre.yml @@ -0,0 +1,6 @@ +vlans: + untagged: 1 +generic_name: chambre_re2o +mac_based: true +addr_limit: 5 +logoff: 3600 diff --git a/configs/profiles/defaults.yml b/configs/profiles/defaults.yml new file mode 100644 index 0000000..43a5d46 --- /dev/null +++ b/configs/profiles/defaults.yml @@ -0,0 +1,9 @@ +radius: false +ra-guard: true +dhcp-trust: false +dhcpv6-trust: false +flowcontrol: false +arp-trust: false +mac-based: false +vlans: + untagged: 1 diff --git a/configs/profiles/downlink.yml b/configs/profiles/downlink.yml new file mode 100644 index 0000000..224770c --- /dev/null +++ b/configs/profiles/downlink.yml @@ -0,0 +1,2 @@ +vlans: + tagged: 2-4,10-15,26,254 diff --git a/configs/profiles/open.yml b/configs/profiles/open.yml new file mode 100644 index 0000000..ce20078 --- /dev/null +++ b/configs/profiles/open.yml @@ -0,0 +1,8 @@ +ra-guard: false +dhcp-trust: true +dhcpv6-trust: true +arp-trust: true +vlans: + untagged: 1 + tagged: 10 +generic_name: default_name diff --git a/configs/profiles/unknown.yml b/configs/profiles/unknown.yml new file mode 100644 index 0000000..f6ac9e7 --- /dev/null +++ b/configs/profiles/unknown.yml @@ -0,0 +1,3 @@ +vlans: + untagged: 1 +ignore_port: true diff --git a/configs/profiles/uplink.yml b/configs/profiles/uplink.yml new file mode 100644 index 0000000..290502f --- /dev/null +++ b/configs/profiles/uplink.yml @@ -0,0 +1,6 @@ +ra-guard: false +dhcp-trust: true +dhcpv6-trust: true +arp-trust: true +vlans: + tagged: 2-4,10-15,26,254 diff --git a/configs/switches/bata-0.yml b/configs/switches/bata-0.yml index 811c40c..c2d14b0 100644 --- a/configs/switches/bata-0.yml +++ b/configs/switches/bata-0.yml @@ -1,5 +1,8 @@ hostname: "bata-0" nb_ports: 52 +ipv4-addr: 10.231.100.45 +ipv4-subnet: 255.255.255.0 +location: 0A interfaces: 1-48: profile: chambre diff --git a/configs/switches/bata-1.yml b/configs/switches/bata-1.yml index 3c27a45..ad05fbd 100644 --- a/configs/switches/bata-1.yml +++ b/configs/switches/bata-1.yml @@ -2,10 +2,12 @@ hostname: "bata-1" dhcp-snooping: True dhcpv6-snooping: True nb_ports: 52 +location: 0A +ipv4-addr: 10.231.100.46 +ipv4-subnet: 255.255.255.0 interfaces: 1-48: profile: chambre - generic_name: True 49: profile: uplink name: 'Uplink bata-2' diff --git a/configs/switches/bata-2.yml b/configs/switches/bata-2.yml index fd1f4f2..d401294 100644 --- a/configs/switches/bata-2.yml +++ b/configs/switches/bata-2.yml @@ -6,13 +6,13 @@ interfaces: 1: profile: downlink name: 'Downlink: bata-0' - 2: + 2: profile: downlink name: 'Downlink: bata-1' - 3: + 3: profile: downlink name: 'Downlink: bata-4' - 4: + 4: profile: downlink name: 'Downlink: bata-3' 11-14,19-20: diff --git a/configs/switches/batm-0.yml b/configs/switches/batm-0.yml index 1d7776c..0b0c0ea 100644 --- a/configs/switches/batm-0.yml +++ b/configs/switches/batm-0.yml @@ -6,5 +6,10 @@ interfaces: 50: profile: uplink name: 'Uplink: batm-7' - 51-52: + 49,51-52: profile: unknown +ipv6-addr: fd01:240:fe3d:c804:96f1:28ff:fe61:81c0 +ipv6-subnet: 64 +ipv4-addr: 10.231.100.51 +ipv4-subnet: 255.255.255.0 +location: 0M diff --git a/configs/switches/batz-0.yml b/configs/switches/batz-0.yml index 1494b9f..1e437bd 100644 --- a/configs/switches/batz-0.yml +++ b/configs/switches/batz-0.yml @@ -3,13 +3,13 @@ dhcp-snooping: False dhcpv6-snooping: False nb_ports: 52 interfaces: - 1-51: + 1-48: + profile: chambre + 49-51: profile: unknown 52: profile: uplink name: "Uplink: georges" location: "2B" -ipv4-addr: 172.16.11.37 -ipv4-subnet: 255.255.255.0 -ipv6-addr: fd00::1 -ipv6-subnet: 64 +ipv4-addr: 172.16.33.37 +ipv4-subnet: 255.255.252.0 diff --git a/configs/switches/manoir-0.yml b/configs/switches/manoir-0.yml new file mode 100644 index 0000000..ca78aad --- /dev/null +++ b/configs/switches/manoir-0.yml @@ -0,0 +1,8 @@ +hostname: "manoir-0" +nb_ports: 28 +interfaces: + 1-3: + profile: borne + 25: + profile: kim + name: 'Pont wifi manoir <-> crans' diff --git a/configs/switches/script b/configs/switches/script new file mode 100755 index 0000000..a33d84c --- /dev/null +++ b/configs/switches/script @@ -0,0 +1,12 @@ +#!/bin/bash + +dir='../../configs-backup/' +name=$1 +port=$(cat $dir$name.bak | grep interface | tail -n 1 | awk '{print $2}') + +echo "hostname: \"$name\"" > $name.yml +echo "nb_ports: $port" >> $name.yml +echo "interfaces:" >> $name.yml + +cat $dir$name.bak | grep interface -A 3 | less + diff --git a/configs/vlans/1.yml b/configs/vlans/1.yml new file mode 100644 index 0000000..b7e7108 --- /dev/null +++ b/configs/vlans/1.yml @@ -0,0 +1,7 @@ +name: void +dhcp-snooping: false +dhcpv6-snooping: false +igmp: false +mld: false +arp-protect: false +has_ip: false diff --git a/configs/vlans/10.yml b/configs/vlans/10.yml new file mode 100644 index 0000000..f6c4367 --- /dev/null +++ b/configs/vlans/10.yml @@ -0,0 +1,7 @@ +name: "adm" +dhcp-snooping: false +dhcpv6-snooping: false +igmp: false +mld: false +arp-protect: false +has_ip: false diff --git a/configs/vlans/11.yml b/configs/vlans/11.yml new file mode 100644 index 0000000..067f065 --- /dev/null +++ b/configs/vlans/11.yml @@ -0,0 +1,7 @@ +name: "infra" +dhcp-snooping: false +dhcpv6-snooping: false +igmp: false +mld: false +arp-protect: false +has_ip: true diff --git a/configs/vlans/12.yml b/configs/vlans/12.yml new file mode 100644 index 0000000..74f3ce3 --- /dev/null +++ b/configs/vlans/12.yml @@ -0,0 +1 @@ +name: "adh" diff --git a/configs/vlans/13.yml b/configs/vlans/13.yml new file mode 100644 index 0000000..1abbf6d --- /dev/null +++ b/configs/vlans/13.yml @@ -0,0 +1 @@ +name: "adh-nat" diff --git a/configs/vlans/14.yml b/configs/vlans/14.yml new file mode 100644 index 0000000..f6790f5 --- /dev/null +++ b/configs/vlans/14.yml @@ -0,0 +1 @@ +name: "accueil" diff --git a/configs/vlans/15.yml b/configs/vlans/15.yml new file mode 100644 index 0000000..9219c57 --- /dev/null +++ b/configs/vlans/15.yml @@ -0,0 +1,7 @@ +name: "open" +dhcp-snooping: false +dhcpv6-snooping: false +igmp: false +mld: false +arp-protect: false +has_ip: false diff --git a/configs/vlans/2.yml b/configs/vlans/2.yml new file mode 100644 index 0000000..4d09cd4 --- /dev/null +++ b/configs/vlans/2.yml @@ -0,0 +1,7 @@ +name: "srv" +dhcp-snooping: false +dhcpv6-snooping: false +igmp: false +mld: false +arp-protect: false +has_ip: false diff --git a/configs/vlans/254.yml b/configs/vlans/254.yml new file mode 100644 index 0000000..2bae331 --- /dev/null +++ b/configs/vlans/254.yml @@ -0,0 +1 @@ +name: "federez" diff --git a/configs/vlans/26.yml b/configs/vlans/26.yml new file mode 100644 index 0000000..9bcdc7a --- /dev/null +++ b/configs/vlans/26.yml @@ -0,0 +1,7 @@ +name: "zayo" +dhcp-snooping: false +dhcpv6-snooping: false +igmp: false +mld: false +arp-protect: false +has_ip: false diff --git a/configs/vlans/3.yml b/configs/vlans/3.yml new file mode 100644 index 0000000..54f862f --- /dev/null +++ b/configs/vlans/3.yml @@ -0,0 +1,7 @@ +name: "srv-nat" +dhcp-snooping: false +dhcpv6-snooping: false +igmp: false +mld: false +arp-protect: false +has_ip: false diff --git a/configs/vlans/4.yml b/configs/vlans/4.yml new file mode 100644 index 0000000..33fd538 --- /dev/null +++ b/configs/vlans/4.yml @@ -0,0 +1,7 @@ +name: "san" +dhcp-snooping: false +dhcpv6-snooping: false +igmp: false +mld: false +arp-protect: false +has_ip: false diff --git a/core.py b/core.py new file mode 100644 index 0000000..4ccd1d5 --- /dev/null +++ b/core.py @@ -0,0 +1,90 @@ +import logging +import os +import socket + +from ssh2.session import Session +from ssh2.sftp import LIBSSH2_FXF_READ, LIBSSH2_SFTP_S_IRUSR, LIBSSH2_FXF_CREAT, LIBSSH2_FXF_WRITE, \ + LIBSSH2_SFTP_S_IRGRP, LIBSSH2_SFTP_S_IWUSR, LIBSSH2_SFTP_S_IROTH +from ssh2.utils import wait_socket + +logger = logging.getLogger() + +def get_output(channel): + out = b"" + size, data = channel.read(255) + while True: + out += data + if size < 255: + break + size, data = channel.read(255) + return out.decode() + +def run_command(channel, command): + logger.debug("Running command {}".format(command)) + channel.write(command + "\n") + get_output(channel) # random garbage after running a command. + return get_output(channel) + +def sftp_read_file(session, path): + logger.debug("Reading file from {}".format(path)) + sftp = session.sftp_init() + with sftp.open(path, LIBSSH2_FXF_READ, LIBSSH2_SFTP_S_IRUSR) as fh: + for size, data in fh: + pass + logger.debug("Done reading {}".format(path)) + return data + +def sftp_write_file(session, origin, destination): + logger.debug("Writing {} to {}".format(origin, destination)) + sftp = session.sftp_init() + mode = LIBSSH2_SFTP_S_IRUSR | \ + LIBSSH2_SFTP_S_IWUSR | \ + LIBSSH2_SFTP_S_IRGRP | \ + LIBSSH2_SFTP_S_IROTH + f_flags = LIBSSH2_FXF_CREAT | LIBSSH2_FXF_WRITE + with open(origin, 'rb') as local_fh, \ + sftp.open(destination, f_flags, mode) as remote_fh: + for data in local_fh: + remote_fh.write(data) + logger.debug("Done writing {} to {}".format(origin, destination)) + +def connect_to_switch(address, port=22, user="root", password=None, key=None): + logger.debug("Connecting to {}:{} as {}".format(address, port, user)) + family, _, _, _, sockaddr = socket.getaddrinfo(address, port)[0] + s = socket.socket(family, socket.SOCK_STREAM) + s.connect(sockaddr) + session = Session() + session.handshake(s) + # Authentication + if key is not None: + logger.debug("Connecting with private key {}".format(key)) + if os.path.isfile(key): + session.userauth_publickey_fromfile(user, key) + elif password is not None: + session.userauth_password(user, password) + logger.debug("Connecting with password") + else: + logger.critical("Provide a password or a private key") + exit() + return session + +def get_shell(session): + chan = session.open_session() + chan.shell() + get_output(chan) + get_output(chan) + chan.write("\n") # pass the press Enter part. + get_output(chan) # random stuff. + # Now we can start interacting with the chan + return chan + +if __name__ == "__main__": + session = connect_to_switch("batb-5.switches.crans.org", key="/root/.ssh/id_rsa") + #sftp_write_file(session, "config.bak", "cfg/startup-config") + config = sftp_read_file(session, "cfg/running-config") + #with open("config.bak", "wb") as config_bak: + # config_bak.write(config) + print(config.decode("utf-8")) + #print(run_command(chan, "show run")) + #chan.close() + diff --git a/generate-conf.py b/generate-conf.py new file mode 100644 index 0000000..9f3a596 --- /dev/null +++ b/generate-conf.py @@ -0,0 +1,224 @@ +import argparse +import difflib +import logging + +from pprint import pprint + +import colorlog +import yaml + +from jinja2 import Template + +from core import connect_to_switch, sftp_read_file, sftp_write_file + +logger = logging.getLogger() + +def syntax_from_range(value, vlan_syntax=False): + if value == []: + return None + value = set(value) + prec = None + syntax = "" + in_range = False + for i in value: + print(i, type(i)) + if prec is None or prec != i - 1: + if syntax == "": + syntax = str(i) + elif in_range: + if vlan_syntax: + syntax += "-{} {}".format(prec,i) + else: + syntax += "-{},{}".format(prec,i) + in_range = False + else: + if vlan_syntax: + syntax += " {}".format(i) + else: + syntax += ",{}".format(i) + in_range = False + elif i == max(value): + syntax += "-{}".format(i) + else: + in_range = True + prec = i + if vlan_syntax: + syntax += " " + return syntax + +def range_from_syntax(value, vlan_syntax=False): + if value is None: + return [] + ret_range = list() + if vlan_syntax: + value = str(value).split(" ") + else: + value = str(value).split(",") + for v in value: + v = v.split("-") + if len(v) == 2: + v = list(range(int(v[0]), int(v[1])+1)) + elif len(v) == 1: + v = [int(v[0])] + else: + raise RuntimeError("You fucked") + ret_range += v + return ret_range + +def gen_vlan(vlan_number, switch_config, dhcp_snooping_vlans): + vlan_config = yaml.load(open("configs/vlans/{}.yml".format(vlan_number), "r"), yaml.Loader) + vlan = { + "name": vlan_config.get("name"), + "tagged": list(), + "untagged": list(), + } + if vlan_config.get("dhcp_snooping", True): + dhcp_snooping_vlans.append(int(vlan_number)) + if vlan_config.get("has_ip"): + vlan["ip"] = dict() + v4 = switch_config.get("ipv4-addr") + v4_sub = switch_config.get("ipv4-subnet") + if v4 is not None and v4_sub is not None: + logger.debug("Adding {} {} to vlan {}".format(v4, v4_sub, vlan_number)) + vlan["ip"]["addr"] = v4 + vlan["ip"]["subnet"] = v4_sub + v6 = switch_config.get("ipv6-addr") + v6_sub = switch_config.get("ipv6-subnet") + if v6 is not None and v6_sub is not None: + logger.debug("Adding {}/{} to vlan {}".format(v6, v6_sub, vlan_number)) + vlan["ip"]["addr6"] = v6 + vlan["ip"]["subnet6"] = v6_sub + if vlan["ip"] == {}: + logger.critical("Missing IPs or subnets configurations for vlan {}".format(vlan_number)) + raise RuntimeError() + return vlan, dhcp_snooping_vlans + +def gen_interfaces(switch_config): + dhcp_snooping_vlans = list() + mac_based_ports = list() + ra_guard_ports = list() + ifaces_done = list() + interfaces = list() + vlans = dict() + has_ip = False + for iface, if_config in switch_config.get("interfaces").items(): + if_profile = yaml.load(open("configs/profiles/{}.yml".format(if_config.get("profile"))), yaml.Loader) + iface = range_from_syntax(iface) + for iface_number in iface: + print("if_num", iface_number) + if not if_profile.get("ignore_port", False): + interface = { + "number": iface_number, + } + generic = if_profile.get("generic_name", None) + if generic is None: + interface["name"] = if_config["name"] + else: + interface["name"] = "TODO {} {}".format(generic, iface_number) + interface["mac_based"] = if_profile.get("mac_based", False) + if interface["mac_based"]: + mac_based_ports.append(iface_number) + if if_profile.get("ra_guard", True): + ra_guard_ports.append(iface_number) + interface["addr_limit"] = if_profile.get("addr_limit", 5) + interface["logoff"] = if_profile.get("logoff", 3600) + interfaces.append(interface) + ifaces_done.append(iface_number) + for vlan in range_from_syntax(if_profile.get("vlans").get("untagged")): + if vlans.get(vlan) is None: + vlans[vlan], dhcp_snooping_vlans = gen_vlan(vlan, switch_config, dhcp_snooping_vlans) + vlans[vlan]["untagged"].append(iface_number) + for vlan in range_from_syntax(if_profile.get("vlans").get("tagged")): + if vlans.get(vlan) is None: + vlans[vlan], dhcp_snooping_vlans = gen_vlan(vlan, switch_config, dhcp_snooping_vlans) + vlans[vlan]["tagged"].append(iface_number) + ifaces_done = set(ifaces_done) + # on reformatte les interfaces des vlans + for vlan, v_conf in vlans.items(): + vlans[vlan]["tagged"] = syntax_from_range(v_conf["tagged"]) + vlans[vlan]["untagged"] = syntax_from_range(v_conf["untagged"]) + + if max(ifaces_done) != switch_config.get("nb_ports") or len(ifaces_done) != switch_config.get("nb_ports"): + raise RuntimeError("Interfaces fucked") + interfaces.sort(key=lambda x: x["number"]) + return interfaces, vlans, mac_based_ports, ra_guard_ports, dhcp_snooping_vlans + +def gen_conf(master_config, switch_config, old_config): + header = "\n".join(old_config.split("\n")[:2]) + interfaces, vlans, mac_based_ports, ra_guard_ports, dhcp_snooping_vlans = gen_interfaces(switch_config) + print("mac", set(mac_based_ports)) + print("ra", set(ra_guard_ports)) + print("dhcp", set(dhcp_snooping_vlans)) + config_dict = { + "header": header, + "hostname": switch_config.get("hostname"), + "dhcp_servers": master_config.get("dhcp_servers"), + "dhcpv6_servers": master_config.get("dhcpv6_servers"), + "snmp_user": master_config.get("snmp_user"), + "interfaces": interfaces, + "vlans": vlans, + "sntp": master_config.get("sntp_servers"), + "ipv4_managers": master_config.get("managers_v4", {}), + "ipv6_managers": master_config.get("managers_v6", {}), + "dns": master_config.get("dns"), + "location": switch_config["location"], + "radius_servers": master_config.get("radius_servers"), + "mac_based_ports": syntax_from_range(mac_based_ports), + "ra_guard_ports": syntax_from_range(ra_guard_ports), + "unauth_redirect": master_config.get("unauth_redirect"), + "dhcp_snooping_vlans": syntax_from_range(dhcp_snooping_vlans, vlan_syntax=True), + } + with open("configs/config.j2", "r") as template_file: + template = Template(template_file.read()) + configuration = template.render(config_dict) + return configuration + + + + +if __name__ == "__main__": + format_string = "%(asctime)s - %(levelname)s - %(message)s" + stdout_handler = logging.StreamHandler() + stdout_formatter = colorlog.ColoredFormatter("%(log_color)s{}".format(format_string)) + stdout_handler.setFormatter(stdout_formatter) + logger.addHandler(stdout_handler) + logger.setLevel(logging.DEBUG) + + parser = argparse.ArgumentParser(description="Script de déploiement de la configuration des switchs") + parser.add_argument("-d", "--dry-run", action="store_true", help="Génère la nouvelle configuration et affiche le diff \ + sans tenter de l'appliquer") + parser.add_argument("-w", "--whole", action="store_true", help="Affiche la configuration en entier au lieu du diff") + parser.add_argument("switch_name", type=str, help="Génère le template de ce switch") + parser.add_argument("-H", "--host", type=str, required=False, help="Host sur lequel de déployer la configuration au lieu de l'adresse dans le template") + args = parser.parse_args() + + logger.debug("Loading master config") + master_config = yaml.load(open("configs/master.yml", "r"), yaml.Loader) + logger.debug("Loading config for {}".format(args.switch_name)) + switch_config = yaml.load(open("configs/switches/{}.yml".format(args.switch_name), "r"), yaml.Loader) + + if args.host: + switch_address = args.host + else: + switch_address = switch_config.get("ipv6-addr") or switch_config.get("ipv4-addr") + if switch_address is None: + switch_address = "{}.switches.crans.org".format(args.switch_name) + + logger.info("Connecting to {} with address {}".format(args.switch_name, switch_address)) + session = connect_to_switch(switch_address, user="root", key=master_config.get("ssh_private_key")) + old_config = sftp_read_file(session, "cfg/running-config").decode("utf-8") + + logging.info("Generating configuration for {}".format(args.switch_name)) + configuration = gen_conf(master_config, switch_config, old_config) + for line in difflib.unified_diff(old_config.split("\n"), configuration.split("\n"), fromfile='origin', tofile='new', lineterm=""): + print(line) + if args.dry_run or input("Voulez-vous déployer la configuration sur le switch ? y/[n]").lower() not in ["o","y"]: + logger.info("Aborting deployement") + exit(0) + with open("/tmp/conf_tmp", "w") as conf_tmp: + conf_tmp.write(configuration) + del session + logging.info("Uploading configuration for {}".format(args.switch_name)) + session = connect_to_switch(switch_address, user="crans", key=master_config.get("ssh_private_key")) + sftp_write_file(session, "/tmp/conf_tmp", "cfg/startup-config") + logging.info("Deployement done for {}".format(args.switch_name))