vacances
This commit is contained in:
parent
cda134cea5
commit
9894c0f240
31 changed files with 685 additions and 10 deletions
80
backup-all.py
Normal file
80
backup-all.py
Normal file
|
@ -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)
|
||||
|
130
configs/config.j2
Normal file
130
configs/config.j2
Normal file
|
@ -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
|
22
configs/master.yml
Normal file
22
configs/master.yml
Normal file
|
@ -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
|
5
configs/profiles/backup.yml
Normal file
5
configs/profiles/backup.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
arp-trust: true
|
||||
dhcp-trust: true
|
||||
dhcpv6-trust: true
|
||||
vlans:
|
||||
tagged: 10
|
6
configs/profiles/chambre.yml
Normal file
6
configs/profiles/chambre.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
vlans:
|
||||
untagged: 1
|
||||
generic_name: chambre_re2o
|
||||
mac_based: true
|
||||
addr_limit: 5
|
||||
logoff: 3600
|
9
configs/profiles/defaults.yml
Normal file
9
configs/profiles/defaults.yml
Normal file
|
@ -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
|
2
configs/profiles/downlink.yml
Normal file
2
configs/profiles/downlink.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
vlans:
|
||||
tagged: 2-4,10-15,26,254
|
8
configs/profiles/open.yml
Normal file
8
configs/profiles/open.yml
Normal file
|
@ -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
|
3
configs/profiles/unknown.yml
Normal file
3
configs/profiles/unknown.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
vlans:
|
||||
untagged: 1
|
||||
ignore_port: true
|
6
configs/profiles/uplink.yml
Normal file
6
configs/profiles/uplink.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
ra-guard: false
|
||||
dhcp-trust: true
|
||||
dhcpv6-trust: true
|
||||
arp-trust: true
|
||||
vlans:
|
||||
tagged: 2-4,10-15,26,254
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
8
configs/switches/manoir-0.yml
Normal file
8
configs/switches/manoir-0.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
hostname: "manoir-0"
|
||||
nb_ports: 28
|
||||
interfaces:
|
||||
1-3:
|
||||
profile: borne
|
||||
25:
|
||||
profile: kim
|
||||
name: 'Pont wifi manoir <-> crans'
|
12
configs/switches/script
Executable file
12
configs/switches/script
Executable file
|
@ -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
|
||||
|
7
configs/vlans/1.yml
Normal file
7
configs/vlans/1.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: void
|
||||
dhcp-snooping: false
|
||||
dhcpv6-snooping: false
|
||||
igmp: false
|
||||
mld: false
|
||||
arp-protect: false
|
||||
has_ip: false
|
7
configs/vlans/10.yml
Normal file
7
configs/vlans/10.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: "adm"
|
||||
dhcp-snooping: false
|
||||
dhcpv6-snooping: false
|
||||
igmp: false
|
||||
mld: false
|
||||
arp-protect: false
|
||||
has_ip: false
|
7
configs/vlans/11.yml
Normal file
7
configs/vlans/11.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: "infra"
|
||||
dhcp-snooping: false
|
||||
dhcpv6-snooping: false
|
||||
igmp: false
|
||||
mld: false
|
||||
arp-protect: false
|
||||
has_ip: true
|
1
configs/vlans/12.yml
Normal file
1
configs/vlans/12.yml
Normal file
|
@ -0,0 +1 @@
|
|||
name: "adh"
|
1
configs/vlans/13.yml
Normal file
1
configs/vlans/13.yml
Normal file
|
@ -0,0 +1 @@
|
|||
name: "adh-nat"
|
1
configs/vlans/14.yml
Normal file
1
configs/vlans/14.yml
Normal file
|
@ -0,0 +1 @@
|
|||
name: "accueil"
|
7
configs/vlans/15.yml
Normal file
7
configs/vlans/15.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: "open"
|
||||
dhcp-snooping: false
|
||||
dhcpv6-snooping: false
|
||||
igmp: false
|
||||
mld: false
|
||||
arp-protect: false
|
||||
has_ip: false
|
7
configs/vlans/2.yml
Normal file
7
configs/vlans/2.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: "srv"
|
||||
dhcp-snooping: false
|
||||
dhcpv6-snooping: false
|
||||
igmp: false
|
||||
mld: false
|
||||
arp-protect: false
|
||||
has_ip: false
|
1
configs/vlans/254.yml
Normal file
1
configs/vlans/254.yml
Normal file
|
@ -0,0 +1 @@
|
|||
name: "federez"
|
7
configs/vlans/26.yml
Normal file
7
configs/vlans/26.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: "zayo"
|
||||
dhcp-snooping: false
|
||||
dhcpv6-snooping: false
|
||||
igmp: false
|
||||
mld: false
|
||||
arp-protect: false
|
||||
has_ip: false
|
7
configs/vlans/3.yml
Normal file
7
configs/vlans/3.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: "srv-nat"
|
||||
dhcp-snooping: false
|
||||
dhcpv6-snooping: false
|
||||
igmp: false
|
||||
mld: false
|
||||
arp-protect: false
|
||||
has_ip: false
|
7
configs/vlans/4.yml
Normal file
7
configs/vlans/4.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: "san"
|
||||
dhcp-snooping: false
|
||||
dhcpv6-snooping: false
|
||||
igmp: false
|
||||
mld: false
|
||||
arp-protect: false
|
||||
has_ip: false
|
90
core.py
Normal file
90
core.py
Normal file
|
@ -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()
|
||||
|
224
generate-conf.py
Normal file
224
generate-conf.py
Normal file
|
@ -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))
|
Loading…
Reference in a new issue