Archiv der Kategorie: Howtos & more …

Check_MK: TG-Notify – Telegram Notification Bot with Callback for ACK & Downtime

TG-Notify ist eine von mir geschriebene Erweiterung für Check_MK welche Notifications über Telegram ermöglicht und über Callback Acknowledge und Downtimes setzen kann für Hosts/Services.

Sourcecode: https://github.com/lanbugs/tg-notify

Status/ Version: 0.1 beta

Download als MKP: folgt

Lizenz: GPLv2

Vorteile:

  • Telegram ist kostenlos
  • Telegram ist für alle gänigen Mobile und PC OS verfügbar -> https://telegram.org/apps
  • Telegram arbeitet mit einer HTTP API, unabhänig von E-Mail
  • Direkte Callbacks möglich, implementiert sind Acknowledge und Downtime -> Super für Bereitschaft wenn es nichts dringendes ist um den Schlaf nicht lange zu unterbrechen 🙂
  • Für die Callbacks muss Check_MK nicht über das Internet direkt erreichbar sein
  • mehr als 160 Zeichen möglich (vgl. SMS)
  • uvm.

Geplant / Future:

  • Eigene Ack / Downtime Texte
  • Weitere Komandos (Ideen ??)

Installation TG-Notify

TG-Notify ist in Python geschrieben und kommt mit den Standard Libraries von Python 2.7 aus die von Check_MK in der OMD Umgebung bereitgestellt werden.

Verwendete Python Libraries:

  • argparse
  • ConfigParser
  • sqlite3
  • os
  • sys
  • urllib
  • json
  • datetime
  • time
  • random
  • string

Bestandteile von TG-Notify:

  • tg_admin – Admin Tool welches auch die Callbacks einsammelt
  • tg_callback – Callback Tool welches die Notifications und Downtimes setzt
  • tg_runner – Cronjob Script
  • tg_notification_with_callback – Notification Script welches die Notifications aus Check_MK versendet
  • tg.ini – Configdatei von TG-Notify
  • tg.db – SQLite3 Datenbank welche mit dem tg_admin Tool generiert werden kann
  •  tg_collect_and_callback_agent, tg_cleanup – Cronjobs die ebenfalls mit dem tg_admin Tool generiert werden

Verzeichnisse -> Dateien:

  • OMD_ROOT/local/bin -> tg_admin und tg_callback, tg_runner
  • OMD_ROOT/local/etc -> tg.ini
  • OMD_ROOT/local/lib/tg_notify -> tg.db
  • OMD_ROOT/local/share/check_mk/notifications -> tg_notification_with_callback
  • OMD_ROOT/etc/cron.d -> tg_collect_and_callback_agent, tg_cleanup

Schritt 1: Anlegen eines Telegram Bots beim BotFather

Message an BotFather schicken mit „/newbot“, anschließend Namen definieren für neuen Bot z.B. „TGnotifyDEMO_bot“. Der BotFather schickt anschlieend den Token für die HTTP API von Telegram.

Schritt 2: Anlegen eines Automation Users in Check_MK

Damit die Callbacks ACKs und Downtimes setzen können muss ein Automation User eingerichtet werden. Der User muss als Contact Group die gleichen hanben wie die User sonst kann er keine ACKs/Downtimes setzen. Alternativ muss das Userprofil angepasst werden. Für den Automation User sind die Notifications zu deaktivieren!

Schritt 3: tg.ini anpassen

Hier ist der Token vom BotFather einzugeben und der Automation User sowie die URL zum Check_MK System.

;
; Configuration for  TG-Notify
;

[Telegram]
; URL mit /
url = https://api.telegram.org/
; Token with bot at the beginning, without / at the end
token = bot123456789:AAAAAbBbbbCCcCccDDddDdDEeEeEEEEeE-FFF

[Database]
; Relative to OMD_ROOT without / at the beginning
path = local/lib/tg_notify/
file = tg.db

[Check_MK]
url = http://localhost/dev1/check_mk/
automation_user = a_test
automation_pass = ATPQTVBKJYGSDUAHYDQF

Schritt 4: Datenbank generieren

Die Funktion ist in tg_admin eingebaut, dazu einfach „tg_admin –initialize-database“ aufrufen.

OMD[dev1]:~$ tg_admin --initialize-database

Schritt 5: Crontab generieren

Die Funktion ist in tg_admin eingebaut, dazu einfach „tg_admin –generate-cronjobs“ aufrufen.

OMD[dev1]:~$ tg_admin --create-cronjobs

Schritt 6: Neuen User anlegen in TG-Notify

TG-Notify hat seine eigene Userdatenbank, es müssen die Check_MK User <-> Chat_ID von Telegram TG-Notify erst bekannt gemacht werden.

Dazu vom Handy ein „open“ an den Bot senden und das Kommando „tg_admin –show-agent“ aufrufen. Es wird die entsprechende Chat_ID angezeigt die der User hat. Anschließend den User mit „tg_admin -c -u hdampf -i 123456789“ anlegen. Aktuelle User können mit „tg_admin -s“ abgefragt werden.

OMD[dev1]:~/local/etc$ tg_admin --show-agent
----------------------------------------------------------------
First Name: Hans
Last Name: Dampf
CHAT ID: 123456789
----------------------------------------------------------------

OMD[dev1]:~/local/etc$ tg_admin -c -u hdampf -i 319651791

OMD[dev1]:~/local/etc$ tg_admin -s
Current User:

Username    Chat_ID     
------------------------
hdampf      123456789   

Schritt 7: Neue Notification Rule erzeugen für TG-Notify

Nach der Installation von TG-Notify umbedingt einen „omd restart“ durchführen damit das neue Notifications Script gefunden wird von Check_MK. Die Regel wie gewohnt erstellen, hier im Beispiel ist es eine sehr einfach gehaltene, es sind alle bekannten Optionen möglich.

Fertig! Erste Gehversuche …

Ich habe in meinem Testsystem Hosts (10.44.4.5, 10.44.4.6) angelegt welche es nicht gibt um Meldungen zu generieren. Mit „tg_admin –show-notify-history“ kann man sich die letzten Notifications ansehen. Für jede Notification wird eine Callback_ID generiert. Die ID dient dazu wenn vom Telegram Client ein Callback aufgeführt wird z.B. Acknowledge eines Services den Callback eindeutig zu identifizieren.

Im Callback wird dann z.B. $$$CB$$$WCA38KSKU9XTODCO:ack gesendet für den Acknowledge von Host 10.44.4.5 / Service PING.

OMD[dev1]:~$ tg_admin --show-notify-history
ID  Callback ID         Host                Service                                 Datum               Chat_ID   Username  
----------------------------------------------------------------------------------------------------------------------------------
232 WCA38KSKU9XTODCO    10.44.4.5           PING                                    2017-08-21 13:01:50 123456789 cmkadmin  
233 T46ETYJ1CH7MV2ZS    10.44.4.5           Check_MK Discovery                      2017-08-21 13:01:54 123456789 cmkadmin  
234 ADZJPKLMO0SV63WU    10.44.4.5           None                                    2017-08-21 13:02:02 123456789 cmkadmin  
235 4WLYX43CGRJJQNHX    10.44.4.4           None                                    2017-08-21 13:12:13 123456789 cmkadmin  
236 6OMRDN6DQ1KAIWY4    10.44.4.5           None                                    2017-08-21 13:12:16 123456789 cmkadmin  
237 BUQ0HEAKPTFMLPED    10.44.4.5           Check_MK Discovery                      2017-08-21 13:31:11 123456789 cmkadmin  
238 3QP6YL48RNWGJV4V    10.44.4.5           PING                                    2017-08-21 13:40:00 123456789 cmkadmin  
239 7BKRA2ATVRHESFWH    10.44.4.5           None                                    2017-08-21 13:46:14 123456789 cmkadmin  
240 1MZ2X50KJ55ZXA2W    10.44.4.4           None                                    2017-08-21 13:46:16 123456789 cmkadmin  
241 N3RUH9EGYGJ314UU    10.44.4.6           PING                                    2017-08-21 13:47:16 123456789 cmkadmin  
242 0273U5QDMDH2RFQA    10.44.4.6           Check_MK Discovery                      2017-08-21 13:47:18 123456789 cmkadmin  
243 JZFJEUYOZAJ7U0VF    10.44.4.6           None                                    2017-08-21 13:47:29 123456789 cmkadmin  
244 0YSN5ZG4DW80NGT1    10.44.4.6           PING                                    2017-08-21 13:48:13 123456789 cmkadmin  
245 D436YY9GE3MLH00W    10.44.4.6           Check_MK Discovery                      2017-08-21 13:48:14 123456789 cmkadmin  

Die empfangenen Callbacks werden in der SQL Datenbank gespeichert und werden hier von dem Programm tg_callback verarbeitet und die entsprechenden Aufrufe gegen Check_MK durchgeführt. Mit „tg_admin –show-callbacks“ lassen sich die angeforderten Callbacks und der Status anzeigen.

OMD[dev1]:~$ tg_admin --show-callbacks
Callback ID         Host                Service                                 Datum               Command   Executed
----------------------------------------------------------------------------------------------------------------------------------
OWQML2LFDZM8LIB3    localhost           Interface 1                             2017-08-18 23:58:49 ack       1 
C6V7O11LAF65F5Q5    localhost           Check_MK Discovery                      2017-08-20 12:23:13 ack       1 
P81F439ZX54ET4SG    10.44.4.4           None                                    2017-08-20 12:53:15 ack       1 
CQHDP8OJFE4VTNHE    10.44.4.4           None                                    2017-08-20 13:04:06 ack       1 
ZZ3OAVP41OPWE1PG    localhost           OMD dev1 Notification Spooler           2017-08-20 13:08:46 ack       1 
2XKFHEVFGLB389QX    10.44.4.4           None                                    2017-08-20 13:34:00 down24h   1 
916IUVV6O66GBDUA    10.44.4.4           None                                    2017-08-20 13:49:45 ack       1 
916IUVV6O66GBDUA    10.44.4.4           None                                    2017-08-20 13:49:46 down24h   1 
ZPAZ4OJ63CJ7IEM7    localhost           OMD dev1 Notification Spooler           2017-08-21 11:57:28 ack       1 
ZPAZ4OJ63CJ7IEM7    localhost           OMD dev1 Notification Spooler           2017-08-21 11:57:29 down24h   1 
ADZJPKLMO0SV63WU    10.44.4.5           None                                    2017-08-21 13:12:02 ack       1 
T46ETYJ1CH7MV2ZS    10.44.4.5           Check_MK Discovery                      2017-08-21 13:31:02 ack       1 
WCA38KSKU9XTODCO    10.44.4.5           PING                                    2017-08-21 13:39:49 down24h   1 
ADZJPKLMO0SV63WU    10.44.4.5           None                                    2017-08-21 13:46:04 down24h   1 
N3RUH9EGYGJ314UU    10.44.4.6           PING                                    2017-08-21 13:48:02 ack       1 
0273U5QDMDH2RFQA    10.44.4.6           Check_MK Discovery                      2017-08-21 13:48:03 down24h   1 

Die ersten Notifications sind am Client angekommen:

Jeder einzelne Service lässt sich ACKen oder für 24h auf Downtime setzen. Klickt man die Buttons an dauert es max. 1 Minute und TG-Notify anwortet das er den Callback empfangen hat.

 

 

 

 

Commandline Tool for exporting Cisco hardware inventory via SNMP

This tool exports every hardware asset from an Cisco device with a serial number. You can export the list as table or CSV.

Download: https://gist.github.com/lanbugs/4dbed5e0e8a7d5b6d29c4ea9b9e93bb2

>python cisco_inventory.py -h
usage: cisco_inventory.py [-h] -H HOST -v SNMP_VERSION [-C SNMP_COMMUNITY]
                          [-u SNMP_USER] [-A SNMP_AUTH] [-a SNMP_AUTH_METHOD]
                          [-X SNMP_PRIVACY] [-x SNMP_PRIVACY_METHOD]
                          [-L SNMP_SECURITY] [--csv]

Cisco inventory grabber Version 0.1 Written by Maximilian Thoma 2018

optional arguments:
  -h, --help            show this help message and exit
  -H HOST               WLC IP address
  -v SNMP_VERSION       SNMP version, valid are 2 or 3
  -C SNMP_COMMUNITY     SNMP Community (only v2)
  -u SNMP_USER          SNMP user (v3)
  -A SNMP_AUTH          SNMP auth password (v3)
  -a SNMP_AUTH_METHOD   SNMP auth method, valid are MD5 or SHA (v3)
  -X SNMP_PRIVACY       SNMP privacy password (v3)
  -x SNMP_PRIVACY_METHOD
                        SNMP privacy method, valid are AES or DES (v3)
  -L SNMP_SECURITY      SNMP security level, valid are no_auth_or_privacy,
                        auth_without_privacy or auth_with_privacy (v3)
  --csv                 Result should be CSV

Demo;

>python cisco_inventory.py -H 10.10.10.33 -v 3 -u snmpuser -A snmpauth -a MD5 -X snmpencr -x DES -L auth_with_privacy
| Description                                            | Class       | Name                                   | HWRev   | FWRev       | SWRev      | Serial           | Manufactor          | Model          | FRU?   |
|--------------------------------------------------------+-------------+----------------------------------------+---------+-------------+------------+------------------+---------------------+----------------+--------|
| 1000BaseSX                                             | -           | GigabitEthernet1/4/12                  | V01     |             |            | FNS11111111      | CISCO               | GLC-SX-MMD     | true   |
| 1000BaseSX                                             | -           | GigabitEthernet1/4/11                  | V01     |             |            | FNS11111111      | CISCO               | GLC-SX-MMD     | true   |
| 1000BaseSX                                             | -           | GigabitEthernet1/4/10                  | V01     |             |            | FNS11111111      | CISCO               | GLC-SX-MMD     | true   |
| 1000BaseSX                                             | -           | GigabitEthernet1/4/11                  | V01     |             |            | FNS11111111      | CISCO               | GLC-SX-MMD     | true   |
| 1000BaseSX                                             | -           | GigabitEthernet1/3/8                   | V01     |             |            | FNS11111111      | CISCO               | GLC-SX-MMD     | true   |
| 1000BaseSX                                             | -           | GigabitEthernet1/3/9                   | V01     |             |            | FNS11111111      | CISCO               | GLC-SX-MMD     | true   |
| 1000BaseSX                                             | -           | GigabitEthernet1/3/6                   | V01     |             |            | FNS11111111      | CISCO               | GLC-SX-MMD     | true   |
| 1000BaseSX                                             | -           | GigabitEthernet1/3/7                   | V01     |             |            | FNS11111111      | CISCO               | GLC-SX-MMD     | true   |
| 1000BaseSX                                             | -           | GigabitEthernet2/3/10                  | V01     |             |            | FNS11111111      | CISCO               | GLC-SX-MMD     | true   |
| 1000BaseX (SFP) with 12 SFP Ports Jumbo Frame Support  | module      | Switch1 Linecard 4 (virtual slot 4)    | V02     |             |            | FNS11111111      | Cisco               | WS-X4612-SFP-E | true   |
| 1000BaseX (SFP) with 12 SFP Ports Jumbo Frame Support  | module      | Switch1 Linecard 3 (virtual slot 3)    | V02     |             |            | FNS11111111      | Cisco               | WS-X4612-SFP-E | true   |
| Cisco Systems, Inc. WS-C4506-E 6 slot switch           | chassis     | Switch1 System                         | V03     |             |            | FNS11111111      | Cisco               | WS-C4506-E     | false  |
| FanTray                                                | fan         | Switch2 FanTray 1                      | V04     |             |            | FNS11111111      | Cisco               | WS-X4596-E     | true   |
| Power Supply ( AC 1400W )                              | powerSupply | Switch2 Power Supply 1                 | V04     |             |            | FNS11111111      | Cisco Systems, Inc. | PWR-C45-1400AC | true   |
| Power Supply ( AC 1400W )                              | powerSupply | Switch2 Power Supply 2                 | V04     |             |            | FNS11111111      | Cisco Systems, Inc. | PWR-C45-1400AC | true   |
| SFP-10Gbase-SR                                         | -           | TenGigabitEthernet1/1/1                | V03     |             |            | FNS11111111      | CISCO-FINISAR       | SFP-10G-SR     | true   |
| SFP-10Gbase-SR                                         | -           | TenGigabitEthernet1/1/2                | V03     |             |            | FNS11111111      | CISCO-FINISAR       | SFP-10G-SR     | true   |
| Sup 7L-E 10GE (SFP+), 1000BaseX (SFP) with 4 SFP Ports | module      | Switch1 Supervisor 1 (virtual slot 1)  | V01     | 15.0(1r)SG3 | 03.06.03.E | FNS11111111      | Cisco               | WS-X45-SUP7L-E | true   |

 

Script:

#!/usr/bin/env python

# Need following pip packages
# - easysnmp
# - tabulate

# Checkout blog article to tool
# https://lanbugs.de/netzwerktechnik/hersteller/cisco/commandline-tool-for-exporting-cisco-hardware-inventory-via-snmp/


from easysnmp import Session
import argparse
from tabulate import tabulate
from operator import itemgetter
from pprint import pprint

def main():
    ####
    # ARGS
    ####
    description = """
    Cisco inventory grabber\nVersion 0.1\nWritten by Maximilian Thoma 2018
    """

    aparser = argparse.ArgumentParser(description=description)
    aparser.add_argument('-H', dest='host', help='WLC IP address', required=True)
    aparser.add_argument('-v', dest='snmp_version', help='SNMP version, valid are 2 or 3', required=True)
    aparser.add_argument('-C', dest='snmp_community', help='SNMP Community (only v2)')
    aparser.add_argument('-u', dest='snmp_user', help='SNMP user (v3)')
    aparser.add_argument('-A', dest='snmp_auth', help='SNMP auth password (v3)')
    aparser.add_argument('-a', dest='snmp_auth_method', help='SNMP auth method, valid are MD5 or SHA (v3)')
    aparser.add_argument('-X', dest='snmp_privacy', help='SNMP privacy password (v3)')
    aparser.add_argument('-x', dest='snmp_privacy_method', help='SNMP privacy method, valid are AES or DES (v3)')
    aparser.add_argument('-L', dest='snmp_security',
                         help='SNMP security level, valid are no_auth_or_privacy, auth_without_privacy or auth_with_privacy (v3)')
    aparser.add_argument('--csv', dest='csv', help='Result should be CSV', action='store_true')
    args = aparser.parse_args()

    ####
    # Setup SNMP connection
    ####

    if args.snmp_version == "2":
        try:
            snmp = Session(hostname=args.host, version=2, use_numeric=True)

        except Exception as e:
            print e

    if args.snmp_version == "3":
        try:
            snmp = Session(
                hostname=args.host,
                version=3,
                security_level=args.snmp_security,
                security_username=args.snmp_user,
                auth_protocol=args.snmp_auth_method,
                auth_password=args.snmp_auth,
                privacy_protocol=args.snmp_privacy_method,
                privacy_password=args.snmp_privacy,
                use_numeric=True
            )

        except Exception as e:
            print e

    ####
    # Init Data Buffer
    ####

    inventory = {}
    inv_print = []

    ####
    # SNMP Walk inventory
    ####

    port = {
    0: "-",
    1: "other",
    2: "unknown",
    3: "chassis",
    4: "backplane",
    5: "container",
    6: "powerSupply",
    7: "fan",
    8: "sensor",
    9: "module",
    10: "port",
    11: "stack",
    12: "cpu"
    
    }

    ## Get inventory
    result_ids = snmp.walk(".1.3.6.1.2.1.47.1.1.1.1")

    def stripper(string):
        if "NoneType" not in str(type(string)):
            return string.strip()
        else:
            return string


    for r in result_ids:
        
        if r.oid_index in inventory:
            element_id = r.oid.replace(".1.3.6.1.2.1.47.1.1.1.1.","")

            if element_id == "16": # fru
                fru = "true" if "1" in r.value else "false"
                inventory[r.oid_index][element_id] = fru

            elif element_id == "5": # class
                classx = port[int(r.value)] if len(r.value) is 1 else port[0]
                inventory[r.oid_index][element_id] = classx

            else: # everything else
                inventory[r.oid_index][element_id] = r.value
            
        else:
            element_id = r.oid.replace(".1.3.6.1.2.1.47.1.1.1.1.","")
            inventory[r.oid_index] = {}
            inventory[r.oid_index][element_id] = r.value
        
    
    
    for elements, values in inventory.iteritems():
        
        if len(values['11']) >= 1:        
            #print elements
            
            #2 entPhysicalDescr
            #3 entPhysicalVendorType
            #4 entPhysicalContainedIn
            #5 entPhysicalClass
            #6 entPhysicalParentRelPos
            #7 entPhysicalName
            #8 entPhysicalHardwareRev
            #9 entPhysicalFirmwareRev
            #10 entPhysicalSoftwareRev
            #11 entPhysicalSerialNum
            #12 entPhysicalMfgName
            #13 entPhysicalModelName
            #14 entPhysicalAlias
            #15 entPhysicalAssetID
            #16 entPhysicalIsFRU
            
            inv_print.append([stripper(values.get('2')),
                              stripper(values.get('5')),
                              stripper(values.get('7')),
                              stripper(values.get('8')),
                              stripper(values.get('9')),
                              stripper(values.get('10')),
                              stripper(values.get('11')),
                              stripper(values.get('12')),
                              stripper(values.get('13')),
                              stripper(values.get('16'))])



    ####
    # Sort table
    ####

    inv = sorted(inv_print, key=itemgetter(0))

    ####
    # Result
    ####

    if args.csv is True:

        print 'Description;Class;Name;HWRev;FWRev;SWRev;Serial;Manufactor;Model;FRU?'
        
        for line in inv:
            print ';'.join(str(l) for l in line)

    else:
        print tabulate(inv, headers=['Description', 'Class', 'Name', 'HWRev', 'FWRev', 'SWRev', 'Serial', 'Manufactor', 'Model', 'FRU?'], tablefmt="orgtbl")


if __name__ == "__main__":
    main()

 

Commandline Tool to export current registered users at APs from an Cisco Wireless LAN Controller (WLC)

This tool exports all current clients on all APs assigned to the WLC. You can export as CSV or as table.

Download: https://gist.github.com/lanbugs/6dc874ad0736424c5c7808f01ec78f96

$ python cisco_ap_clients_grabber.py -h
usage: cisco_ap_clients_grabber.py [-h] -H HOST -v SNMP_VERSION
                                   [-C SNMP_COMMUNITY] [-u SNMP_USER]
                                   [-A SNMP_AUTH] [-a SNMP_AUTH_METHOD]
                                   [-X SNMP_PRIVACY] [-x SNMP_PRIVACY_METHOD]
                                   [-L SNMP_SECURITY] [--csv]

Cisco AP clients grabber Version 0.1 Written by Maximilian Thoma 2017

optional arguments:
  -h, --help            show this help message and exit
  -H HOST               WLC IP address
  -v SNMP_VERSION       SNMP version, valid are 2 or 3
  -C SNMP_COMMUNITY     SNMP Community (only v2)
  -u SNMP_USER          SNMP user (v3)
  -A SNMP_AUTH          SNMP auth password (v3)
  -a SNMP_AUTH_METHOD   SNMP auth method, valid are MD5 or SHA (v3)
  -X SNMP_PRIVACY       SNMP privacy password (v3)
  -x SNMP_PRIVACY_METHOD
                        SNMP privacy method, valid are AES or DES (v3)
  -L SNMP_SECURITY      SNMP security level, valid are no_auth_or_privacy,
                        auth_without_privacy or auth_with_privacy (v3)
  --csv                 Result should be CSV

Demo:

>python cisco_ap_clients_grabber.py -H 1.1.1.1 -v 3 -u username -A authpass -a MD5 -X privpass -x DES -L auth_with_privacy
| IP            | MAC               | Name                               | SSID         | SSID MAC          |
|---------------+-------------------+------------------------------------+--------------+-------------------|
| 10.10.10.1    | C0:FF:EE:C0:FF:EE | host/client0001.m.local            | workstations | DE:AD:BE:EF:00:00 |
| 10.10.10.2    | C0:FF:EE:C0:FF:EE | host/client0002.m.local            | workstations | DE:AD:BE:EF:00:00 |
| 10.10.10.3    | C0:FF:EE:C0:FF:EE | host/client0003.m.local            | workstations | DE:AD:BE:EF:00:00 |
| 10.10.10.4    | C0:FF:EE:C0:FF:EE | host/client0004.m.local            | workstations | DE:AD:BE:EF:00:00 |
| 10.10.10.5    | C0:FF:EE:C0:FF:EE | host/client0005.m.local            | workstations | DE:AD:BE:EF:00:00 |
| 10.10.45.200  | C0:FF:EE:C0:FF:EE |                                    | guests       | DE:AD:BE:EF:00:00 |
| 10.10.45.201  | C0:FF:EE:C0:FF:EE |                                    | guests       | DE:AD:BE:EF:00:00 |
| 10.10.45.202  | C0:FF:EE:C0:FF:EE |                                    | guests       | DE:AD:BE:EF:00:00 |
| 10.10.45.203  | C0:FF:EE:C0:FF:EE |                                    | guests       | DE:AD:BE:EF:00:00 |
| 10.10.45.204  | C0:FF:EE:C0:FF:EE |                                    | guests       | DE:AD:BE:EF:00:00 |
| 10.10.45.205  | C0:FF:EE:C0:FF:EE |                                    | guests       | DE:AD:BE:EF:00:00 |
| 10.10.45.206  | C0:FF:EE:C0:FF:EE |                                    | guests       | DE:AD:BE:EF:00:00 |

Script:

#!/usr/bin/env python

# Need following pip packages
# - easysnmp
# - tabulate

# Checkout blog article to tool
# https://lanbugs.de/netzwerktechnik/commandline-tool-to-export-current-registered-users-at-aps-from-an-cisco-wireless-lan-controller-wlc/

from easysnmp import Session
import argparse
from tabulate import tabulate
from operator import itemgetter


def main():
    ####
    # ARGS
    ####
    description = """
    Cisco AP clients grabber\nVersion 0.1\nWritten by Maximilian Thoma 2018
    """

    aparser = argparse.ArgumentParser(description=description)
    aparser.add_argument('-H', dest='host', help='WLC IP address', required=True)
    aparser.add_argument('-v', dest='snmp_version', help='SNMP version, valid are 2 or 3', required=True)
    aparser.add_argument('-C', dest='snmp_community', help='SNMP Community (only v2)')
    aparser.add_argument('-u', dest='snmp_user', help='SNMP user (v3)')
    aparser.add_argument('-A', dest='snmp_auth', help='SNMP auth password (v3)')
    aparser.add_argument('-a', dest='snmp_auth_method', help='SNMP auth method, valid are MD5 or SHA (v3)')
    aparser.add_argument('-X', dest='snmp_privacy', help='SNMP privacy password (v3)')
    aparser.add_argument('-x', dest='snmp_privacy_method', help='SNMP privacy method, valid are AES or DES (v3)')
    aparser.add_argument('-L', dest='snmp_security',
                         help='SNMP security level, valid are no_auth_or_privacy, auth_without_privacy or auth_with_privacy (v3)')
    aparser.add_argument('--csv', dest='csv', help='Result should be CSV', action='store_true')
    args = aparser.parse_args()

    ####
    # Setup SNMP connection
    ####

    if args.snmp_version == "2":
        try:
            snmp = Session(hostname=args.host, version=2, use_numeric=True)

        except Exception as e:
            print e

    if args.snmp_version == "3":
        try:
            snmp = Session(
                hostname=args.host,
                version=3,
                security_level=args.snmp_security,
                security_username=args.snmp_user,
                auth_protocol=args.snmp_auth_method,
                auth_password=args.snmp_auth,
                privacy_protocol=args.snmp_privacy_method,
                privacy_password=args.snmp_privacy,
                use_numeric=True
            )

        except Exception as e:
            print e

    ####
    # Init Data Buffer
    ####

    inventory = []
    longids = []

    ####
    # SNMP Walk Clients inventory
    ####

    ## Get longids for clients
    result_longids = snmp.walk(".1.3.6.1.4.1.14179.2.1.4.1.1")

    for rl in result_longids:
        longids.append(rl.oid.replace(".1.3.6.1.4.1.14179.2.1.4.1.1.", "") + "." + rl.oid_index)

    ## Collect informations

    for id in longids:
        # Client MAC
        result_mac = snmp.get(".1.3.6.1.4.1.14179.2.1.4.1.1." + id)
        mac = ":".join(["%02s" % hex(ord(m))[2:] for m in result_mac.value]).replace(' ', '0').upper()

        # Client Name
        name = snmp.get(".1.3.6.1.4.1.14179.2.1.4.1.3." + id).value

        # Client IP
        ip = snmp.get(".1.3.6.1.4.1.14179.2.1.4.1.2." + id).value

        # SSID
        ssid = snmp.get(".1.3.6.1.4.1.14179.2.1.4.1.7." + id).value

        # SSID MAC
        result_ssid_mac = snmp.get(".1.3.6.1.4.1.14179.2.1.4.1.4." + id)
        ssid_mac = ":".join(["%02s" % hex(ord(m))[2:] for m in result_ssid_mac.value]).replace(' ', '0').upper()

        inventory.append([ip, mac, name, ssid, ssid_mac])

    ###
    # Sort table
    ####

    inv = sorted(inventory, key=itemgetter(0))

    ####
    # Result
    ####

    if args.csv is True:

        print 'IP;MAC;Name;SSID;SSID MAC'

        for ip, mac, name, ssid, ssid_mac in inv:
            print '%s;%s;%s;%s;%s' % (ip, mac, name, ssid, ssid_mac)

    else:
        print tabulate(inv, headers=["IP", "MAC", "Name", "SSID", "SSID MAC"], tablefmt="orgtbl")


if __name__ == "__main__":
    main()

 

 

CLI Tool – Export AP inventory from an Cisco Wireless LAN Controller (WLC)

This tool exports the complete AP inventory from an Cisco WLC. You can create an CSV export or an table.

Download: https://gist.github.com/lanbugs/e86042c0b2afaf7166637a9aa9711cb6

$ python cisco_wlc_ap_grabber.py -h
usage: cisco_wlc_ap_grabber.py [-h] -H HOST -v SNMP_VERSION
                               [-C SNMP_COMMUNITY] [-u SNMP_USER]
                               [-A SNMP_AUTH] [-a SNMP_AUTH_METHOD]
                               [-X SNMP_PRIVACY] [-x SNMP_PRIVACY_METHOD]
                               [-L SNMP_SECURITY] [--csv]

Cisco AP WLC inventory grabber Version 0.1 Written by Maximilian Thoma 2017

optional arguments:
  -h, --help            show this help message and exit
  -H HOST               WLC IP address
  -v SNMP_VERSION       SNMP version, valid are 2 or 3
  -C SNMP_COMMUNITY     SNMP Community (only v2)
  -u SNMP_USER          SNMP user (v3)
  -A SNMP_AUTH          SNMP auth password (v3)
  -a SNMP_AUTH_METHOD   SNMP auth method, valid are MD5 or SHA (v3)
  -X SNMP_PRIVACY       SNMP privacy password (v3)
  -x SNMP_PRIVACY_METHOD
                        SNMP privacy method, valid are AES or DES (v3)
  -L SNMP_SECURITY      SNMP security level, valid are no_auth_or_privacy,
                        auth_without_privacy or auth_with_privacy (v3)
  --csv                 Result should be CSV

Demo result:

>python cisco_wlc_ap_grabber.py -H 1.1.1.1 -v 3 -u username -A authpass -a MD5 -X privpass -x DES -L auth_with_privacy

| Name        | IP            | MAC               | Model             | Serialnumber   |
|-------------+---------------+-------------------+-------------------+----------------|
| dexxx-acp01 | 10.10.100.1   | 64:F6:9D:C0:FF:EE | AIR-CAP1702I-E-K9 | FCZ11111111    |
| dexxx-acp02 | 10.11.100.2   | 58:97:1E:C0:FF:EE | AIR-LAP1142N-E-K9 | FCZ11111111    |
| dexxx-acp03 | 10.11.100.3   | 34:DB:FD:C0:FF:EE | AIR-CAP1602I-E-K9 | FGL11111111    |
| dexxx-acp04 | 10.11.100.4   | 58:97:1E:C0:FF:EE | AIR-LAP1142N-E-K9 | FCZ11111111    |
| dexxx-acp05 | 10.11.100.5   | 64:F6:9D:C0:FF:EE | AIR-CAP1702I-E-K9 | FCZ11111111    |
| dexxx-acp06 | 10.11.100.6   | 40:F4:EC:C0:FF:EE | AIR-LAP1142N-E-K9 | FCZ11111111    |
| dexxx-acp07 | 10.11.100.7   | 0C:27:24:C0:FF:EE | AIR-CAP1602I-E-K9 | FGL11111111    |
| dexxx-acp08 | 10.11.100.8   | 00:78:88:C0:FF:EE | AIR-CAP1702I-E-K9 | FCZ11111111    |
| dexxx-acp09 | 10.11.100.9   | 40:F4:EC:C0:FF:EE | AIR-LAP1142N-E-K9 | FCZ11111111    |
| dexxx-acp10 | 10.11.100.10  | 44:AD:D9:C0:FF:EE | AIR-CAP1602I-E-K9 | FGL11111111    |
| dexxx-acp11 | 10.11.100.11  | 50:0F:80:C0:FF:EE | AIR-AP2802I-E-K9  | FDW11111111    |
| dexxx-acp12 | 10.11.100.12  | 74:A0:2F:C0:FF:EE | AIR-CAP1702I-E-K9 | FCZ11111111    |
| dexxx-acp13 | 10.11.100.13  | 0C:85:25:C0:FF:EE | AIR-LAP1142N-E-K9 | FCZ11111111    |
| dexxx-acp14 | 10.11.100.14  | 74:A0:2F:C0:FF:EE | AIR-CAP1702I-E-K9 | FCZ11111111    |
| dexxx-acp15 | 10.11.100.15  | 64:F6:9D:C0:FF:EE | AIR-CAP1702I-E-K9 | FCZ11111111    |
| dexxx-acp16 | 10.11.100.16  | C0:62:6B:C0:FF:EE | AIR-LAP1142N-E-K9 | FCZ11111111    |
| dexxx-acp17 | 10.11.100.17  | 0C:27:24:C0:FF:EE | AIR-CAP1602I-E-K9 | FGL11111111    |
| dexxx-acp18 | 10.11.100.18  | CC:16:7E:C0:FF:EE | AIR-CAP1702I-E-K9 | FCW11111111    |
| dexxx-acp19 | 10.11.100.19  | 64:F6:9D:C0:FF:EE | AIR-CAP1702I-E-K9 | FCZ11111111    |
| dexxx-acp20 | 10.11.100.20  | 64:F6:9D:C0:FF:EE | AIR-CAP1702I-E-K9 | FCZ11111111    |
| dexxx-acp21 | 10.11.100.21  | 64:F6:9D:C0:FF:EE | AIR-CAP1702I-E-K9 | FCZ11111111    |
| dexxx-acp22 | 10.11.100.22  | E4:AA:5D:C0:FF:EE | AIR-CAP1702I-E-K9 | FCZ11111111    |
| dexxx-acp23 | 10.11.100.23  | F4:CF:E2:C0:FF:EE | AIR-CAP1702I-E-K9 | FCZ11111111    |
| dexxx-acp24 | 10.11.100.24  | 00:FE:C8:C0:FF:EE | AIR-CAP1702I-E-K9 | FCZ11111111    |
| dexxx-acp25 | 10.11.100.25  | F4:CF:E2:C0:FF:EE | AIR-CAP1702I-E-K9 | FCZ11111111    |
| dexxx-acp26 | 10.11.100.26  | 00:FE:C8:C0:FF:EE | AIR-CAP1702I-E-K9 | FCZ11111111    |
| dexxx-acp27 | 10.11.100.27  | 64:F6:9D:C0:FF:EE | AIR-CAP1702I-E-K9 | FCZ11111111    |
| dexxx-acp28 | 10.11.100.28  | 64:F6:9D:C0:FF:EE | AIR-CAP1702I-E-K9 | FCZ11111111    |
| dexxx-acp29 | 10.11.100.29  | 64:F6:9D:C0:FF:EE | AIR-CAP1702I-E-K9 | FCZ11111111    |

Script:

#!/usr/bin/env python

# Need following pip packages
# - easysnmp
# - tabulate

# Checkout blog article to tool
# https://lanbugs.de/netzwerktechnik/hersteller/cisco/cli-tool-export-ap-inventory-from-an-cisco-wireless-lan-controller-wlc/


from easysnmp import Session
import argparse
from tabulate import tabulate
from operator import itemgetter


def main():
    ####
    # ARGS
    ####
    description = """
    Cisco AP WLC inventory grabber\nVersion 0.1\nWritten by Maximilian Thoma 2017
    """

    aparser = argparse.ArgumentParser(description=description)
    aparser.add_argument('-H', dest='host', help='WLC IP address', required=True)
    aparser.add_argument('-v', dest='snmp_version', help='SNMP version, valid are 2 or 3', required=True)
    aparser.add_argument('-C', dest='snmp_community', help='SNMP Community (only v2)')
    aparser.add_argument('-u', dest='snmp_user', help='SNMP user (v3)')
    aparser.add_argument('-A', dest='snmp_auth', help='SNMP auth password (v3)')
    aparser.add_argument('-a', dest='snmp_auth_method', help='SNMP auth method, valid are MD5 or SHA (v3)')
    aparser.add_argument('-X', dest='snmp_privacy', help='SNMP privacy password (v3)')
    aparser.add_argument('-x', dest='snmp_privacy_method', help='SNMP privacy method, valid are AES or DES (v3)')
    aparser.add_argument('-L', dest='snmp_security',
                         help='SNMP security level, valid are no_auth_or_privacy, auth_without_privacy or auth_with_privacy (v3)')
    aparser.add_argument('--csv', dest='csv', help='Result should be CSV', action='store_true')
    args = aparser.parse_args()

    ####
    # Setup SNMP connection
    ####

    if args.snmp_version == "2":
        try:
            snmp = Session(hostname=args.host, version=2, use_numeric=True)

        except Exception as e:
            print e

    if args.snmp_version == "3":
        try:
            snmp = Session(
                hostname=args.host,
                version=3,
                security_level=args.snmp_security,
                security_username=args.snmp_user,
                auth_protocol=args.snmp_auth_method,
                auth_password=args.snmp_auth,
                privacy_protocol=args.snmp_privacy_method,
                privacy_password=args.snmp_privacy,
                use_numeric=True
            )

        except Exception as e:
            print e

    ####
    # Init Data Buffer
    ####

    inventory = []
    longids = []

    ####
    # SNMP Walk AP Inventory
    ####

    ## Get longids for APs
    result_longids = snmp.walk(".1.3.6.1.4.1.14179.2.2.1.1.1")

    for rl in result_longids:
        longids.append(rl.oid.replace(".1.3.6.1.4.1.14179.2.2.1.1.1.", "") + "." + rl.oid_index)

    ## Collect informations

    for id in longids:
        # MAC
        result_mac = snmp.get(".1.3.6.1.4.1.14179.2.2.1.1.1." + id)
        mac = ":".join(["%02s" % hex(ord(m))[2:] for m in result_mac.value]).replace(' ', '0').upper()

        # Name
        name = snmp.get(".1.3.6.1.4.1.14179.2.2.1.1.3." + id).value

        # IP
        ip = snmp.get(".1.3.6.1.4.1.14179.2.2.1.1.19." + id).value

        # SN
        sn = snmp.get(".1.3.6.1.4.1.14179.2.2.1.1.17." + id).value

        # Model
        model = snmp.get(".1.3.6.1.4.1.14179.2.2.1.1.16." + id).value

        inventory.append([name, ip, mac, model, sn])

    ###
    # Sort table
    ####

    inv = sorted(inventory, key=itemgetter(0))

    ####
    # Result
    ####

    if args.csv is True:

        print 'Name;IP;MAC;Model;Serialnumber'

        for name, ip, mac, model, sn in inv:
            print '%s;%s;%s;%s;%s' % (name, ip, mac, model, sn)

    else:
        print tabulate(inv, headers=["Name", "IP", "MAC", "Model", "Serialnumber"], tablefmt="orgtbl")


if __name__ == "__main__":
    main()

 

 

PowerDNS MongoDB Backend

This a pipe backend for PowerDNS to MongoDB written in Python.

Requirements

  • pymongo python library
  • powerdns min. version 4.x

The MongoDB schema (for example DB: dns / Collection: records)

For SOA records:

{
"name":"example.org",
"type":"SOA",
"content":"",
"ttl": 300,
"primary": "ns1.example.org",
"mail": "admin.example.org",
"serial": 2018030311,
"refresh": 86400,
"retry": 7200,
"expire": 3600000,
"nttl": 3600 
}


For standard records:

{
"name":"www.example.org",
"type":"A",
"ttl": 300,
"content": "1.1.1.1"
}

Install PowerDNS

apt install pdns-server pdns-backend-pipe

Install pymongo Library

pip install pymongo

The Backend Script (for example: /opt/pdns/backend.py)

You can download it from GitHub – https://github.com/lanbugs/powerdns_mongodb_backend

Parts of the code based on: https://gist.github.com/sokratisg/10069682

#!/usr/bin/env python

import sys
from pymongo import MongoClient

# Config
mongo_host = "127.0.0.1"
mongo_port = 27017
mongo_db = "dns"
mongo_collation = "records"


class Lookup(object):
    ttl = 30

    def __init__(self, query):
        (_type, qname, qclass, qtype, _id, ip) = query
        self.has_result = False
        qname_lower = qname.lower()

        self.results = []

        self.results.append('LOG\t%s-%s-%s-%s-%s-%s' % (_type, qname, qclass, qtype, _id, ip))
        self.has_result = True

        client = MongoClient(mongo_host, mongo_port, connect=False)
        db = client[mongo_db]
        coll = db[mongo_collation]

        if qtype == "ANY":
            records = coll.find({"name": qname_lower})
        else:
            records = coll.find({"type": qtype, "name": qname_lower})

        if records:
            for record in records:
                if record['type'] == "SOA":
                    """
                    {
                     "name":"example.org",
                     "type":"SOA",
                     "content":"",
                     "ttl": 300,
                     "primary": "ns1.example.org",
                     "mail": "admin.example.org",
                     "serial": 2018030311,
                     "refresh": 86400,
                     "retry": 7200,
                     "expire": 3600000,
                     "nttl": 3600 
                    }
                    """
                    try:
                        self.results.append(
                            'DATA\t%s\t%s\t%s\t%s\t-1\t%s\t%s\t%s\t%s\t%s\t%s\t%s' % ( qname_lower,
                                                                                       qclass,
                                                                                       qtype,
                                                                                       record['ttl'],
                                                                                       record['primary'],
                                                                                       record['mail'],
                                                                                       record['serial'],
                                                                                       record['refresh'],
                                                                                       record['retry'],
                                                                                       record['expire'],
                                                                                       record['nttl']
                                                                                     ))
                        self.has_result = True
                    except:
                        self.results.append('LOG\t %s SOA Record currupt maybe fields are missing.' % qname_lower)
                else:
                    """
                    {
                     "name":"www.example.org",
                     "type":"A",
                     "ttl": 300,
                     "content": "1.1.1.1"
                    }
                    """
                    self.results.append('DATA\t%s\t%s\t%s\t%d\t-1\t%s' % (
                    qname_lower, qclass, record['type'], record['ttl'], record['content']))
                    self.has_result = True

    def str_result(self):
        if self.has_result:
            return '\n'.join(self.results)
        else:
            return ''


class DNSbackend(object):

    def __init__(self, filein, fileout):
        self.filein = filein
        self.fileout = fileout

        self._process_requests()

    def _fprint(self, message):
        self.fileout.write(message + '\n')
        self.fileout.flush()

    def _process_requests(self):
        first_time = True

        while 1:
            rawline = self.filein.readline()

            if rawline == '':
                return

            line = rawline.rstrip()

            if first_time:
                if line == 'HELO\t1':
                    self._fprint('OK\tPython backend ready.')
                else:
                    rawline = self.filein.readline()
                    sys.exit(1)
                first_time = False
            else:
                query = line.split('\t')
                if len(query) != 6:
                    self._fprint('LOG\tPowerDNS sent unparseable line')
                    self._fprint('FAIL')
                else:
                    lookup = Lookup(query)
                    if lookup.has_result:
                        pdns_result = lookup.str_result()
                        self._fprint(pdns_result)
                    self._fprint('END')


if __name__ == "__main__":
    infile = sys.stdin
    outfile = sys.stdout

    try:
        DNSbackend(infile, outfile)

    except:
        raise

Don`t forget to make the backend.py script executable to the world.

PowerDNS Configuration /etc/powernds/pdns.d/pdns.local.conf

# Here come the local changes the user made, like configuration of
# the several backends that exist.

launch=pipe
pipe-command=/opt/pdns/backend.py

Try…

Launch the PowerDNS service in monitor mode

root@pdnsdev:~# /etc/init.d/pdns monitor        
Jun 20 21:10:11 Reading random entropy from '/dev/urandom'
Jun 20 21:10:11 Loading '/usr/lib/x86_64-linux-gnu/pdns/libpipebackend.so'
Jun 20 21:10:11 [PIPEBackend] This is the pipe backend version 4.0.0-alpha2 reporting
Jun 20 21:10:11 Loading '/usr/lib/x86_64-linux-gnu/pdns/libbindbackend.so'
Jun 20 21:10:11 [bind2backend] This is the bind backend version 4.0.0-alpha2 reporting
Jun 20 21:10:11 This is a standalone pdns
Jun 20 21:10:11 UDP server bound to 0.0.0.0:53
Jun 20 21:10:11 Unable to enable timestamp reporting for socket
Jun 20 21:10:11 UDPv6 server bound to [::]:53
Jun 20 21:10:11 TCP server bound to 0.0.0.0:53
Jun 20 21:10:11 TCPv6 server bound to [::]:53
Jun 20 21:10:11 PowerDNS Authoritative Server 4.0.0-alpha2 (C) 2001-2016 PowerDNS.COM BV
Jun 20 21:10:11 Using 64-bits mode. Built using gcc 5.3.1 20160330.
Jun 20 21:10:11 PowerDNS comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it according to the terms of the GPL version 2.
Jun 20 21:10:11 Set effective group id to 118
Jun 20 21:10:11 Set effective user id to 112
Jun 20 21:10:11 Creating backend connection for TCP
% Jun 20 21:10:11 Backend launched with banner: OK      Python backend ready.
Jun 20 21:10:11 [bindbackend] Parsing 0 domain(s), will report when done
Jun 20 21:10:11 [bindbackend] Done parsing domains, 0 rejected, 0 new, 0 removed
Jun 20 21:10:11 About to create 3 backend threads for UDP
Jun 20 21:10:11 Backend launched with banner: OK        Python backend ready.
Jun 20 21:10:11 Backend launched with banner: OK        Python backend ready.
Jun 20 21:10:11 Done launching threads, ready to distribute questions
Jun 20 21:10:11 Backend launched with banner: OK        Python backend ready.

Do some querys

user@pdnsdev:~$ dig example.org @127.0.0.1 SOA            

; <<>> DiG 9.10.3-P4-Ubuntu <<>> example.org @127.0.0.1 SOA
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 63891
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1680
;; QUESTION SECTION:
;example.org.                   IN      SOA

;; ANSWER SECTION:
example.org.            300     IN      SOA     ns1.example.org. admin.example.org. 2018030311 86400 7200 3600000 3600

;; Query time: 13 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Thu Jun 20 21:20:22 CEST 2018
;; MSG SIZE  rcvd: 86

user@pdnsdev:~$ dig www.example.org @127.0.0.1

; <<>> DiG 9.10.3-P4-Ubuntu <<>> www.example.org @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 896
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1680
;; QUESTION SECTION:
;www.example.org.               IN      A

;; ANSWER SECTION:
www.example.org.        300     IN      A       1.1.1.1

;; Query time: 26 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Thu Jun 20 21:20:44 CEST 2018
;; MSG SIZE  rcvd: 60

Have fun 🙂

 

 

Flask, uWSGI und Nginx auf Ubuntu 16.04

– Kurze Zusammenfassung, ausführlich siehe Quelle am Ende des Artikels –

Python Virtual Environment aufsetzen

pip install virtualenv

virtualenv projekt

source projekt/bin/activate

pip install uwsgi flask


Mini Flask Applikation projekt.py

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello world!"

WSGI Startfile wsgi.py

from projekt import app

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

Virtual Environment verlassen

deactivate

uWSGI Configfile erstellen

[uwsgi]
module = wsgi:app

master = true
processes = 5

socket = /opt/projekt/projekt.sock
chmod-socket = 660
vacuum = true

die-on-term = true

systemd File erstellen  /etc/systemd/system/projekt.service

[Unit]
Description=uWSGI instance to serve projekt
After=network.target

[Service]
User=projekt
Group=www-data
WorkingDirectory=/opt/projekt
Environment="PATH=/opt/projekt/bin"
ExecStart=/opt/projekt/bin/uwsgi --ini projekt.ini

[Install]
WantedBy=multi-user.target

uWSGI Projekt Service starten

sudo systemctl start projekt
sudo systemctl enable projekt

NGINX Config erstellen – /etc/nginx/sites-available/projekt

server {
    listen 80;
    server_name 10.10.10.10;

    location / {
        include uwsgi_params;
        uwsgi_pass unix:/opt/projekt/projekt.sock;
    }
}

NGINX Config aktivieren

ln -s /etc/nginx/sites-available/projekt /etc/nginx/sites-enabled
systemctl restart nginx

Verifizieren NGINX und uWSGI

dev@dev1:~$ ps aux | grep projekt
projekt 17175  0.0  2.1  71216 21812 ?        Ss   19:26   0:00 /opt/projekt/bin/uwsgi --ini projekt.ini
projekt 17178  0.0  1.7  71216 17316 ?        S    19:26   0:00 /opt/projekt/bin/uwsgi --ini projekt.ini
projekt 17179  0.0  1.7  71216 17316 ?        S    19:26   0:00 /opt/projekt/bin/uwsgi --ini projekt.ini
projekt 17180  0.0  1.6  71216 16504 ?        S    19:26   0:00 /opt/projekt/bin/uwsgi --ini projekt.ini
projekt 17181  0.0  1.7  71472 17848 ?        S    19:26   0:00 /opt/projekt/bin/uwsgi --ini projekt.ini
projekt 17182  0.0  1.6  71216 16504 ?        S    19:26   0:00 /opt/projekt/bin/uwsgi --ini projekt.ini

dev@dev1:~$ tail -f /var/log/syslog
Jun 19 19:44:42 dev1 uwsgi[17175]: [pid: 17178|app: 0|req: 1/3] 10.10.10.1 () {46 vars in 834 bytes} [Wed Jun 19 19:44:42 2018] GET / => generated 40 bytes in 6 msecs (HTTP/1.1 200) 2 headers in 79 bytes (1 switches on core 0)
Jun 19 19:44:43 dev1 uwsgi[17175]: [pid: 17179|app: 0|req: 1/4] 10.10.10.1 () {46 vars in 834 bytes} [Wed Jun 19 19:44:43 2018] GET / => generated 40 bytes in 4 msecs (HTTP/1.1 200) 2 headers in 79 bytes (2 switches on core 0)
Jun 19 19:44:44 dev1 uwsgi[17175]: [pid: 17179|app: 0|req: 2/5] 10.10.10.1 () {46 vars in 834 bytes} [Wed Jun 19 19:44:44 2018] GET / => generated 40 bytes in 0 msecs (HTTP/1.1 200) 2 headers in 79 bytes (2 switches on core 0)
Jun 19 19:44:45 dev1 uwsgi[17175]: [pid: 17179|app: 0|req: 3/6] 10.10.10.1 () {46 vars in 834 bytes} [Wed Jun 19 19:44:45 2018] GET / => generated 40 bytes in 0 msecs (HTTP/1.1 200) 2 headers in 79 bytes (2 switches on core 0)

Quelle / ausführlicher Artikel: https://www.digitalocean.com/community/tutorials/how-to-serve-flask-applications-with-uwsgi-and-nginx-on-ubuntu-16-04

 

MongoDB Authentication aktivieren

Per Default ist bei einer MongoDB Instanz keine Authentication aktiviert. Hier der kurze Weg.

Admin Account anlegen

devusr@testsystem:~# mongo
MongoDB shell version v3.6.5
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.6.5
> use admin
switched to db admin
> db.createUser({user: "admin", pwd: "geheimes_passwort", roles: [{ role: "root", db: "admin" }]})
Successfully added user: {
        "user" : "admin",
        "roles" : [
                {
                        "role" : "root",
                        "db" : "admin"
                }
        ]
}
> quit()

Authentication in mongod.conf aktivieren (Ubuntu: /etc/mongod.conf)

...
# network interfaces
net:
  port: 27017
  bindIp: 127.0.0.1

security:
  authorization: enabled
...

Mongod neustarten

service mongod restart

Erster Test: User anlegen für Test DB

> use test
switched to db test
> db.createUser({user: "devuser", pwd: "secure_pwd", roles: [{ role: "readWrite", db: "test" }]})
2018-06-18T08:05:53.448+0200 E QUERY    [thread1] Error: couldn't add user: not authorized on test to execute command { createUser: "devuser", pwd: "xxx", roles: [ { role: "readWrite", db: "test" } ], digestPassword: false, writeConcern: { w: "majority", wtimeout: 600000.0 }, $db: "test" } :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
DB.prototype.createUser@src/mongo/shell/db.js:1437:15
@(shell):1:1
>

Sieht gut aus, es geht nicht 😉

Anmelden und User für Test DB anlegen

use admin
db.auth("admin","geheimes_passwort")
1
use test
> db.createUser({user: "devuser", pwd: "secure_pwd", roles: [{ role: "readWrite", db: "test" }]})
Successfully added user: {
        "user" : "devuser",
        "roles" : [
                {
                        "role" : "readWrite",
                        "db" : "test"
                }
        ]
}
>

Authentifizierung mit pymongo in Python Code

>>> from pymongo import MongoClient
>>> uri = "mongodb://devuser:secure_pwd@localhost/test?authSource=test"                                     
>>> client = MongoClient(uri)
>>> db = client.test
>>> collection = db.foo
>>> collection.insert_one({"foo":"bar"})
<pymongo.results.InsertOneResult object at 0x7fa764f2a998>
>>> collection.find_one()
{u'_id': ObjectId('5b274db939d9c0683b47c0e2'), u'foo': u'bar'}
>>>

Quellen / Weitere Informationen:

Enable Authentication – https://docs.mongodb.com/manual/tutorial/enable-authentication/

Built-In Roles – https://docs.mongodb.com/manual/core/security-built-in-roles/

pymongo authentication – http://api.mongodb.com/python/current/examples/authentication.html

MongoDB und Python

Ein paar Notizen zu Python und MongoDB 🙂 MongoDB ist eine NoSQL Datenbank, weitere Infos -> Wikipedia 🙂

Aktuelle MongoDB Version installieren (auf Ubuntu 16.04)

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2930ADAE8CAF5059EE73BB4B58712A2291FA4AD5
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/3.6 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.6.list
apt install apt-transport-https
apt update
apt-get install -y mongodb-org

siehe auch: https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/

Python Erweiterung pymongo für MongoDB installieren mit pip

python -m pip install pymongo

siehe auch: https://api.mongodb.com/python/current/

Verbindung zur MongoDB

from pymongo import MongoClient

client = MongoClient('localhost', 27017)

Connect zur Datenbank test

db = client.test

Collection myCollection in Datenbank test verbinden 

coll = db.myCollection

Einen Datensatz anlegen in einer Collection

post = {"author":"Fritz Fuchs", "book":"Hasenjagd"}
>>> coll.insert_one(post)
<pymongo.results.InsertOneResult object at 0x7fd0136ce3f8>

Mehrere Datensätze in einer Collection anlegen

>>> posts = [{"author":"Fritz Fuchs", "book":"Hasenjagd 5"}, {"author":"Fritz Fuchs", "book":"Hasenjagd 6"}, {"author":"Fritz Fuchs", "book":"Hasenjagd 7"}]
>>> result = coll.insert_many(posts)
>>> result.inserted_ids
[ObjectId('5b259b2c39d9c04f7b43af01'), ObjectId('5b259b2c39d9c04f7b43af02'), ObjectId('5b259b2c39d9c04f7b43af03')]

Datensatz suchen

>>> coll.find_one({"author":"Fritz Fuchs"}) 
{u'_id': ObjectId('5b25998139d9c04f7b43aefe'), u'book': u'Hasenjagd', u'author': u'Fritz Fuchs'}

Datensatz mit Regex suchen

>>> coll.find_one({"author":{"$regex": "^Fritz.*"}})     
{u'_id': ObjectId('5b25998139d9c04f7b43aefe'), u'book': u'Hasenjagd', u'author': u'Fritz Fuchs'}

Datensatz mit ObjectId abrufen

>>> from bson.objectid import ObjectId
>>> coll.find_one({"_id":ObjectId("5b25998139d9c04f7b43aefe")})
{u'_id': ObjectId('5b25998139d9c04f7b43aefe'), u'book': u'Hasenjagd', u'author': u'Fritz Fuchs'}

Mehrere Datensätze abrufen z.B. mit Regex

>>> for post in coll.find({"author":{"$regex": "^Fritz.*"}}):
...     print post
... 
{u'_id': ObjectId('5b25998139d9c04f7b43aefe'), u'book': u'Hasenjagd', u'author': u'Fritz Fuchs'}
{u'_id': ObjectId('5b259a8939d9c04f7b43aeff'), u'book': u'Hasenjagd 2', u'author': u'Fritz Fuchs'}
{u'_id': ObjectId('5b259a9039d9c04f7b43af00'), u'book': u'Hasenjagd 3', u'author': u'Fritz Fuchs'}

Index erzeugen für Collection z.B. Username ist Unique

>>> import pymongo
>>> db.users.create_index([('user_id', pymongo.ASCENDING)], unique=True)
u'user_id_1'
>>> new_users = [{'user_id':'max'},{'user_id':'fritz'}]     
>>> db.users.insert_many(new_users)
<pymongo.results.InsertManyResult object at 0x7fd0136cee18>                   
>>> new_user = {'user_id':'max'}      
>>> db.users.insert_one(new_user) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/dist-packages/pymongo/collection.py", line 683, in insert_one
    session=session),
  File "/usr/local/lib/python2.7/dist-packages/pymongo/collection.py", line 599, in _insert
    bypass_doc_val, session)
  File "/usr/local/lib/python2.7/dist-packages/pymongo/collection.py", line 580, in _insert_one
    _check_write_command_response(result)
  File "/usr/local/lib/python2.7/dist-packages/pymongo/helpers.py", line 207, in _check_write_command_response
    _raise_last_write_error(write_errors)
  File "/usr/local/lib/python2.7/dist-packages/pymongo/helpers.py", line 188, in _raise_last_write_error
    raise DuplicateKeyError(error.get("errmsg"), 11000, error)
pymongo.errors.DuplicateKeyError: E11000 duplicate key error collection: test.users index: user_id_1 dup key: { : "max" }

ObjectId als String

>>> new_user = {'user_id':'maxx'} 
>>> result = db.users.insert_one(new_user) 
>>> result
<pymongo.results.InsertOneResult object at 0x7fd0122410e0>
>>> result.inserted_id
ObjectId('5b259ead39d9c04f7b43af07')
>>> str(result.inserted_id)   
'5b259ead39d9c04f7b43af07'

String ObjectId zu ObjectId Object wandeln und für Suche verwenden

>>> from bson.objectid import ObjectId
>>> str_obj = '5b259ead39d9c04f7b43af07'
>>> users.find_one({'_id':ObjectId(str_obj)})
{u'_id': ObjectId('5b259ead39d9c04f7b43af07'), u'user_id': u'maxx'}

Datensatz löschen

>>> users.delete_one({'_id':ObjectId(str_obj)})    
<pymongo.results.DeleteResult object at 0x7fd0136cee18>

Datensatz aktualisieren

>>> new_user = {'user_id':'maxx', 'name':'Hans Wurst'}
>>> users.insert_one(new_user)
>>> change = {'name': 'Fritz Fritz'}
>>> users.update_one({'_id':ObjectId('5b25a14f39d9c04f7b43af08')}, {'$set':change} )
<pymongo.results.UpdateResult object at 0x7fd0136ced40>
>>> users.find_one({'_id':ObjectId('5b25a14f39d9c04f7b43af08')})                                                            
{u'_id': ObjectId('5b25a14f39d9c04f7b43af08'), u'user_id': u'maxx', u'name': u'Fritz Fritz'}

Bereich bei Suche

>>> client = MongoClient()         
>>> db = client.huu
>>> c = db.test
>>> posts = [{"author":"Fritz Fuchs", "book":"Hasenjagd 5", "boo":1}, {"author":"Fritz Fuchs", "book":"Hasenjagd 6", "boo":5}, {"author":"Fritz Fuchs", "book":"Hasenjagd 7", "boo":10}] 
>>> c.insert_many(posts)

>>> for post in c.find({"boo": {"$lt": 6}}).sort("author"):
...     print post
... 
{u'author': u'Fritz Fuchs', u'_id': ObjectId('5b25bb9539d9c05186997b54'), u'book': u'Hasenjagd 5', u'boo': 1}
{u'author': u'Fritz Fuchs', u'_id': ObjectId('5b25bb9539d9c05186997b55'), u'book': u'Hasenjagd 6', u'boo': 5}

>>> for post in c.find({"boo": {"$gt": 6}}).sort("author"): 
...     print post
... 
{u'author': u'Fritz Fuchs', u'_id': ObjectId('5b25bb9539d9c05186997b56'), u'book': u'Hasenjagd 7', u'boo': 10}

>>> for post in c.find({"boo": {"$lt": 6, "$gt": 3}}).sort("author"): 
...     print post
... 
{u'author': u'Fritz Fuchs', u'_id': ObjectId('5b25bb9539d9c05186997b55'), u'book': u'Hasenjagd 6', u'boo': 5}

 

Quelle / weitere Beispiele: http://api.mongodb.com/python/current/tutorial.html

 

Passwort Hashing in Python

Hier die einfachste Variante um in sicheres Passwort Hashing in Python umzusetzen.

Dazu wird die passlib verwendet. Diese kann per pip installiert werden, passlib ist für Python 2.x und 3.x kompatibel.

pip install passlib

Passwort hashen:

>>> # passlib laden
>>> from passlib.hash import pbkdf2_sha256
>>>
>>> # das passwort zum hashen 
>>> password = "EinsuperGeheimesPasswort"
>>> 
>>> # ein falsches passwort zum testen
>>> password2 = "FalschesPasswort"
>>> 
>>> hash = pbkdf2_sha256.hash(password)
>>> # hash ausgeben
>>> hash
'$pbkdf2-sha256$29000$IQSgdE6p1VoL4fwfQwjBWA$9sy/3NJmX1jP.3kYwgmG96zVpBxoVA5yKA6pB.T5Mrw'

Passwort verifizieren:

>>> # richtiges passwort
>>> pbkdf2_sha256.verify(password, hash)
True
>>> # falsches passwort
>>> pbkdf2_sha256.verify(password2, hash)
False
>>> 

Wem pbkdf2_sha256 zu kompliziert ist kann das auch beim Import mit einem Alias versehen.

>>> from passlib.hash import pbkdf2_sha256 as pwhash
>>> hash = pwhash.hash(password)
>>> hash
'$pbkdf2-sha256$29000$Z2zNWYsxBiDEmFPK2XsvZQ$WQ8Urv1d4AmBV4v.vFV01uHeZZ7ya52VuYbLkEHEsWE'
>>> pwhash.verify(password, hash)
True

 

Weitere Infos:

https://passlib.readthedocs.io/en/stable/

Kein Platz mehr auf der /boot Partition in Ubuntu / Debian

Per Default ist die Boot Partition ca. 500M groß, da sich hier durch die Updates gerne alte Kernel ansammeln ist es ratsam zwischendurch aufzuräumen.

Beispiel einer abgebrochenen Installation, Meldung ist „No space left on device“

linux-image-4.4.0-116-generic (4.4.0-116.140) wird eingerichtet ...
Running depmod.
update-initramfs: deferring update (hook will be called later)
The link /initrd.img is a dangling linkto /boot/initrd.img-4.4.0-116-generic
vmlinuz(/boot/vmlinuz-4.4.0-116-generic
) points to /boot/vmlinuz-4.4.0-116-generic
 (/boot/vmlinuz-4.4.0-116-generic) -- doing nothing at /var/lib/dpkg/info/linux-image-4.4.0-116-generic.postinst line 491.
Examining /etc/kernel/postinst.d.
run-parts: executing /etc/kernel/postinst.d/apt-auto-removal 4.4.0-116-generic /boot/vmlinuz-4.4.0-116-generic
run-parts: executing /etc/kernel/postinst.d/initramfs-tools 4.4.0-116-generic /boot/vmlinuz-4.4.0-116-generic
update-initramfs: Generating /boot/initrd.img-4.4.0-116-generic
W: mdadm: /etc/mdadm/mdadm.conf defines no arrays.

gzip: stdout: No space left on device
E: mkinitramfs failure cpio 141 gzip 1
update-initramfs: failed for /boot/initrd.img-4.4.0-116-generic with 1.
run-parts: /etc/kernel/postinst.d/initramfs-tools exited with return code 1
Failed to process /etc/kernel/postinst.d at /var/lib/dpkg/info/linux-image-4.4.0-116-generic.postinst line 1052.
dpkg: Fehler beim Bearbeiten des Paketes linux-image-4.4.0-116-generic (--configure):
 Unterprozess installiertes post-installation-Skript gab den Fehlerwert 2 zurück

Bei ubuntuusers gibt es einen nützlichen Artikel wie man am besten seine alten Kernel entsorgt.

Hier die Kurzfassung, weitere Details bitte direkt bei ubuntuusers nachlesen.

Aktuellen Kernel feststellen:

max@kronos01:~$ uname -a
Linux kronos01 4.4.0-109-generic #132-Ubuntu SMP Tue Jan 9 19:52:39 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

Alte Kernel in Textdatei schreiben, prüfen ob aktueller Kernel dabei ist wenn ja entfernen:

max@kronos01:~$ dpkg -l 'linux-[ihs]*' | sed '/^ii/!d;/'"$(uname -r | sed "s/\([-0-9]*\)-\([^0-9]\+\)/\1/")"'/d;s/^[^ ]* [^ ]* \([^ ]*\).*/\1/;/[0-9]/!d' | tee zu_entfernende_Kernel
max@kronos01:~$ cat zu_entfernende_Kernel 
linux-headers-4.4.0-101
linux-headers-4.4.0-101-generic
linux-headers-4.4.0-103
linux-headers-4.4.0-103-generic
linux-headers-4.4.0-104
linux-headers-4.4.0-104-generic
linux-headers-4.4.0-108
linux-headers-4.4.0-108-generic
linux-headers-4.4.0-81
linux-headers-4.4.0-81-generic
linux-headers-4.4.0-93
linux-headers-4.4.0-93-generic
linux-headers-4.4.0-97
linux-headers-4.4.0-97-generic
linux-headers-4.4.0-98
linux-headers-4.4.0-98-generic
linux-image-4.4.0-101-generic
linux-image-4.4.0-103-generic
linux-image-4.4.0-104-generic
linux-image-4.4.0-108-generic
linux-image-4.4.0-81-generic
linux-image-4.4.0-93-generic
linux-image-4.4.0-97-generic
linux-image-4.4.0-98-generic
linux-image-extra-4.4.0-101-generic
linux-image-extra-4.4.0-103-generic
linux-image-extra-4.4.0-104-generic
linux-image-extra-4.4.0-108-generic
linux-image-extra-4.4.0-93-generic
linux-image-extra-4.4.0-97-generic
linux-image-extra-4.4.0-98-generic

Alte Kernel löschen und File löschen:

max@kronos01:~$ cat zu_entfernende_Kernel | xargs sudo apt-get -y purge 

max@kronos01:~$ rm zu_entfernende_Kernel

 

Quelle: https://wiki.ubuntuusers.de/Skripte/Alte_Kernel_entfernen/

 

 

Outlook 2016 issues with many mailboxes and many folders

I had a huge problem at one of my clients. He has an Exchange 2016 CU8 with Windows 10 Clients and Outlook 2016 Build 8829 click-to-run. The customer has in total 20 mailboxes and all coworkers (5) in most cases have full access to each other. Some of the users have a big folder structure in the mailbox.

Outlook begins to stall while trying to access the mailboxes during startup of Outlook. If you look in the connection Details, you will see many connections to the different boxes and one or two lines where the VID/CID counts up really fast and changes the state between success and failure. Additionally, you see some garbage (random ASCII) in the mailbox name.

Big thanks to my collegue Wolle who is a geat Microsoft engineer. He found the issue which was causing the problem 🙂

The issue is caused because the user reaches the MAPI object and session limit of Exchange 2016. That is logged in „C:\Program Files\Microsoft\Exchange Server\V15\Logging\MapiHttp\Mailbox“ logs.

MoMTException:1246 (rop 1246) -> [TooManyObjectsOpenedException]
  Das Postfach /o=First Organization/ou=Exchange Administrative Group (XXXXXXXXXXXXXXXXXX)/cn=Recipients/cn=xxxxxxxxxxxxxxxxxxx-Customer
  kann nicht geöffnet werden. -> [MapiExceptionSessionLimit] Unable to open message store.

Additionally, the user runs into the MAPI limits due to the high access rate and the Exchange server begins to throttle down the connections.

These two things fixes the problems of my customer. Be aware that setting throttling to unlimited is typically not a good idea, but in this small environment it is okay.

In Exchange Powershell, create a new throttling policy and apply it to all affected mailboxes:

New-ThrottlingPolicy -Name "New Throttling" -RCAMAXConcurrency Unlimited
  
get-mailbox -name | set-mailbox -ThrottlingPolicy "New Throttling"

A new registry key to raise the MAPI objects limit and the maximum allowed sessions per user:

Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\MSExchangeIS\ParametersSystem\MaxObjsPerMapiSession]
"objtMessage"=dword:000005dc

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\MSExchangeIS\ParametersSystem]
"Maximum Allowed Sessions Per User"=dword:00000080

After changing the Limit, you must restart the Exchange Information Store service.

Additional information:

https://social.technet.microsoft.com/Forums/windows/en-US/f5752a11-0cea-4849-abc3-65f6c1c2f6b0/exchange-2016-mapiexceptionsessionlimit?forum=Exch2016GD

 

KeeWeb: Self-hosted password manager with apache2

Add Authentication

First of all you’ll need to setup a .htpasswd file. To do so type in the following:

sudo htpasswd -c /etc/apache2/.htpasswd username

To use the authentication you can add the following to your VHost configuration:

<Location "/">
  AuthType "Basic"
  AuthName "KeeWeb"
  AuthBasicProvider file
  AuthUserFile "/etc/apache2/.htpasswd"
  Require valid-user
</Location>

Setup WebDAV

WebDAV is needed to store your password database.

To enable WebDAV on Apache2 you need to type in the following command:

sudo a2enmod dav_fs

Now create a new directory for WebDAV and make it only accessable for the apache user:

mkdir /var/www/webdav
chown www-data:www-data /var/www/webdav
chmod 700 /var/www/webdav

Now we are going to create a new config for WebDAV in apache2:

Type in:

sudo vi /etc/apache2/sites-available/webdav.conf

Add the following:

Alias /webdav "/var/www/webdav"
<Directory "/var/www/webdav">
  DAV On
  Options Indexes
</Directory>


Save it and type in: sudo a2ensite webdav.conf && sudo service apache2 reload

Now test your settings by accessing yoururl.com/webdav.
You should see the Index of WebDAV.

Install KeeWeb

First you need to install git:

sudo apt install git

After this you can clone the keeweb repository by typing in the following:

cd /var/www/
git clone -b gh-pages https://github.com/keeweb/keeweb.git

This will create a keeweb folder in your /var/www/ directory.

 

Now we need to create a keeweb config for our apache:

vi /etc/apache2/sites-available/keeweb.conf

Type in the following:

<VirtualHost *:443>
        ServerName pass.yourdomain.com
        ServerAlias www.pass.yourdomain.de
        DocumentRoot /var/www/keeweb
</VirtualHost>


Save it and type in: sudo a2ensite keeweb.conf && sudo service apache2 reload

Now we add the authentication to our keeweb site.

To do so you can either create a .htaccess or add the authentication to your VHost like we setup above.

.htaccess:

vi /var/www/keeweb/.htaccess

Add the following:

AuthType Basic
AuthName "KeeWeb"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user

Save it and quit.

Test your KeeWeb installation:

If you enter the url pass.yourdomain.com you should see a login prompt and after this the keeweb index page.

YOU MADE IT!

Create a default database:

Go to your keeweb index page and click on new. Now you created a temporary database. We want to save the database to our WebDAV to access it from everywhere. To do so, click on new in the bottom left corner.

Now you can change a few settings. I highly recommend to set a master password and a username! If you changed all your settings click on „Save as…“ and choose WebDAV.

Type in the path to your webdav and your username + password.

Example:

 

Congratulations! Your password manager is ready to use!

Ansible: Template Engine nutzen um Cisco Konfigurationen zu erzeugen

Ansible verwendet als Template Engine Jinja2. Als Beispiel wird für einen Switch die Radius Konfiguration erzeugt.
Man kann direkt vom Template aus die Konfiguration auf ein Gerät fallen lassen oder so wie in diesem Fall einfach als File speichern.

Eine ausführliche Anleitung was man alles mit der Jinja2 Template Engine machen kann ist unter http://jinja.pocoo.org/docs/2.10/ zu finden.

Template radius_demo.j2:

{% for rad in globals_demo.aaagroup %}
aaa group server radius {{ rad.name }}
{% for server in rad.server %}
 server name {{ server }}
{% endfor %}
 deadtime {{ rad.deadtime }}

{% endfor %}

{% for server in globals_demo.radiusserver %}
radius server {{ server.name }}
 address ipv4 {{ server.address }} auth-port {{ server.authport }} acct-port {{ server.acctport }}
 timeout {{ server.timeout }}
 retransmit {{ server.retransmit }}
 key {{ server.pskkey }}

{% endfor %}

globals_demo.yaml – Konfigdatei mit globalen Variablen welche in das Playbook geladen werden.

---

aaagroup:
  - name: DOT1X
    server:
      - RADIUS1-DOT1X
      - RADIUS2-DOT1X
      - RADIUS3-DOT1X
      - RADIUS4-DOT1X
    deadtime: 1
    
radiusserver:
  - name: RADIUS1-DOT1X
    address: 192.168.1.101
    authport: 1812
    acctport: 1813
    timeout: 4
    retransmit: 3
    pskkey: 7 02000B5409071D

  - name: RADIUS2-DOT1X
    address: 192.168.1.102
    authport: 1812
    acctport: 1813
    timeout: 4
    retransmit: 3
    pskkey: 7 02000B5409071D

  - name: RADIUS3-DOT1X
    address: 192.168.1.103
    authport: 1812
    acctport: 1813
    timeout: 4
    retransmit: 3
    pskkey: 7 02000B5409071D

  - name: RADIUS4-DOT1X
    address: 192.168.1.104
    authport: 1812
    acctport: 1813
    timeout: 4
    retransmit: 3
    pskkey: 7 02000B5409071D

config_gen.yaml – Das Playbook welches die Konfiguration erzeugen soll

---
- hosts: all
  connection: local
  gather_facts: False
  vars:
    cli:
      host: "{{ inventory_hostname }}"
      username: "{{ ansible_user }}"
      password: "{{ ansible_ssh_pass }}"

  tasks:
  - name: Load globals
    include_vars:
      file: globals_demo.yaml
      name: globals_demo

  - name: Generate config
    template:
      src: radius_demo.j2
      dest: "/tmp/out_{{ inventory_hostname }}.cfg"

Testlauf config_gen.yaml Playbook

ansible-playbook -i demo.ini config_gen.yaml -v

PLAY [all] *****************************************************************

TASK [Load globals] ********************************************************
ok: [switch1] => {"ansible_facts": {"globals_demo": {"aaagroup": [{"deadtime": 1, "name": "DOT1X", "server": ["RADIUS1-DOT1X", "RADIUS2-DOT1X", "RADIUS3-DOT1X", "RADIUS4-DOT1X"]}], "radiusserver": [{"acctport": 1813, "address": "192.168.1.101", "authport": 1812, "name": "RADIUS1-DOT1X", "pskkey": "7 02000B5409071D", "retransmit": 3, "timeout": 4}, {"acctport": 1813, "address": "192.168.1.102", "authport": 1812, "name": "RADIUS2-DOT1X", "pskkey": "7 02000B5409071D", "retransmit": 3, "timeout": 4}, {"acctport": 1813, "address": "192.168.1.103", "authport": 1812, "name": "RADIUS3-DOT1X", "pskkey": "7 02000B5409071D", "retransmit": 3, "timeout": 4}, {"acctport": 1813, "address": "192.168.1.104", "authport": 1812, "name": "RADIUS4-DOT1X", "pskkey": "7 02000B5409071D", "retransmit": 3, "timeout": 4}]}}, "ansible_included_var_files": ["/home/ansible/_ansible_cisco_tests/globals_demo.yaml"], "changed": false}

TASK [Generate config] *****************************************************
ok: [switch1] => {"changed": false, "checksum": "b1cac79a0ba981ab398439a9102df687b7932ede", "gid": 1001, "group": "ansible", "mode": "0644", "owner": "ansible", "path": "/tmp/out_switch1.cfg", "size": 715, "state": "file", "uid": 1001}

PLAY RECAP *****************************************************************
switch1                : ok=2    changed=0    unreachable=0    failed=0   

Ausgabe out_switch1.cfg

aaa group server radius DOT1X
 server name RADIUS1-DOT1X
 server name RADIUS2-DOT1X
 server name RADIUS3-DOT1X
 server name RADIUS4-DOT1X
 deadtime 1


radius server RADIUS1-DOT1X
 address ipv4 192.168.1.101 auth-port 1812 acct-port 1813
 timeout 4
 retransmit 3
 key 7 02000B5409071D

radius server RADIUS2-DOT1X
 address ipv4 192.168.1.102 auth-port 1812 acct-port 1813
 timeout 4
 retransmit 3
 key 7 02000B5409071D

radius server RADIUS3-DOT1X
 address ipv4 192.168.1.103 auth-port 1812 acct-port 1813
 timeout 4
 retransmit 3
 key 7 02000B5409071D

radius server RADIUS4-DOT1X
 address ipv4 192.168.1.104 auth-port 1812 acct-port 1813
 timeout 4
 retransmit 3
 key 7 02000B5409071D

Möchte man die Konfig direkt auf ein Gerät ausrollen kann man das Playbook wie folgt erweitern:

- name: Apply RADIUS configuration to switch
  ios_config:
    src: radius_demo.j2
    provider: "{{ cli }}"
    match: line

 

Ansible: CLI Kommandos auf Cisco Devices ausführen und Ausgaben speichern

Beispiel Playbook exec_command.yaml:

---
- hosts: switch1
  connection: local
  gather_facts: False
  vars:
    cli:
      host: "{{ inventory_hostname }}"
      username: "{{ ansible_user }}"
      password: "{{ ansible_ssh_pass }}"

  tasks:
  - name: Call show inventory
    ios_command:
      commands: show inventory
      provider: "{{ cli }}"
    register: output

  - name: Store output to file
    copy:
      content="{{ output.stdout[0] }}"
      dest="out/inventory_{{ inventory_hostname }}.txt"

Mit „ios_command“ lassen sich Kommandos auf der CLI ausführen und die Ausgabe weiterverwenden. In dem Beispiel wird die Ausgabe in einer Datei gespeichert.

ansible-playbook -i hosts.yaml exec_command.yaml -v

PLAY [switch1] *********************************************************************************

TASK [Call show inventory] *********************************************************************
ok: [switch1] => {"changed": false, "stdout": ["NAME: \"1\", DESCR: \"WS-C2960-8TC-S\"\nPID: WS-C2960-8TC-S    , VID: V01  , SN: FOCXXXXXXXX"], "stdout_lines": [["NAME: \"1\", DESCR: \"WS-C2960-8TC-S\"", "PID: WS-C2960-8TC-S    , VID: V01  , SN: FOCXXXXXXXX"]]}

TASK [Store output to file] ********************************************************************
changed: [switch1] => {"changed": true, "checksum": "bb873c38a92c5fcf4f337c4730bf626ba36ac558", "dest": "out/inventory_switch1.txt", "gid": 0, "group": "root", "md5sum": "f7a2aca1b08cdaeb5564d1e368b6ff19", "mode": "0644", "owner": "root", "size": 87, "src": "/root/.ansible/tmp/ansible-tmp-1516308679.35-272245399379253/source", "state": "file", "uid": 0}

PLAY RECAP *************************************************************************************
switch1                    : ok=2    changed=1    unreachable=0    failed=0   



cat out/inventory_switch1.txt

NAME: "1", DESCR: "WS-C2960-8TC-S"
PID: WS-C2960-8TC-S    , VID: V01  , SN: FOCXXXXXXXX

 

 

Ansible: Cisco Netzwerkautomatisierung mit Ansible – Erste Schritte

Ich habe kürzlich Angefangen meine ersten Gehversuche mit Ansible zu machen. Ziel ist unteranderem meine Linux Maschinen zu managen als auch Cisco Geräte. Hier meine ersten Erfahrungen mit Ansible.

Was ist der Vorteil von Ansible?

  • Opensource
  • reichhaltiges Plugin Angebot (Ansible Galaxy)
  • viele Artikel & Tipps im Internet, kein Nischenprodukt
  • es ist in Python geschrieben 😉
  • Ansible nutzt die jinja2 Template Engine
  • Nutzt YAML & JSON
  • und das wichtigste -> es wird auf dem Zielgerät kein Client oder Agent benötigt, benötigt wird nur SSH und bei Linux Hosts Python

Installation von Ansible auf einem Ubuntu System

Es ist empfehlenswert die letzte Version aus den PPA Repositories zu verwenden, die Version in 16.04 LTS ist 2.x, aktuell ist 2.4.2.0. Ich habe bei meinen Versuchen festgestellt das viele Plugins z.B. ansible-hardening (https://github.com/openstack/ansible-hardening) nicht funktionieren.

Kurzanleitung aktuelle Version auf Ubuntu

sudo apt update
sudo apt install software-properties-common
sudo apt-add-repository ppa:ansible/ansible
sudo apt update
sudo apt install ansible

Erste Gehversuche in Ansible

Ein Ansible Setup besteht aus 2 Bestandteilen, ein Inventory und YAML Files (Playbooks, etc.).

Beispiel meines Inventorys

[switches]
switch1

[switches:vars]
ansible_user=admin
ansible_ssh_pass=admin
ansible_ssh_common_args='-o StrictHostKeyChecking=no'

In den eckigen Klammern kann man Gruppen erzeugen, darunter sind die Mitglieder der Gruppe. Ein Host kann in mehreren Gruppen sein. Mit [gruppenname:vars] können gruppenspezifische Parameter mit übergeben werden. Der Host switch1 ist in meinem DNS auflösbar, sollte das nicht der Fall sein kann man hinter den Hostnamen die IP-Adresse statisch übergeben wie folgt:

[switches]
switch1 ansible_host=192.168.1.44

Das Inventory lässt sich alternativ zum INI Format auch als YAML definieren.

---
all:
  hosts:

switches:
  hosts:
    switch1:
      ansible_host: 192.168.1.44
    switch2:
    switch3:
    switch4:
  
  vars:
    ansible_user: admin
    ansible_ssh_pass: admin
    ansible_ssh_common_args: -o StrictHostKeyChecking=no

Erster Test: IOS_Facts vom Switch laden

test.yaml

---
- hosts: switch1
  connection: local
  gather_facts: False
  vars:
    cli:
      host: "{{ inventory_hostname }}"
      username: "{{ ansible_user }}"
      password: "{{ ansible_ssh_pass }}"

  tasks:
  - name: Get IOS facts
    ios_facts:
      gather_subset: all
      provider: "{{ cli }}"
    register: iosfacts_out
    tags: foobar

  - name: print iosfacts_out
    debug: var=iosfacts_out




Im YAML File sind folgende Dinge definiert:

  • hosts: switch1
    • Hier können Gruppen oder einzelne Hosts angegeben werden, wird mehr als ein Host oder Gruppe angegeben ist eine Liste mit Spiegelstrichen zu erstellen

z.B.

- hosts:
  - switch1
  - switch2
  • connection: local
    • Da auf dem Cisco Switch kein Python vorhanden ist wird das von Ansible erzeuge Python File lokal ausgeführt.
  • gather_facts: False
    • Das Sammeln von Fakten (Informationen) über den Hosts ist deaktiviert
  • vars:
    • Hier können Variablen definiert werden die für das Playbook gelten
  • tasks:
    • Hier können die einzelnen Tasks definiert werden
    • – name: <Beschreibung des Task>
      • register: <variablename>
        • Ausgaben die durch ein Plugin erzeugt werden in der genannten Variable gespeichert
      • tags: <tagname>
        • Für einen Task können ein oder mehrere Tags definiert werden, so lassen sich aus einem Playbook ein oder mehrere Tasks aus dem kompletten Playbook ausführen, dazu muss man ansible-playbook mit dem Parameter –tags=<tagname> aufrufen.

Das Playbook ausführen

ansible-playbook -i hosts.yaml test.yaml

PLAY [switch1] **************************************************************************************

TASK [Get IOS facts] *******************************************************************************
ok: [switch1]

TASK [print iosfacts_out] **************************************************************************
ok: [switch1] => {
    "iosfacts_out": {
        "ansible_facts": {
            "ansible_net_all_ipv4_addresses": [
                "192.168.1.44"
            ], 
... gekürzt ... 
PLAY RECAP *****************************************************************************************
switch1                    : ok=2    changed=0    unreachable=0    failed=0   

Der erste Test war erfolgreich, alle weiteren Beispiele kommen in kleineren Beiträgen.

Viel Spaß 🙂

 

Performance Probleme auf einem Linux System ermitteln

Aktuell habe ich auf einem meiner virtuellen Server Probleme mit der Performance, ein Blick in top offenbart das auf einer CPU 100% Waiting ist, Hintergrund war das der Provider einen ZFS scrub auf dem Host laufen ließ was die Disk Performance in den Keller gezogen hat. Ich nehme das zum Anlass ein paar Tools zu zeigen mit denen man sehen kann was in einem Linux System abgeht. Ich verzichte auf großartige Beschreibungen da die Tools und die MAN Pages für sich sprechen.

ATOP – AT Computing’s System & Process Monitor

atop lässt sich auf einem Ubuntu System mit apt install atop installieren.

In meinem Fall habe ich atop mit den Parametern -dD aufgerufen, so werden alle Prozesse angezeigt welche mit Festplatten Aktivität zu tun haben. avio zeigt die durchschnittlichen Millisekunden für eine Abfrage für Suche, Latenz und Datentransfer, der Wert mit 3035 ms ist viel zu hoch.

-d  show disk-related process-info
-D  sort processes in order of disk-activity

TOP – display Linux processes

Das Standard Tool welches in der Regel auf jedem unixoiden Betriebsystem zur Verfügung steht.

Im Programm kann man noch Ansichten ändern etc. hier die aus meiner Sicht wichtigsten:

Dazu einfach die entsprechende Traste drücken:

  • d – Delay in Sekunden für die Aktualisierung einstellen
  • u – Usernamen eingeben, es werden nur laufende Prozesse des Users angezeigt
  • z – Wechsel zwischen Monocrome und Color Mode
  • 1 – Alle CPUs werden einzeln angezeigt
  • q oder STRG+C – Quit – Verlassen der Applikation

IOSTAT – Report Central Processing Unit (CPU) statistics and input/output statistics for devices and partitions.

iostat gibt die IO Statistiken aus. Wenn iostat mit dem Parameter 1 gestartet wird wird jede Sekunde eine Aktualisierung ausgegeben.

root@dev01:~# iostat 1
Linux 3.13.0-137-generic (dev01.m.test) 	12/24/2017 	_x86_64_	(4 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           0.26    0.02    0.09    7.43    0.00   92.20

Device:            tps    kB_read/s    kB_wrtn/s    kB_read    kB_wrtn
sda               3.18        44.11        34.80    3049729    2406628

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           5.04    0.00    0.76   28.97    0.00   65.24

Device:            tps    kB_read/s    kB_wrtn/s    kB_read    kB_wrtn
sda               3.00         0.00        52.00          0         52

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           5.28    0.00    0.50   24.87    0.00   69.35

Device:            tps    kB_read/s    kB_wrtn/s    kB_read    kB_wrtn
sda               0.00         0.00         0.00          0          0

VMSTAT – Report virtual memory statistics

Gibt die Speichernutzung aus. Wenn vmstat mit dem Parameter 1 gestartet wird wird jede Sekunde eine Aktualisierung ausgegeben.

root@dev01:~# vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0 10447220 272300 3105828    0    0    11     9   63   60  0  0 92  8  0
 1  0      0 10446968 272300 3105956    0    0   128    24 2330  230  1  0 83 16  0
 0  1      0 10446684 272300 3106100    0    0     0     4 2317  416  1  0 99  0  0
 0  1      0 10446908 272300 3106100    0    0     0    96 2082  128  0  0 80 20  0
 0  1      0 10446616 272304 3106224    0    0   128    76 2247  117  0  0 76 24  0
 0  1      0 10446492 272304 3106356    0    0   128     0 2225  111  0  0 75 25  0
 0  1      0 10446524 272304 3106356    0    0     0     0 2096  105  0  0 75 25  0
 0  2      0 10446432 272304 3106484    0    0   128     0 2066  108  0  0 55 45  0
 0  2      0 10446528 272304 3106484    0    0   128    12 2052  100  0  0 51 49  0
 0  2      0 10446560 272304 3106484    0    0     0     0 1992   80  0  0 50 49  0
 0  1      0 10446132 272308 3106864    0    0   256    12 2058  103  0  0 58 42  0
 0  0      0 10445880 272308 3106868    0    0   128     0 2031  100  0  0 97  3  0
 1  1      0 10445756 272308 3106996    0    0   128     0 2187  234  1  0 95  4  0
 0  2      0 10445788 272308 3107124    0    0     0    28 2032  100  2  0 52 46  0
 0  2      0 10445820 272308 3107124    0    0     0     0 1906  107  0  0 50 50  0
 0  2      0 10445476 272308 3107124    0    0   256    16 2222  110  0  0 67 32  0

IOTOP – simple top-like I/O monitor

Zeigt alle Prozesse an und welchen I/O Load sie im System verursachen.

HTOP – interactive process viewer

htop ist ein grafisch gepimpter Prozess Viewer. Mit F5 lassen sich die Prozesse als Baum darstellen.

LSOF – list open files

Zeigt alle offenen Dateien in einem Linux System

 

root@dev1:~# lsof -u www-data
COMMAND   PID     USER   FD      TYPE DEVICE SIZE/OFF   NODE NAME
apache2 51385 www-data  cwd       DIR  252,0     4096      2 /
apache2 51385 www-data  rtd       DIR  252,0     4096      2 /
apache2 51385 www-data  txt       REG  252,0   662496 409596 /usr/sbin/apache2
apache2 51385 www-data  mem       REG  252,0    89696 653325 /lib/x86_64-linux-gnu/libgcc_s.so.1
apache2 51385 www-data  mem       REG  252,0    47600 653363 /lib/x86_64-linux-gnu/libnss_files-2.23.so
apache2 51385 www-data  mem       REG  252,0    47648 653367 /lib/x86_64-linux-gnu/libnss_nis-2.23.so
apache2 51385 www-data  mem       REG  252,0    93128 653357 /lib/x86_64-linux-gnu/libnsl-2.23.so
apache2 51385 www-data  mem       REG  252,0    35688 653359 /lib/x86_64-linux-gnu/libnss_compat-2.23.so
apache2 51385 www-data  mem       REG  252,0    22536 143878 /usr/lib/apache2/modules/mod_status.so
apache2 51385 www-data  mem       REG  252,0    14344 143967 /usr/lib/apache2/modules/mod_setenvif.so
apache2 51385 www-data  mem       REG  252,0    34832 143955 /usr/lib/apache2/modules/mod_negotiation.so
apache2 51385 www-data  mem       REG  252,0    63504 143890 /usr/lib/apache2/modules/mod_mpm_event.so
apache2 51385 www-data  mem       REG  252,0    18440 143969 /usr/lib/apache2/modules/mod_mime.so
apache2 51385 www-data  mem       REG  252,0    18440 143942 /usr/lib/apache2/modules/mod_filter.so
apache2 51385 www-data  mem       REG  252,0    10248 143913 /usr/lib/apache2/modules/mod_env.so
apache2 51385 www-data  mem       REG  252,0    14344 143881 /usr/lib/apache2/modules/mod_dir.so
apache2 51385 www-data  mem       REG  252,0   104864 653422 /lib/x86_64-linux-gnu/libz.so.1.2.8
apache2 51385 www-data  mem       REG  252,0    34824 143912 /usr/lib/apache2/modules/mod_deflate.so
apache2 51385 www-data  mem       REG  252,0    38928 143951 /usr/lib/apache2/modules/mod_autoindex.so
apache2 51385 www-data  mem       REG  252,0    10256 143964 /usr/lib/apache2/modules/mod_authz_user.so
apache2 51385 www-data  mem       REG  252,0    10256 143950 /usr/lib/apache2/modules/mod_authz_host.so
apache2 51385 www-data  mem       REG  252,0    22544 143873 /usr/lib/apache2/modules/mod_authz_core.so
apache2 51385 www-data  mem       REG  252,0    10256 143954 /usr/lib/apache2/modules/mod_authn_file.so
apache2 51385 www-data  mem       REG  252,0    10256 143947 /usr/lib/apache2/modules/mod_authn_core.so
apache2 51385 www-data  mem       REG  252,0    14352 143960 /usr/lib/apache2/modules/mod_auth_basic.so
apache2 51385 www-data  mem       REG  252,0    14344 143909 /usr/lib/apache2/modules/mod_alias.so
apache2 51385 www-data  mem       REG  252,0    10256 143920 /usr/lib/apache2/modules/mod_access_compat.so
apache2 51385 www-data  mem       REG  252,0    14608 653313 /lib/x86_64-linux-gnu/libdl-2.23.so
apache2 51385 www-data  mem       REG  252,0    18976 653418 /lib/x86_64-linux-gnu/libuuid.so.1.3.0
apache2 51385 www-data  mem       REG  252,0   166032 653320 /lib/x86_64-linux-gnu/libexpat.so.1.6.0
apache2 51385 www-data  mem       REG  252,0    39224 653307 /lib/x86_64-linux-gnu/libcrypt-2.23.so
apache2 51385 www-data  mem       REG  252,0  1868984 653299 /lib/x86_64-linux-gnu/libc-2.23.so
apache2 51385 www-data  mem       REG  252,0   138696 653386 /lib/x86_64-linux-gnu/libpthread-2.23.so
apache2 51385 www-data  mem       REG  252,0   204936 409578 /usr/lib/x86_64-linux-gnu/libapr-1.so.0.5.2
apache2 51385 www-data  mem       REG  252,0   159488 409580 /usr/lib/x86_64-linux-gnu/libaprutil-1.so.0.5.4
apache2 51385 www-data  mem       REG  252,0   456632 653379 /lib/x86_64-linux-gnu/libpcre.so.3.13.2
...

IFTOP – display bandwidth usage on an interface by host

Zeigt aktuelle Netzwerkverbindungen an und deren Traffic.

Defekten GRUB Bootloader reparieren

Defekten Rechner mit Knoppix oder der systemrescuecd starten, mit fdisk -l oder parted -l kann man sich alle Partitionen anzeigen lassen. Je nach Installation ist die Bootpartition einzeln z.B. bei einem LVM Setup oder in einer Partition.

Um per Chroot in die Installation des Rechners zu wechseln müssen die Partitionen gemountet werden, im Beispiel befindet sich die Bootpartition auf einer Partition mit den restlichen Daten:

mkdir /mnt/linux
mount /dev/sda1 /mnt/linux
mount -o bind /sys /mnt/linux/sys
mount -o bind /proc /mnt/linux/proc
mount -o bind /dev /mnt/linux/dev

Wenn alles gemountet ist kann man mit chroot in die Umgebung wechseln:

chroot /mnt/linux /bin/bash

Wenn die Bootpartition extra ist muss in der chroot Umgebung die Bootpartition extra gemountet werden:

mount /dev/sda2 /boot

Jetzt kann man mit grub-install die GRUB Installation fixen:

grub-install /dev/sda