Standaryzacja konfiguracji urządzeń sieciowych Cisco – narzędzia skryptowe

cisco_routersW kolejnym kroku po audycie, być może wystąpi potrzeba standaryzacji konfiguracji, w ramach której będziemy chcieli sprawnie wdrożyć zmiany wynikające z zaleceń audytowych.

Z całą pewnością pomocne okażą się narzędzia skryptowe, które generowanie plików konfiguracyjnych mogą uczynić procesem automatycznym lub semi-automatycznym, a na pewno znacznie mniej czasochłonnym i dużo prostszym do wykonania.

Posłużymy się językiem Python i biblioteką ‚ciscoconfparse‚. O tym jak ją zainstalować i jak z niej skorzystać napisałem w poprzednim artykule: https://janusznawrat.wordpress.com/2015/10/24/audyt-konfiguracji-urzadzen-sieciowych-z-systemem-cisco-ios-narzedzia-skryptowe/.

W przypadku audytu konfiguracji, rzecz jasna, istniała potrzeba dostępu do plików konfiguracyjnych jedynie w trybie do odczytu i na takiej właśnie funkcjonalności blokowego czytania hierarchicznej konfiguracji urządzeń wówczas skupiliśmy się.

Natomiast w przypadku standaryzacji konfiguracji, potrzebujemy czegoś więcej – zmian w plikach konfiguracyjnych, takich jak chociażby wstawianie, podmiana i usuwanie linii czy bloków konfiguracji.

Przykładowo, dopisywanie do końca pliku konfiguracyjnego realizujemy w sposób analogiczny do przedstawionego w tym przykładzie:

if len(parse.find_objects(r'^service\stcp-small-servers')):
    parse.append_line('no service tcp-small-servers')
    print "[-] Service tcp-small-servers disabled"

Dopisywanie do początku pliku konfiguracyjnego realizujemy zaś w następujący sposób:

if not parse.has_line_with(r'^service\stimestamp'):
    parse.prepend_line('service timestamps debug datetime msec localtime show-timezone')
    parse.prepend_line('service timestamps log datetime msec localtime show-timezone')
    parse.insert_after(r'service timestamps debug', insertstr='!')
    print "[+] Service timestamp enabled."
    parse.commit()

Dopisanie fragmentów konfiguracji do określonego podpoziomu trybu konfiguracyjnego oraz dopisywanie za wskazanym przy pomocy wyrażenia regularnego wierszem pliku konfiguracyjnego, ilustruje przykład z konfiguracją linii terminalowych:

for line in parse.find_objects(r'^line'):
    if not line.has_child_with(r'login authentication'):
        answer = raw_input("Enable login authentication on %s line? (Y/N): " % line.text)
        if answer.upper() == 'Y':
            if 'vty' in line.text:
                # To correct a problem with append_to_family for range of lines
                parse.insert_after(r'line vty', insertstr=' login authentication local_auth')
            else:    
                line.append_to_family(' login authentication local_auth')
                line.append_to_family(' exec-timeout 10 0')
            parse.commit()

Ważne jest, by pamiętać o wykonaniu metody commit() obiektu po każdej zmianie treści danych, a przed kolejną operacją wyszukiwania w nim informacji.

Załączony do artykułu skrypt można uruchomić w następujący sposób, otrzymując następujące rezultaty na standardowym wyjściu:

