joi, 25 aprilie 2013

... cu de toate (Linux, Expect, Cisco)


Ideea de la care am pornit a fost sa fac un script in BASH si cu ajutorul limbajul EXPECT sa execut comenzi, unele interactive (adica trebuie sa introduci parametrii, de ex. comanda enable
solicita introducerea unui parole s.a.m.d), pe un numar de elemente de retea (aici Cisco) a.i. sa pot configura la orice moment un numar cat mai mare din acestea fara sa ma conectez eu pe fiecare in parte.

Ziceam pe cand predam cursuri - si in continuare cred in faptul - ca si lenea a dus la progres.

Ceea ce am scris mai jos nu necesita cunostinte avansate de BASH scripting si de EXPECT, eu nu stiu mai mult de genunchiul broastei; totusi in 3-4 zile am reusit sa inteleg sintaxa de baza pt BASH si EXPECT si iata ce a iesit.

Scriptul se lanseaza cu comanda sh <numescript>.sh <inputFile>.csv

Fisierul cu datele de intrare <inputFile>.csv este in format csv, fiecare linie este de forma:
ipaddr,<numefisier>.txt (de ex: 192.168.1.2,routerBackbone.txt)

<numefisier>.txt contine pe fiecare linie comenzile pe care doriti sa le executati pe fiecare element de retea identificat cu ipaddr, de ex.:

show clock
conf t
interface Gi 0/0/0
no shutdown
ip address 10.0.0.0.1 255.255.255.0
exit
exit
write memory

De la linia de comanda se introduce user/parola, parola VTY si enable secret; doar userul este afisat pe ecran
Erorile se salveaza in fisierul <numefisier>.error.log, iar outputul comenzilor executate pe fiecare element de retea se savleaza in fisierul <numefisier>.report

Va rog sa inlocuiti <numefisier> cu ceva care are sens pt fiecare.

Continutul fisierului <numescript>.sh este:


#!/bin/bash

#functia de mai jos se apeleaza cu 3 parametrii si practic trimite 5 mesaje ICMP echo de marime 512 octeti 
#si asteapta raspuns timp de 10 sec
#nu ma intereseaza sa capturez outputul comenzii ping si nici eventualele erori

isAlive() {
host=$1
contor=$2
rows=$3
         echo -e "\n$contor/$rows#####start probing...$host"
         ping -c 5 -s 512 -w 10 $host > /dev/null 2>&1
}

#IFS este parametrul prin care ii transmit BASH ca Internal Field Separator este ,
IFS=","

#mai jos ii transmit BASH ca fisierul de intrare este primul argument din comanda de lansare
inputFile=$1

#ma asigur ca fisierul de intrare exista (-f) si ca are o lungime diferita de 0 octeti (-s)
if [ -f $inputFile -a -s $inputFile ]
then

#mai jos solicit utilizatorului sa introduca user/parola, VTY line password si enable secret
 stty echo
 echo username:
 read userName
 echo password:
 stty -echo
 read passWord
 stty echo
 echo VTY password:
 stty -echo
 read VTYpassWord
 stty echo
 echo ROOT/ENABLE password:
 stty -echo
 read RootPassWord
 stty echo

#mai jos ma asigur ca s-au introdus niste valori si nu s-a apasat doar Enter
 if [ "x$userName" != x -a "x$passWord" != x -a "x$VTYpassWord" != x -a "x$RootPassWord" != x ]
 then

#numar cate elemente de retea sunt in total
rows=`cat $inputFile | wc -l`

#sterg continutul fisierului cu erori si al celui cu outputul comenzilor
echo > <numefisier>.error.log
echo > <numefisier>.report




#initializez un contor care imi raporteaza indexul elementului de retea la care a ajuns scriptul 


i=1





#parcurg fisierul inputFile linie cu linie pana la sfarsitul acestuia

while read line

        do

#varialbila host va contine adresa IP a elementului de retea
                host=`echo $line | awk '{print $1}'`

#variabila inputCmdFile va contine numele fisierului cu comenzile de executat
                inputCmdFile=`echo $line | awk '{print $2}'`

#daca fisierul cu comenzi exista (-f) si are marimea diferita de 0 octeti (-s)
                if [ -f $inputCmdFile -a -s $inputCmdFile ]
                then

#variabila NOW contine momentul de timp, folositor pt raportare erori
                 NOW=$(date +"%c")

#apelez functia prin care testez ca elementul de retea este pornit
                 isAlive $host $i $rows

#daca rezultatul de executie al functiei isAlive este 0 (in BASH valoarea 0 inseamna SUCCESS)
                 if [ $? -eq "0" ]
                 then

#apelez scriptul <numescriptEXPECT>.expect pe care il gasiti mai jos (eu am facut acest script ca fiind 
#executabil) si outpul executiei il documentez in fisierul <numefisier>.report
./<numescriptEXPECT>.expect $inputCmdFile $host $userName $passWord $VTYpassWord $RootPassWord $NOW >> <numefisier>.report

#incrementez contor 
                       let i++

#daca elementul de retea nu raspunde la ping, documentez eroarea in fisierul cu erori si incrementez contor
                else
                        echo $NOW,$host",INACCESIBILl!" >> sendCmdCisco.error.log
                        let i++
                 fi

#daca un fisier cu comenzi nu exista sau are dimensiune 0 octeti, trec la urmatorul element


                else

                 continue
                fi
        done < $inputFile



#daca nu s-a introdus una  valorile user/password/VTYpassword/enableSecret, se iese din script
else echo "EROARE: Nu ati introdus user/password/VTYpassword/rootpassword!"; exit 1
 fi



