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"
|
hostname: "bata-0"
|
||||||
nb_ports: 52
|
nb_ports: 52
|
||||||
|
ipv4-addr: 10.231.100.45
|
||||||
|
ipv4-subnet: 255.255.255.0
|
||||||
|
location: 0A
|
||||||
interfaces:
|
interfaces:
|
||||||
1-48:
|
1-48:
|
||||||
profile: chambre
|
profile: chambre
|
||||||
|
|
|
@ -2,10 +2,12 @@ hostname: "bata-1"
|
||||||
dhcp-snooping: True
|
dhcp-snooping: True
|
||||||
dhcpv6-snooping: True
|
dhcpv6-snooping: True
|
||||||
nb_ports: 52
|
nb_ports: 52
|
||||||
|
location: 0A
|
||||||
|
ipv4-addr: 10.231.100.46
|
||||||
|
ipv4-subnet: 255.255.255.0
|
||||||
interfaces:
|
interfaces:
|
||||||
1-48:
|
1-48:
|
||||||
profile: chambre
|
profile: chambre
|
||||||
generic_name: True
|
|
||||||
49:
|
49:
|
||||||
profile: uplink
|
profile: uplink
|
||||||
name: 'Uplink bata-2'
|
name: 'Uplink bata-2'
|
||||||
|
|
|
@ -6,13 +6,13 @@ interfaces:
|
||||||
1:
|
1:
|
||||||
profile: downlink
|
profile: downlink
|
||||||
name: 'Downlink: bata-0'
|
name: 'Downlink: bata-0'
|
||||||
2:
|
2:
|
||||||
profile: downlink
|
profile: downlink
|
||||||
name: 'Downlink: bata-1'
|
name: 'Downlink: bata-1'
|
||||||
3:
|
3:
|
||||||
profile: downlink
|
profile: downlink
|
||||||
name: 'Downlink: bata-4'
|
name: 'Downlink: bata-4'
|
||||||
4:
|
4:
|
||||||
profile: downlink
|
profile: downlink
|
||||||
name: 'Downlink: bata-3'
|
name: 'Downlink: bata-3'
|
||||||
11-14,19-20:
|
11-14,19-20:
|
||||||
|
|
|
@ -6,5 +6,10 @@ interfaces:
|
||||||
50:
|
50:
|
||||||
profile: uplink
|
profile: uplink
|
||||||
name: 'Uplink: batm-7'
|
name: 'Uplink: batm-7'
|
||||||
51-52:
|
49,51-52:
|
||||||
profile: unknown
|
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
|
dhcpv6-snooping: False
|
||||||
nb_ports: 52
|
nb_ports: 52
|
||||||
interfaces:
|
interfaces:
|
||||||
1-51:
|
1-48:
|
||||||
|
profile: chambre
|
||||||
|
49-51:
|
||||||
profile: unknown
|
profile: unknown
|
||||||
52:
|
52:
|
||||||
profile: uplink
|
profile: uplink
|
||||||
name: "Uplink: georges"
|
name: "Uplink: georges"
|
||||||
location: "2B"
|
location: "2B"
|
||||||
ipv4-addr: 172.16.11.37
|
ipv4-addr: 172.16.33.37
|
||||||
ipv4-subnet: 255.255.255.0
|
ipv4-subnet: 255.255.252.0
|
||||||
ipv6-addr: fd00::1
|
|
||||||
ipv6-subnet: 64
|
|
||||||
|
|
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