$ python cisco_standardize_config.py -i cisco.conf
   ____ _                                  __ _
  / ___(_)___  ___ ___     ___ ___  _ __  / _(_) __ _
 | |   | / __|/ __/ _ \   / __/ _ \| '_ \| |_| |/ _` |
 | |___| \__ \ (_| (_) | | (_| (_) | | | |  _| | (_| |
  \____|_|___/\___\___/   \___\___/|_| |_|_| |_|\__, |
                                                 |___/
 == Tool for standardize Cisco router configuration ==
                                  Janusz Nawrat (2015)

[+] Service timestamp enabled
[*] Disabling insecure services
[+] Service password-encryption is enabled
[+] Service tcp-keepalives-in and tcp-keepalives-out are enabled
[+] Service service sequence-numbers is enabled
How many authentication failure allowed
before blockig access to router (default 10)?
[+] Authentication failure rate limit is enabled and set to 10 trials
Minimum length of login passwords (default 8): 10
[+] Minimum password length on 10 was set
[+] AAA functionality is enabled
Would you like to disable source routing? (Y/N) y
[-] source routing disabled
Would you like to disable gratuitious ARP? (Y/N) y
[-] Gratuitous ARP disabled
Enable login authentication on line con 0 line? (Y/N): y
Enable login authentication on line aux 0 line? (Y/N): y
Enable login authentication on line vty 0 4 line? (Y/N): y
Logging facility (local0-local7) (default local6): y
[*] Logging facility local6
[*] Removing temporary file: cisco.conf.temp
[*] Saving configuration changes to new config file cisco-conf.new

Oto kod skryptu:

# -*- coding: utf-8 -*-
from ciscoconfparse import CiscoConfParse
from optparse import OptionParser
import re, os, os.path, sys, string

if __name__ == "__main__":
 CONFIG_INFILE = 'cisco.conf'
 
 usage = u"""
   ____ _                                  __ _       
  / ___(_)___  ___ ___     ___ ___  _ __  / _(_) __ _ 
 | |   | / __|/ __/ _ \   / __/ _ \| '_ \| |_| |/ _` |
 | |___| \__ \ (_| (_) | | (_| (_) | | | |  _| | (_| |
  \____|_|___/\___\___/   \___\___/|_| |_|_| |_|\__, |
                                                 |___/
 == Tool for standardize Cisco router configuration ==
                                  Janusz Nawrat (2015) 
 """
 parser = OptionParser(usage=usage)
 
 parser.add_option('-i', '--input_file', type='string',
 help='input config file (default: cisco.conf)',
 dest='infile', default="cisco.conf")
 
 options, args = parser.parse_args()

 if options.infile:
 CONFIG_INFILE = options.infile
 
 if not os.path.isfile(CONFIG_INFILE):
 print usage
 print >> sys.stderr, "FATAL ERROR: %s doesn't exist" % CONFIG_INFILE
 sys.exit(1)
 
 # parser.print_help()
 print usage
 parse = CiscoConfParse(CONFIG_INFILE)
 
 if not parse.has_line_with(r'^service\stimestamp'):
 parse.prepend_line('service timestamps debug datetime msec localtime show-timezone')
 parse.prepend_line('service timestamps log datetime msec localtime show-timezone')
 parse.insert_after(r'service timestamps debug', insertstr='!')
 print "[+] Service timestamp enabled"
 parse.commit()
 
 print "[*] Disabling insecure services"
 if len(parse.find_objects(r'^service\sfinger')):
 parse.append_line('no service finger')
 print "[-] Service finger disabled"
 parse.commit()
 
 if len(parse.find_objects(r'^service\spad')):
 parse.append_line('no service pad')
 print "[-] Service pad disabled"
 
 if len(parse.find_objects(r'^service\sudp-small-servers')):
 parse.append_line('no service udp-small-servers')
 print "[-] Service udp-small-servers disabled"
 parse.commit()
 
 if len(parse.find_objects(r'^service\stcp-small-servers')):
 parse.append_line('no service tcp-small-servers')
 print "[-] Service tcp-small-servers disabled"
 
 if len(parse.find_objects(r'^ip\sbootp\sserver')):
 parse.append_line('no ip bootp server')
 print "[-] Service bootp disabled"
 parse.commit()
 
 if len(parse.find_objects(r'^ip\shttp\sserver')):
 parse.append_line('no ip http server')
 print "[-] Service http disabled"
 
 if len(parse.find_objects(r'^ip\sfinger')):
 parse.append_line('no ip finger')
 print "[-] Service IP finger disabled"
 parse.commit()

 if len(parse.find_objects(r'cdp\srun')):
 parse.append_line('no cdp run')
 print "[-] Service CDP disabled"
 parse.commit()
 
 if len(parse.find_objects(r'ip\sidentd')):
 parse.append_line('no ip identd')
 print "[-] Service IP identd disabled"
 parse.commit()
 
 parse.append_line('service password-encryption')
 print "[+] Service password-encryption is enabled"
 parse.append_line('service tcp-keepalives-in')
 parse.append_line('service tcp-keepalives-out')
 print "[+] Service tcp-keepalives-in and tcp-keepalives-out are enabled"
 parse.append_line('service sequence-numbers')
 print "[+] Service service sequence-numbers is enabled"
 parse.commit()
 
 answer = raw_input("How many authentication failure allowed\nbefore blockig access to router (default 10)? ")
 if len(answer) == 0 or not re.search(r'^\d+$', answer):
 answer = "10"
 parse.append_line('security authentication failure rate %d log' % int(answer))
 print "[+] Authentication failure rate limit is enabled and set to %d trials" % int(answer)
 parse.commit()
 
 answer = raw_input("Minimum length of login passwords (default 8): ")
 if len(answer) == 0 or not re.search(r'^\d+$', answer):
 answer = "8"
 parse.append_line('security passwords min-length %d' % int(answer))
 print "[+] Minimum password length on %d was set" % int(answer)
 parse.append_line('!')
 parse.commit()
 
 if not len(parse.find_objects(r'aaa\snew-model')):
 parse.insert_before(r'^line con', 'aaa new-model')
 parse.commit()
 
 parse.insert_after(r'^aaa new-model', 'aaa authentication login local_auth local')
 parse.insert_after(r'^aaa authentication login local_auth local', '!')
 parse.commit()
 print "[+] AAA functionality is enabled"
 
 choice = raw_input("Would you like to disable source routing? (Y/N) ")
 if choice.upper() == 'Y':
 parse.append_line('no ip source-route')
 print "[-] source routing disabled"
 parse.commit()
 
 choice = raw_input("Would you like to disable gratuitious ARP? (Y/N) ")
 if choice.upper() == 'Y':
 parse.append_line('no ip gratuitous-arps')
 print "[-] Gratuitous ARP disabled"
 parse.commit()
 
 for line in parse.find_objects(r'^line'):
 if not line.has_child_with(r'login authentication'):
 answer = raw_input("Enable login authentication on %s line? (Y/N): " % line.text)
 if answer.upper() == 'Y':
 if 'vty' in line.text:
 # To correct a problem with append_to_family for range of lines
 parse.insert_after(r'line vty', insertstr=' login authentication local_auth')
 else: 
 line.append_to_family(' login authentication local_auth')
 line.append_to_family(' exec-timeout 10 0')
 parse.commit()
 
 if not parse.find_objects(r'^logging\sfacility'):
 fac = raw_input("Logging facility (local0-local7) (default local6): ")
 if not re.search(r'^local\d$', fac):
 fac = 'local6'
 print "[*] Logging facility %s" % fac
 parse.append_line('logging facility %s' % fac) 
 parse.commit()
 
 parse.append_line('logging trap debugging')
 parse.append_line('logging console critical')
 parse.append_line('logging buffered')
 parse.append_line('!')
 parse.commit()
 
 parse.save_as('cisco.conf.temp')
 
 # Removing extra newline and 'end' keyword not at the actual end of the config file
 with open('cisco.conf.temp', 'r') as f:
 content = f.readlines()
 
 with open('cisco-conf.new', 'w') as the_file:
 for line in content:
 line = string.replace(line, '\r\n', '\n')
 line = re.sub(r'^end', '!', line)
 the_file.write(line)
 
 the_file.write('end') 
 
 if os.path.exists('cisco.conf.temp'):
 print "[*] Removing temporary file: cisco.conf.temp"
 os.remove('cisco.conf.temp')
 
 print "[*] Saving configuration changes to new config file cisco-conf.new"

Na potrzeby przykładu przyjęto następujący wejściowy plik konfiguracyjny:

policy-map QOS_1
 class GOLD
  priority percent 10
 class SILVER
  bandwidth 30
  random-detect
 class default
!
policy-map QOS_2
 class PLATINUM
  priority percent 30
 class DIAMOND
  bandwidth 45
  random-detect
 class default 
!
policy-map type loadbalance first-match LOAD_BALANCE
 class LB_CLASS
  serverfarm SF1
!
interface Ethernet0/0
 ip address 1.1.2.1 255.255.255.0
 no cdp enable
!
interface FastEthernet0/1
 ip verify unicast source reachable-via rx allow-default 101
 ip inspect audit-trail
 ip inspect dns-timeout 7
 ip inspect tcp idle-time 14400
 ip inspect udp idle-time 1800
 ip inspect name autosec_inspect cuseeme timeout 3600
 ip inspect name autosec_inspect ftp timeout 3600
 ip inspect name autosec_inspect http timeout 3600
 ip inspect name autosec_inspect rcmd timeout 3600
 ip inspect name autosec_inspect realaudio timeout 3600
 ip inspect name autosec_inspect smtp timeout 3600
 ip inspect name autosec_inspect tftp timeout 30
 ip inspect name autosec_inspect udp timeout 15
 ip inspect name autosec_inspect tcp timeout 3600
!
interface FastEthernet0/1
 ip address 192.168.10.1 255.255.255.0
 ip inspect autosec_inspect out
 ip access-group autosec_firewall_acl in
!
interface VLAN1
 ip address 10.1.1.1 255.255.255.0
 service-policy output LOAD_BALANCE
!
interface Serial1/0
 encapsulation ppp
 ip address 1.1.1.1 255.255.255.252
 service-policy output QOS_2
 service-policy input QOS_1
!
interface Serial1/1
 encapsulation ppp
 ip address 1.1.1.5 255.255.255.252
 service-policy output QOS_1
!
interface Serial1/2
 encapsulation hdlc
 ip address 1.1.1.9 255.255.255.252
!
class-map GOLD
 match access-group 102
class-map SILVER
 match protocol tcp
class-map PLATINUM
 match access-group 103
class-map DIAMOND
 match protocol udp
class-map type http loadbalance match-any LB_CLASS
 match http url /foo
 match http url /bar
 match access-group in_to_out
!
access-list 102 permit tcp host 10.1.1.2 host 10.2.1.1
access-list 102 permit ip any 10.240.1.1 0.0.255.255
!
access-list 103 permit tcp any any eq 22
access-list 103 permit tcp any host 10.34.1.101 eq 443
!
ip access-list extended in_to_out 
 permit tcp host 10.1.1.2 host 172.16.1.1 eq www 
 permit tcp host 10.1.1.2 host 172.16.1.1 eq 443
! 
ip access-list extended autosec_firewall_acl
 permit udp any any eq bootpc
 deny ip any any
!
line con 0
 no modem enable
 transport preferred all
 transport output all
line aux 0
 transport preferred all
 transport output all
line vty 0 4
 transport preferred all
 transport input all
!
end

Wyjściowy plik konfiguracyjny powstały na skutek działania narzędzia wygląda tak:

service timestamps log datetime msec localtime show-timezone
service timestamps debug datetime msec localtime show-timezone
!
policy-map QOS_1
 class GOLD
  priority percent 10
 class SILVER
  bandwidth 30
  random-detect
 class default
!
policy-map QOS_2
 class PLATINUM
  priority percent 30
 class DIAMOND
  bandwidth 45
  random-detect
 class default 
!
policy-map type loadbalance first-match LOAD_BALANCE
 class LB_CLASS
  serverfarm SF1
!
interface Ethernet0/0
 ip address 1.1.2.1 255.255.255.0
 no cdp enable
!
interface FastEthernet0/1
 ip verify unicast source reachable-via rx allow-default 101
 ip inspect audit-trail
 ip inspect dns-timeout 7
 ip inspect tcp idle-time 14400
 ip inspect udp idle-time 1800
 ip inspect name autosec_inspect cuseeme timeout 3600
 ip inspect name autosec_inspect ftp timeout 3600
 ip inspect name autosec_inspect http timeout 3600
 ip inspect name autosec_inspect rcmd timeout 3600
 ip inspect name autosec_inspect realaudio timeout 3600
 ip inspect name autosec_inspect smtp timeout 3600
 ip inspect name autosec_inspect tftp timeout 30
 ip inspect name autosec_inspect udp timeout 15
 ip inspect name autosec_inspect tcp timeout 3600
!
interface FastEthernet0/1
 ip address 192.168.10.1 255.255.255.0
 ip inspect autosec_inspect out
 ip access-group autosec_firewall_acl in
!
interface VLAN1
 ip address 10.1.1.1 255.255.255.0
 service-policy output LOAD_BALANCE
!
interface Serial1/0
 encapsulation ppp
 ip address 1.1.1.1 255.255.255.252
 service-policy output QOS_2
 service-policy input QOS_1
!
interface Serial1/1
 encapsulation ppp
 ip address 1.1.1.5 255.255.255.252
 service-policy output QOS_1
!
interface Serial1/2
 encapsulation hdlc
 ip address 1.1.1.9 255.255.255.252
!
class-map GOLD
 match access-group 102
class-map SILVER
 match protocol tcp
class-map PLATINUM
 match access-group 103
class-map DIAMOND
 match protocol udp
class-map type http loadbalance match-any LB_CLASS
 match http url /foo
 match http url /bar
 match access-group in_to_out
!
access-list 102 permit tcp host 10.1.1.2 host 10.2.1.1
access-list 102 permit ip any 10.240.1.1 0.0.255.255
!
access-list 103 permit tcp any any eq 22
access-list 103 permit tcp any host 10.34.1.101 eq 443
!
ip access-list extended in_to_out 
 permit tcp host 10.1.1.2 host 172.16.1.1 eq www 
 permit tcp host 10.1.1.2 host 172.16.1.1 eq 443
! 
ip access-list extended autosec_firewall_acl
 permit udp any any eq bootpc
 deny ip any any
!
aaa new-model
aaa authentication login local_auth local
!
line con 0
 no modem enable
 transport preferred all
 transport output all
 exec-timeout 10 0
 login authentication local_auth
line aux 0
 transport preferred all
 transport output all
 exec-timeout 10 0
 login authentication local_auth
line vty 0 4
 login authentication local_auth
 transport preferred all
 transport input all
!
service password-encryption
service tcp-keepalives-in
service tcp-keepalives-out
service sequence-numbers
security authentication failure rate 10 log
security passwords min-length 10
!
no ip source-route
no ip gratuitous-arps
logging facility local6
logging trap debugging
logging console critical
logging buffered
!
end

Poszczególne sekcje konfiguracji nie występują w wygenerowanym pliku we właściwej kolejności, ale naprawę tego drobnego defektu zostawiam szanownym czytelnikom, zachęcając jak zwykle do eksperymentów z kodem.

Informacje Janusz Nawrat
Just ordinary man who likes thinking...

One Response to Standaryzacja konfiguracji urządzeń sieciowych Cisco – narzędzia skryptowe

  1. Janusz Nawrat pisze:

    Jeśli mamy potrzebę wstawienia fragmentu konfiguracji w określone miejsce pliku, to możemy posłużyć się sposobem przedstawionym w poniższym fragmencie kodu:

    if not len(parse.find_objects(r'aaa\snew-model')):
        parse.insert_before(r'^line con', 'aaa new-model')
        parse.commit()
    

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

TOMASZ WEŁNA

artysta grafik | wykładowca

PRACOWNIA OKO

Szkoła Rysunku Malarstwa i Grafiki DR TOMASZA WEŁNY | KRAKÓW | Plac Matejki 10 | tel 691 81 75 74

Piękno neurobiologii

Blog Jerzego Vetulaniego

Teoria muzyki, zasady muzyki, podstawy muzyki

Teoria muzyki, zasady muzyki, podstawy muzyki - czyli to co każdy amator muzyki wiedzieć powinien :)

Personal Development & Inspirations

Przemyślenia i refleksje, którymi warto się podzielić (blog by Janusz Nawrat)

Business IT Cooperation Platform

Biznes i IT - dwa światy, które muszą współdziałać

%d bloggers like this: