aurore
Michaël Paulon 4 years ago
parent cda134cea5
commit 9894c0f240

@ -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)

@ -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

@ -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

@ -0,0 +1,5 @@
arp-trust: true
dhcp-trust: true
dhcpv6-trust: true
vlans:
tagged: 10

@ -0,0 +1,6 @@
vlans:
untagged: 1
generic_name: chambre_re2o
mac_based: true
addr_limit: 5
logoff: 3600

@ -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

@ -0,0 +1,2 @@
vlans:
tagged: 2-4,10-15,26,254

@ -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

@ -0,0 +1,3 @@
vlans:
untagged: 1
ignore_port: true

@ -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

@ -0,0 +1,8 @@
hostname: "manoir-0"
nb_ports: 28
interfaces:
1-3:
profile: borne
25:
profile: kim
name: 'Pont wifi manoir <-> crans'

@ -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

@ -0,0 +1,7 @@
name: void
dhcp-snooping: false
dhcpv6-snooping: false
igmp: false
mld: false
arp-protect: false
has_ip: false

@ -0,0 +1,7 @@
name: "adm"
dhcp-snooping: false
dhcpv6-snooping: false
igmp: false
mld: false
arp-protect: false
has_ip: false

@ -0,0 +1,7 @@
name: "infra"
dhcp-snooping: false
dhcpv6-snooping: false
igmp: false
mld: false
arp-protect: false
has_ip: true

@ -0,0 +1 @@
name: "adh"

@ -0,0 +1 @@
name: "adh-nat"

@ -0,0 +1 @@
name: "accueil"

@ -0,0 +1,7 @@
name: "open"
dhcp-snooping: false
dhcpv6-snooping: false
igmp: false
mld: false
arp-protect: false
has_ip: false

@ -0,0 +1,7 @@
name: "srv"
dhcp-snooping: false
dhcpv6-snooping: false
igmp: false
mld: false
arp-protect: false
has_ip: false

@ -0,0 +1 @@
name: "federez"

@ -0,0 +1,7 @@
name: "zayo"
dhcp-snooping: false
dhcpv6-snooping: false
igmp: false
mld: false
arp-protect: false
has_ip: false

@ -0,0 +1,7 @@
name: "srv-nat"
dhcp-snooping: false
dhcpv6-snooping: false
igmp: false
mld: false
arp-protect: false
has_ip: false

@ -0,0 +1,7 @@
name: "san"
dhcp-snooping: false
dhcpv6-snooping: false
igmp: false
mld: false
arp-protect: false
has_ip: false

@ -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()

@ -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…
Cancel
Save