Python & Flask del 1 – Dynamisk webbsida

1022 dagar sedan senaste inlägget så kändes som det kanske var dags att ge den här sidan lite kärlek igen.. Liten uppdatering – inget CCIE-cert ännu, däremot nytt jobb som IP Specialist på Telia sedan ~1 år tillbaka. Med SDN på intåg samt att certifieringar i det stora hela börjar kännas allt mer icke-relevanta pga braindumps m.m. så vet jag inte riktigt hur jag ska göra framöver. Just nu är det mer lärande för lärandets skull.. 🙂

Har haft som sidoprojekt senaste tiden att automatisera arbetsuppgifter/-moment via (shell)script, men tänkte försöka gå över till att använda Python istället i samband med lansering av en ny scriptserver på jobbet. Så i väntan på lanseringen har jag börjat kika lite på små pythonscript, men hade även en idé att försöka lyfta ur vissa bef. script som inte kräver någon nätaccess till en webbserver istället (och samtidigt skriva dessa i python istället) för att göra det mer användarvänligt/lättillgängligt.

logo-full

Då jag ville använde mig av Python3 som “backend” verkade Flask intressant, webbservern driver jag just nu på en raspberry (rasbian)/apache2. Efter x antal timmar börjar det nu faktiskt lira helt ok så tänkte det kunde vara på sin plats att dokumentera ner det hela lite grann.

För att komma igång:

Skapar sedan en katalog för min “hemsida” under /var/www/html/ (default för debian):

$ mkdir website
# För bilder/css/js etc
$ mkdir website/static
# För html-filer
$ mkdir website/templates
$ cd website
# Skapar en Virtual enviroment för Py3 med namnet flaskenv
$ virtualenv --python=python3 flaskenv
# Aktivera VE
$ source flaskenv/bin/activate

Fungerar allt som det ska bör prompten se ut likt detta (min prompt ser kanske lite udda ut men är endast för jag skapat en symlänk mellan hemkatalogen & /var/www/html/):

(flaskenv) joco02@webdev:~/www/website$
(flaskenv) joco02@webdev:~/www/website$ python -V
Python 3.5.3
# Installera Flask:
(flaskenv) joco02@webdev:~/www/website$ pip install flask

Vi kan nu testa slänga ihop ett litet script:

(flaskenv) joco02@webdev:~/www/website$ nano test.py

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
 return 'Hello World!'

if __name__ == '__main__':
 app.run()

Starta sedan Flasks inbyggda webbserver med:

(flaskenv) joco02@webdev:~/www/website$  python test.py
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

I default-läget är dock Flasks webbserver endast nåbar från localhost, vi kan alltid verifiera mer curl om det fungerar men mer lämpligt är väl att öppna upp så du kan testa från andra enheter:

(flaskenv) joco02@webdev:~/www/website$ export LC_ALL=C.UTF-8
(flaskenv) joco02@webdev:~/www/website$ export LANG=C.UTF-8
(flaskenv) joco02@webdev:~/www/website$ export FLASK_APP=test.py
(flaskenv) joco02@webdev:~/www/website$ flask run --host=0.0.0.0
 * Serving Flask app "test"
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

# Alt. så justerar vi vår test.py enligt följande:
if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5000, debug=True)

Surfar vi nu in till vår server på port 5000 bör du mötas av en fin “Hello World!”. Men för att få det att fungera med apache2 krävs det ytterligare en hel del jobb.

Vi behöver först installera WSGI för Python3:

(flaskenv) joco02@webdev:~/www/website$ deactivate
joco02@webdev:~/www/website$ sudo apt-get install libapache2-mod-wsgi-py3
joco02@webdev:~/www/website$ # Aktivera mod_wsgi och starta om apache2
joco02@webdev:~/www/website$ a2enmod wsgi 
joco02@webdev:~/www/website$ sudo /etc/init.d/apache2 restart

Skapa sedan en Virtual Host i Apache2:

joco02@webdev:/var/www/html/website$ sudo nano /etc/apache2/sites-available/website.conf

Min fil ser ut enligt följande (observera att WSGIProcessGroup måste matcha din WSGI-fil du skapar i nästa steg):

<VirtualHost *:80>
 WSGIDaemonProcess website user=xx group=xx threads=5
 WSGIScriptAlias / /var/www/html/website/website.wsgi

ServerName x.se
 ServerAdmin x@x
 <Directory /var/www/html/website/>
 WSGIProcessGroup website
 WSGIApplicationGroup %{GLOBAL}
 WSGIScriptReloading On

Require all granted

</Directory>
 Alias /static /var/www/html/website/static
 <Directory /var/www/html/website/static/>
 Order allow,deny
 Allow from all
 </Directory>
 ErrorLog ${APACHE_LOG_DIR}/error.log
 LogLevel warn
 CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

Vi akviterar den sedan med:

sudo a2ensite website

Vi närmar oss.. Nu behövs en WSGI-fil som vi skapar i vår “website”-mapp. Efter en hel del trixande/trial & error ser min fil tillslut ut enligt följande:

joco02@webdev:/etc/apache2/sites-available$ cd /var/www/html/website/
joco02@webdev:/var/www/html/website$ nano website.wsgi


#!/usr/bin/python
import sys
import logging
import site

site.addsitedir('/var/www/website/flaskenv/lib/python3.5/site-packages')

logging.basicConfig(stream=sys.stderr)
sys.path.insert(0,"/var/www/html/website/")

from test import app as application

Starta sedan om apache och du ska *förhoppningsvis* få upp din hemsida om du surfar in på http://*serverip*, det var en del svett, blod & tårar innan jag kom såhär långt själv… 🙂

Kommer ytterligare något inlägg om hur vi bygger vidare på applikationen framöver, min egna lilla server rullar fortfarande på: www.jonascollen.se.

 

Python & SNMP

Tänkte fortsätta på samma tema med SNMP och kombinera detta med lite enklare script i Python. Är långt ifrån någon programmerare så detta är nog inte direkt någon vacker lösning, men det fungerar åtminstone. 🙂

För att göra SNMP-querys kan vi använda exempelvis biblioteken subprocess eller netsnmp enligt följande:

Subprocess

#!/usr/bin/env python
import subprocess
checkarg = sys.argv[1:]
#Checks arguments and saves to variables
if len(checkarg) == 2:
    cCommunity = checkarg[0]
    cIP = checkarg[1]
#Modelname
#Gets the Model-ID, returns numeric
cModelInt = subprocess.Popen([r"snmpwalk","-v2c","-Oqv","-c",cCommunity,cIP,"sysObjectID"],stdout=subprocess.PIPE).communicate()[0]

Alternativt netsnmp:

#!/usr/bin/env python
import subprocess
checkarg = sys.argv[1:]
#Checks arguments and saves to variables
if len(checkarg) == 2:
    cCommunity = checkarg[0]
    cIP = checkarg[1]
oid = netsnmp.Varbind('sysName.0')
cName = netsnmp.snmpget(oid, Version = 2, DestHost = cIP, Community = cCommunity)

Vi testar sedan scriptet med “./script.py community ip-adress”.

Här ett exempel där jag hämtar hem lite grundläggande information från en cisco router/switch:

#!/usr/bin/env python
import netsnmp
import subprocess
import ipaddr
import sys
import prettytable
from prettytable import PrettyTable

checkarg = sys.argv[1:]
#Checks arguments and saves to variables
if len(checkarg) == 2:
    cCommunity = checkarg[0]
    cIP = checkarg[1]
if isinstance(cCommunity, str):
    try:
        #Checks for valid IPv4-address
        ccIP = ipaddr.IPv4Address(cIP)

        try:
            #Modelname
            #Gets the Model-ID, returns numeric
            cModelInt = subprocess.Popen([r"snmpwalk","-v2c","-Oqv","-c",cCommunity,cIP,"sysObjectID"],stdout=subprocess.PIPE).communicate()[0]
            cModelSplit = cModelInt.split("::")
            cModelSplit = cModelSplit[1].rstrip()
            #Translate numeric Model-ID to string
            cModel = subprocess.Popen([r"snmptranslate","-m","CISCO-PRODUCTS-MIB","-IR",cModelSplit],stdout=subprocess.PIPE).communicate()[0]
            cModel = cModel.split("::") 

            #Name
            oid = netsnmp.Varbind('sysName.0')
            cName = netsnmp.snmpget(oid, Version = 2, DestHost = cIP, Community = cCommunity)

            #Description
            oid = netsnmp.Varbind('sysDescr.0')
            cDesc = netsnmp.snmpget(oid, Version = 2, DestHost = cIP, Community = cCommunity)
            #Output
            print ""
            print "Device info:"
            print "" 
            print "Host:", ccIP
            print "Model:", cModel[1].rstrip()
            print "Name:", cName[0]
            print cDesc[0]
	    x = PrettyTable(["Interface", "IP", "Subnet", "MAC"])
            x.align["Interface"] = "l"

	    #Index of all the interfaces
	    oid = netsnmp.Varbind("ifIndex")
	    cIndex = netsnmp.snmpwalk(oid, Version = 2, DestHost = cIP, Community = cCommunity)

	    #Index of all the interfaces with IP
	    oid = netsnmp.Varbind("ipAdEntIfIndex")
	    cIndexIP = netsnmp.snmpwalk(oid, Version = 2, DestHost = cIP, Community = cCommunity)

	    #IP-Adress list
	    oid = netsnmp.Varbind("ipAdEntAddr")    
	    ipAdd = netsnmp.snmpwalk(oid, Version = 2, DestHost = cIP, Community = cCommunity)

	    #MAC for all interfaces
	    oid = netsnmp.Varbind("ifPhysAddress")
	    cMAC = netsnmp.snmpwalk(oid, Version = 2, DestHost = sys.argv[2], Community = sys.argv[1])

	    #Subnetmask for all IP interfaces
	    oid = netsnmp.Varbind("ipAdEntNetMask")
	    cNetmask = netsnmp.snmpwalk(oid, Version = 2, DestHost = cIP, Community = cCommunity)

	    looped = 0

	    #Prints out interface-details
	    for i in cIndex:

	        #Name of every interface
	        oid = netsnmp.Varbind("ifDescr."+i)
	        cInterfacename = netsnmp.snmpget(oid, Version = 2, DestHost = cIP, Community = cCommunity)

	        try:
	            #Matches interface-list with interface+IP-list       
	            IPindex = cIndexIP.index(i)

	            try:
	                #Hex to readable
	                mac = cMAC[looped]
	                mac = ":".join( [ '%x'%(ord(c)) for c in mac ] )

	                #Prints row
	                x.addrow(cInterfacename[0],ipAdd[IPindex],cNetmask[IPindex],mac)

	            except:
		        pass
		    looped += 1

	        except:
	            try:
	                #Hex to readable
	                mac = cMAC[looped]
	                mac = ":".join( [ '%x'%(ord(c)) for c in mac ] )
	                #Prints row w/o IP
	                x.addrow(cInterfacename[0],mac)

	            except:

	                #Prints row w/o MAC & IP
	                x.addrow(cInterfacename[0])

	            looped += 1

		 except:
		     print "SNMP-polling failed"

	     except: 
                 print "Invalid IP given:", cIP
     else:
         print "Invalid Community given (must be string)"
else:
    print "Error - you must set both Community and IP (ex ./3a.py public localhost):"

Programmering/scripting kan vara väldigt skoj har jag märkt! Kan verkligen rekommendera exempelvis https://www.khanacademy.org/science/computer-science-subject/computer-science för att få en liten introduktion till programmering i just python. Till nästa inlägg tänkte jag komplettera scriptet med att spara ner informationen i en MySQL-databas eller fil istället för att endast skriva ut på skärmen.