#daca nu s-a introdus argumentul pt lansarea scriptului (cel cu datele de intrare), se iese din script

else

 echo "EROARE: Verificati fisierul de intrare $inputFile !"; exit 1
fi


Scriptul scris in EXPECT (<numescriptEXPECT>.expect) arata dupa cum urmeaza:

#!/usr/local/bin/expect

#parcurg fisierul cu comenzi si introduc fiecare comanda in lista inputCmd
set inputCmd [split [read [open [lindex $argv 0] r]] "\n"]

#deschid fisierul cu erori in modul append
set errorFile [open "<numefiser>.error.log" a]

#initializez variablilele host, userName, passWord, VTYpassword, RootPassWord si recordTime
set host [lindex $argv 1]
set userName [lindex $argv 2]
set passWord [lindex $argv 3]
set VTYpassWord [lindex $argv 4]
set RootPassWord [lindex $argv 5]
set recordTime [lindex $argv 6]

#initiez sesiunea Telnet
spawn telnet $host

#daca se solicita username atunci trimit valorile variabilelor userName si apoi passWord
#daca se solicita numai parola atunci trimit valorea variabilelei VTYpassWord
#daca nu se solicita nimic timp de 10 sec atunci documentez eroarea
expect {
"*sername: " {send "$userName\n"; expect "*assword: "; send "$passWord\n"}
"*assword: " {send "$VTYpassWord\n"}
timeout {puts $errorFile "$recordTime,$host,TIMEOUT 1"; exit 1}
}

#daca nu primesc promptul specific user-EXEC (adica >) pt a trece mai departe si se solicita din nou user 
#sau parola VTY atunci documentez eroarea (poate ati gresit cand ati introdus user/parola etc)
expect {
"*sername: " {puts $errorFile "$recordTime,$host,contul de acces $userName INCORECT!!!"; exit 1}
"*assword: " {puts $errorFile "$recordTime,$host,parola de acces VTY INCORECTA!!!"; exit 1}

#SUCCESS, suntem in modul user-EXEC
"*>"
}

#trimit comanda enable pt a ajunge in privileged-EXEC
send "enable\n"

#se solicita parola de escaladare a niv de privilegii (enable secret)
expect "*assword: "

#trimit valoarea variabilei care contine aceasta parola
send "$RootPassWord\n"

#daca nu primesc promptul specific priviled-EXEC (adica #) pt a trece mai departe si solicita din nou parola 
#atunci documentez eroarea (poate ati gresit cand ati introdus enable secret etc)
expect {
"*>" {puts $errorFile "$recordTime,$host,parola de acces in privilegiul 15 INCORECTA!!!"; exit 1}
"*assword: " {puts $errorFile "$recordTime,$host,parola de acces in privilegiul 15 INCORECTA!!!"; exit 1}

#SUCCESS, suntem in modul privileged-EXEC
"*#"
}

#se trimite fiecare comanda din lista comenzi si apoi se inchide sesiunea Telnet,
#parasind astfel si scriptul scris in EXPECT
foreach cmd $inputCmd {
send "$cmd\n"
expect "*#"
}
exit 0

Ca sa recapitulam:
1. trebuie sa aveti instalat EXPECT
2. va trebuie fisierul <numefisier>.EXPECT - vezi mai sus
3. va trebuie fisierul <numefisier>.sh - vezi mai sus
4. va trebuie <numefisier>.csv - vezi mai sus
5. va trebuie <numefisier>.txt (atatea fisiere cate hosturi aveti, daca nu rulati aceleasi comenzi, altfel numai unul singur) - vezi mai sus

Eu am rulat cu sucess si cu BASH si cu KSH (cu mici modificari) atat din RHEL, Debian, Sun Solaris.
Spor in tot ce faceti si sper sa am vesti cat de curand pt cei care le asteapta (acum suntem cu actele la judecatorie - greu la deal cu boii mici si vale tre' sa-mpingi).  



3 comentarii:

  1. 4 idei:
    * "let i++" poate fi pus doar o data
    #daca elementul de retea nu raspunde la ping blah
    else
    echo $NOW,$host",INACCESIBILl!"blahblah
    fi
    let i++ # ca oricum fac asta
    #daca un fisier cu comenzi blahblah
    else
    continue
    fi
    done < $inputFile

    *cum nu se face multa procesare de intrare poate ca 'awk' e prea mult, iar 'cut' face fata
    #IFS="," #nu mai e necesar acum
    ...
    #varialbila host va contine blah
    host=`echo $line | cut -d"," -f1`
    #variabila inputCmdFile va contine blah
    inputCmdFile=`echo $line | cut -d"," -f2`

    *va trebuie un plugin ceva pentru a prezenta codul pe blog mai bine, acum arata ca un text aruncat :)

    *

    RăspundețiȘtergere
  2. Multumesc pentru idei, mai ales ca le impartasiti si altora.

    Aveti dreptate cu incrementarea variabilei i. awk mi-a venit in minte la acel moment, caci nu scriu frecvent scripturi in BASH (dar ma tratez). Intr-adevar, varianta cu cut este mult mai simpla.

    Ma voi documenta sa vad cum pot scrie textul intr-un alt mod, caci recunosc ca am fost atras de ideea de a prezenta continutul si nu forma, ceea ce e o greseala atunci cand vrei sa transmiti cunostinte noi.

    RăspundețiȘtergere
  3. Acest comentariu a fost eliminat de administratorul blogului.

    RăspundețiȘtergere