Archiv für den Monat: Juni 2018

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/