Archiv des Autors: Maximilian Thoma

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

 

 

Festplatte aus einem kaputten Seagate NAS System mounten – EXT4 mit 64k Blocksize mounten

Neulich hat mir ein Kunde eine kaputte Seagate NAS gebracht, eine der Disks hatte ein Problem, die NAS hat nicht mehr zuverlässig gebootet.

Ich habe die Disks in meinen Linux PC eingebaut, mit mdadm –scan –assemble hat er die Software RAIDs gefunden.

Auf ein Problem bin ich dann doch gestoßen, ein Linux mit Standardkernel kann kein Filesystem mit einer Blocksize von 65536 (64k) mounten.

Dmesg gibt folgende Fehlermeldung aus:

EXT4-fs bad block size 65536

Ich habe dazu einen Artikel gefunden wie man dennoch die Platte im Lesemodus mounten kann.

Die Blocksize des Filesystems kann man sich mit

tune2fs -l /dev/mapper/vg8-lv8

anzeigen lassen.

Mit folgendem Kommando lässt sich die Disk als Readonly mounten:

fuseext2 -o ro -o sync_read /dev/vg8/lv8 /mnt

Sollte das fuseext2 Paket nicht installiert sein lässt es sich mit apt install fuseext2 installieren.

Quelle: https://john-hunt.com/2013/04/25/recovering-data-from-a-wd-mybook-live-2tb-3tbor-similar/

Python: Arbeiten mit YAML Files

YAML ist eine vereinfachte Auszeichnungssprache zur Datenserialisierung. Das Format ist einfach gehalten und ist eine schöne alternative zu XML, JSON usw. Ist auch ein super Format für Configfiles als alternative zu INI Files.

Einfaches YAML File:

variable_str: value
variable_num: 33

list:
  - a
  - b
  - c
  - d

dictionary:
  - list1:
    - a
    - b
    - c
  - list2:
    - a
    - b
    - c

block: |
    block ..................
    block ..................
    block ..................
    block ..................
    block ..................
    block ..................
    block ..................

Testscript:

import yaml
from pprint import pprint

with open("test3.yaml", "r") as f:
    x = yaml.load(f.read())

print "RAW Data:"
pprint(x)
print "----"
print "Block:"
print x['block']
print "Dictionary:"
print x['dictionary']
print "List:"
print x['list']
print "Var String:"
print x['variable_str']
print "Var Num:"
print x['variable_num']

Ausgabe:

RAW Data:
{'block': 'block ..................\nblock ..................\nblock ..................\nblock ..................\nblock ..................\nblock ..................\nblock ..................\n',
 'dictionary': [{'list1': ['a', 'b', 'c']}, {'list2': ['a', 'b', 'c']}],
 'list': ['a', 'b', 'c', 'd'],
 'variable_num': 33,
 'variable_str': 'value'}
----
Block:
block ..................
block ..................
block ..................
block ..................
block ..................
block ..................
block ..................

Dictionary:
[{'list1': ['a', 'b', 'c']}, {'list2': ['a', 'b', 'c']}]
List:
['a', 'b', 'c', 'd']
Var String:
value
Var Num:
33

Python Dictionarys / Listen etc. „dumpen“ in ein YAML File:

import yaml

t = {
    "bubu": {
            "a": "a",
            "b": "b",
            "c": "c",
            "d": "d",
            "e": "e",
            },
    "baba": {
            "a": "a",
            "b": "b",
            "c": "c",
            "d": "d",
            "e": "e",
            }
    }

with open('foobar.yaml', 'w') as f:
    yaml.dump(t, f, default_flow_style=False)

Ausgabe:

bubu:
  a: a
  b: b
  c: c
  d: d
  e: e
baba:
  a: a
  b: b
  c: c
  d: d
  e: e

 

 

Python: Entfernen von Steuerzeichen in ASCII Strings

Bin heute wieder mal über ein Problem mit ASCII Strings gestolpert. Im String war ein CTRL+C welches als \x03 in der ASCII Tabelle ist. Ich bin auf eine Seite gestoßen welche für alle möglichen Script und Programmiersprachen Codesnippets bereitstellt um Steuerzeichen zu entfernen.

ASCII Tabelle bei Wikipedia: https://de.wikipedia.org/wiki/American_Standard_Code_for_Information_Interchange

Hier für Python:

stripped = lambda s: "".join(i for i in s if 31 < ord(i) < 127)
 
print(stripped("\ba\x00b\n\rc\fd\xc3"))

Output:

abcd

Quelle: https://rosettacode.org/wiki/Strip_control_codes_and_extended_characters_from_a_string

Alternativ kann man auch einzelne ASCII Codes ersetzen mit der Regex Library

Beispiel:

import re

data = "ab\x03cd"
data_clean = re.sub(r"\x03","",data)

print data

abcd

Viel Spaß 🙂

Cisco/Python: Backup der Konfiguration bei write Event auf externen Server

Diverse Cisco Geräte können bei einem write Event die Konfiguration an einen anderen Server z.B. über HTTP pushen.

Cisco Config:

archive
 path http://1.2.3.4/cisco_config/put/$h-$t
 write-memory

Apache /etc/httpd/conf.d/zzz_cisco_backup.conf:

WSGIDaemonProcess cisco_backup user=apache group=apache threads=10
WSGIPythonPath /opt/cisco_backup/web_root
WSGIScriptAlias /cisco_backup /opt/cisco_backup/web_root/cisco_backup.wsgi

<Directory /opt/cisco_backup/web_root>
WSGIProcessGroup cisco_backup
WSGIApplicationGroup %{GLOBAL}
WSGIScriptReloading On
Order deny,allow
Allow from all

<Files cisco_backup.py>
Require all granted
</Files>
<Files cisco_backup.wsgi>
Require all granted
</Files>

</Directory>

cisco_backup.wsgi File:

import sys

sys.path.append("/opt/cisco_backup/web_root")

from cisco_backup import app as application

cisco_backup.py File:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from flask import Flask
from flask import request

app = Flask(__name__)

@app.route("/put/<cfg>", methods=['PUT'])
def get_config(cfg):
   with open('/opt/cisco_config/incoming_configs/%s' % cfg, "wb") as f:
      f.write(request.data)
   return "ok"

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

Viel Spaß 😉

Python: Snippet – E-Mail versenden, alternative zu Mailer

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import smtplib
from email.mime.text import MIMEText


def postmaster(mfrom, mto, msubject, message, smtphost):

    msg = MIMEText(message.encode("utf-8"))
    msg['Subject'] = msubject
    msg['From'] = mfrom
    msg['To'] = mto

    s = smtplib.SMTP(smtphost)
    s.sendmail(msg['From'], msg['To'], msg.as_string())
    s.quit()

 

Python: Snippet: SSH shell on Cisco devices

Mit dem Snippet können Kommandos auf einer Cisco Shell via SSH ausgeführt werden.

#!/usr/bin/env python

import paramiko
import sys


def send_string_and_wait_for_string(command, wait_string, should_print):
    shell.send(command)
  
    receive_buffer = ""

    while not wait_string in receive_buffer:
        receive_buffer += shell.recv(1024)

    if should_print:
        print receive_buffer

    return receive_buffer

client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect("10.62.62.10", username="testuser", password="testpasswd", look_for_keys=False, allow_agent=False)

shell = client.invoke_shell()
send_string_and_wait_for_string("", "#", False)
send_string_and_wait_for_string("terminal length 0\n", "#", False)
output=send_string_and_wait_for_string("show logging\n", "#", False)
print output
client.close()

Mehr Infos / Quelle: http://blog.timmattison.com/archives/2014/06/25/automating-cisco-switch-interactions/

NGINX Reverse Proxy für Exchange 2016

Folgende Pakete müssen auf einem Ubuntu 16.04 LTS installiert werden:

apt install nginx nginx-extras

Die Konfiguration wird abgelegt unter /etc/nginx/conf.d/exchange.conf

Folgende Dinge müssen angepasst werden:

  • DNS Name unter dem OWA etc. erreichbar sein soll z.B. mail.example.org
  • Autodiscover DNS Name z.B. autodiscover.example.org
  • Interner Exchange Server z.B. exchange-server.example.internal
server {
    listen 80;
    server_name mail.example.org autodiscover.example.org;
    return 301 https://$host$request_uri;
}

server {
    tcp_nodelay on;
    listen 443;
    ssl                     on;
    ssl_certificate /etc/ssl/certs/star.example.org.pem;
    ssl_certificate_key /etc/ssl/private/star.example.org.key;

    ssl_session_timeout     5m;
    server_name mail.example.org;

    location / {
            return 301 https://mail.example.org/owa;
    }

    proxy_http_version      1.1;
    proxy_read_timeout      360;
    proxy_pass_header       Date;
    proxy_pass_header       Server;
    proxy_pass_header       Authorization;
    proxy_set_header        Host $host;
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_set_header        X-Forwarded-For  $proxy_add_x_forwarded_for;
    proxy_pass_request_headers on;
    more_set_input_headers 'Authorization: $http_authorization';
    proxy_set_header Accept-Encoding "";
    more_set_headers -s 401 'WWW-Authenticate: Basic realm="exchange-server.example.internal"';
    # proxy_request_buffering off;
    proxy_buffering off;
    proxy_set_header Connection "Keep-Alive";

    location ~* ^/owa { proxy_pass https://exchange-server.example.internal; }
    location ~* ^/Microsoft-Server-ActiveSync { proxy_pass https://exchange-server.example.internal; }
    location ~* ^/ecp { proxy_pass https://exchange-server.example.internal; }
    location ~* ^/rpc { proxy_pass https://exchange-server.example.internal; }
    location ~* ^/autodiscover { proxy_pass https://exchange-server.example.internal; }
    location ~* ^/oab { proxy_pass https://exchange-server.example.internal; }

    error_log /var/log/nginx/exchange-rproxy-ssl-error.log;
    access_log /var/log/nginx/exchange-rproxy-ssl-access.log;
}

Ob alles einwandfrei Funktioniert lässt sich über einen Webservice von Microsoft testen:

https://testconnectivity.microsoft.com/

 

Quellen:

Viel Spaß 😉

Check_MK: Problem mit Apache HTTP Proxy – SELinux blockt Reverse Proxy Verbindung zur Check_MK Instanz

Habe gerade auf ein frisch installiertes CentOS 7.4 Check_MK 1.4.0p19 installiert. Nach dem Start einer OMD Instanz kommt nur die Fehlermeldung:

OMD: Site Not Started

You need to start this site in order to access the web interface.

Im Apache Log ist folgendes zu sehen:

[Mon Dec 04 08:50:48.097245 2017] [proxy_http:error] [pid 20887] [client x.x.x.x:31372] AH01114: HTTP: failed to make connection to backend: 127.0.0.1, referer: http://server.example.net/extern/
[Mon Dec 04 08:50:56.943253 2017] [proxy:error] [pid 20883] (13)Permission denied: AH00957: HTTP: attempt to connect to 127.0.0.1:5000 (127.0.0.1) failed
[Mon Dec 04 08:50:56.943276 2017] [proxy:error] [pid 20883] AH00959: ap_proxy_connect_backend disabling worker for (127.0.0.1) for 0s
[Mon Dec 04 08:50:56.943280 2017] [proxy_http:error] [pid 20883] [client x.x.x.x:31408] AH01114: HTTP: failed to make connection to backend: 127.0.0.1

netstat – tulpen zeigt aber das das Backend läuft:

# netstat -tulpen
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       User       Inode      PID/Program name    
...
tcp        0      0 127.0.0.1:5000          0.0.0.0:*               LISTEN      997        71127      21004/httpd         
...

Ein Blick in das Audit Log verrät das SELinux zuschägt:

#tail -f /var/log/audit/audit.log
...
type=AVC msg=audit(1512377448.096:3647): avc:  denied  { name_connect } for  pid=20887 comm="httpd" dest=5000 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:commplex_main_port_t:s0 tclass=tcp_socket
type=SYSCALL msg=audit(1512377448.096:3647): arch=c000003e syscall=42 success=no exit=-13 a0=a a1=559b02b25650 a2=10 a3=7fffb621631c items=0 ppid=20882 pid=20887 auid=4294967295 uid=48 gid=48 euid=48 suid=48 fsuid=48 egid=48 sgid=48 fsgid=48 tty=(none) ses=4294967295 comm="httpd" exe="/usr/sbin/httpd" subj=system_u:system_r:httpd_t:s0 key=(null)
type=AVC msg=audit(1512377508.204:3689): avc:  denied  { name_connect } for  pid=21020 comm="httpd" dest=5000 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:commplex_main_port_t:s0 tclass=tcp_socket
type=SYSCALL msg=audit(1512377508.204:3689): arch=c000003e syscall=42 success=no exit=-13 a0=a a1=559b02b25650 a2=10 a3=7fffb621633c items=0 ppid=20882 pid=21020 auid=4294967295 uid=48 gid=48 euid=48 suid=48 fsuid=48 egid=48 sgid=48 fsgid=48 tty=(none) ses=4294967295 comm="httpd" exe="/usr/sbin/httpd" subj=system_u:system_r:httpd_t:s0 key=(null)
...

Das Problem kann temporär zum testen wie folgt gelöst werden:

/usr/sbin/setsebool httpd_can_network_connect 1

um die Änderung permanent zu übernehmen:

/usr/sbin/setsebool -P httpd_can_network_connect 1

 

Python: Snippet/Experiment – Syslog Server mit globalen und Host Filtern

Der Code ist nicht fertig und war mal ein Labor Versuch. Es lassen sich globale und Host Filter setzen wo diese zutreffen werden die Logs in ein extra File geschrieben.

Config file:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Config definition


class CFG:

    def __init__(self):

        # Path for logfiles
        self.syslogpath = "/home/mthoma/_dev/syslog/log/"

        # Listner Port
        self.port = 3702

        # Listner address
        self.host = "0.0.0.0"

        # Global Filter
        self.global_filter = {
            "filter": [
                ".*FOOBAR.*",
                ".*COFFEE.*"
            ]
        }

        # Host Filter
        self.host_filter = {
            "10.201.11.33": {
                "filter": [
                    ".*MACFLAP.*",
                    ".*BUBU.*",
                ]
            },
        }

Syslog Server:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Load config class
from config import CFG

# Load common classes
import re
import logging
import SocketServer
import socket
import os

# Load configuration file
C = CFG()

formatter = logging.Formatter('%(message)s')

def setup_logger(name, log_file, level=logging.INFO):
    handler = logging.FileHandler(log_file)
    handler.setFormatter(formatter)
    
    logger = logging.getLogger(name)
    logger.setLevel(level)
    logger.addHandler(handler)
    
    return logger


class SyslogUDPHandler(SocketServer.BaseRequestHandler):

    def handle(self):
        data = bytes.decode(self.request[0].strip())
        sockets = self.request[1]

        ip = str(self.client_address[0])
        
    # Try to resolve reverse record via DNS
        try:
            name, alias, addresslist = socket.gethostbyaddr(ip)
        except:
            name = ip
        
    # Set path
        path = C.syslogpath+name+"/"
        
    # Create path if not exist
        try:
            os.stat(path)
        except:
            os.mkdir(path)
        
        logger = setup_logger('normal_log', path+"log")
        logger.info(str(data))
        
        logger_sp = setup_logger('special_log', path+"spec")
        
        if ip in C.host_filter:
            filters = options['filter'] + C.global_filter['filter']
            filter_join = "|".join(filters)
            
            if re.match(r"%s" % filter_join, str(data)):
                logger_sp.info(str(data))
        else:
            filters = C.global_filter['filter']
            filter_join = "|".join(filters)
            
            if re.match(r"%s" % filter_join, str(data)):
                logger_sp.info(str(data))
                
        
        
        print "%s : " % self.client_address[0], str(data)

        logging.info(str(data))



if __name__ == "__main__":

    try:
        server = SocketServer.UDPServer((C.host,C.port), SyslogUDPHandler)
        server.serve_forever(poll_interval=0.5)

    except (IOError, SystemExit):
        raise

    except KeyboardInterrupt:
        print "Crtl+C Pressed. Shutting down."

 

Python: Snippet Multiprocessing mit Ergebnis

Beispiel für Parallelisierung von Jobs mit Ergebnis welche als Liste zurückgegeben werden.

#!/usr/bin/env python
# -*- encoding: utf-8; py-indent-offset: 4 -*-

import os
from multiprocessing import Pool


def worker(job):
    x, y = job
    result = x ** y
    return os.getpid(), result
  
if __name__ == '__main__':
    jobs = [(1, 2), (3, 4), (5, 6), (11, 12), (13, 14), (15, 16), (21, 22), (23, 24), (25, 26)]
    
    result_buffer = []
  
    pool = Pool(processes=5)
    
    for job in jobs:
        result_buffer.append(pool.apply_async(worker, args=(job,)))
    
    pool.close()
    pool.join()
  
    results = [r.get() for r in result_buffer]

    print results
  
    for pid, result in results:
        print "working pid was: %s" % pid
        print "result is: %s" % result
        print "---"

Beispiel Ergebnis:

$python mp_with_result.py

[(7992, 1), (7992, 81), (7992, 15625), (7992, 3138428376721L), (7992, 3937376385699289L), (7992, 6568408355712890625L), (7992, 122694327386105632949003612841L), (7992, 480250763996501976790165756943041L), (7992, 2220446049250313080847263336181640625L)]
working pid was: 7992
result is: 1
---
working pid was: 7992
result is: 81
---
working pid was: 7992
result is: 15625
---
working pid was: 7992
result is: 3138428376721
---
working pid was: 7992
result is: 3937376385699289
---
working pid was: 7992
result is: 6568408355712890625
---
working pid was: 7992
result is: 122694327386105632949003612841
---
working pid was: 7992
result is: 480250763996501976790165756943041
---
working pid was: 7992
result is: 2220446049250313080847263336181640625
---

 

Python: Experiment/Snippet – Komprimieren und löschen von Logfiles nach X Tagen

Ein Ansatz für Logverzeichnisse im Format /log/<yyyy>/<mm>/<dd>/<div. logsfiles>

#!/usr/bin/env python

import gzip
import shutil
import os
import datetime
import time

#############################################
# Config
#############################################

# Path of Logfiles
# Structure is /opt/log/<YYYY>/<MM>/<DD>/
gpath='/opt/log/'

# hold logs for x days
hold_time=180



#############################################

def get_immediate_subdirectories(a_dir):
    return [name for name in os.listdir(a_dir) if os.path.isdir(os.path.join(a_dir, name))]

def delete_files(f):
    # delete file if older than hold time
    nowx = time.time()

    for file in os.listdir(f):    
        if os.stat(f+file).st_mtime < nowx - hold_time * 86400:
            f_path = f+file
            print "delete %s " % f_path
            os.remove(f_path)
            
    try:
        os.rmdir(f)
    except:
        pass
            
    
    
def compress_files(lpath):
    # Compress files
    print "Working on: " + lpath
    obj = os.listdir(lpath)
    for f in obj:
        if os.path.isfile(lpath+f) and ".gz" not in f:
            with open(lpath+f,'rb') as f_in:
                with gzip.open(lpath+f+".gz",'wb') as f_out:
                    shutil.copyfileobj(f_in, f_out)
                    os.remove(lpath+f)
            
#compress everything which ist older than now

now = datetime.datetime.now()
years = get_immediate_subdirectories(gpath)

for year in years:

    # delete empty directories
    if not os.listdir(gpath+year):
        os.rmdir(gpath+year)
    else:

        months = get_immediate_subdirectories(gpath+year)
    
        for month in months:

            # delete empty directories
            if not os.listdir(gpath+year+"/"+month):
                os.rmdir(gpath+year+"/"+month)

            else:
                days = get_immediate_subdirectories(gpath+year+"/"+month)
                        
                # Remove current day from compressing & cleaning
                if month == str(now.month) and year == str(now.year):

                    if len(str(now.day)):
                        now_day = "0%s" % now.day
                    else:
                        now_day = str(now.day)

                    days.remove(now_day)
        
                for day in days:
                    # delete empty directories
                    if not os.listdir(gpath+year+"/"+month+"/"+day+"/"):
                        os.rmdir(gpath+year+"/"+month+"/"+day+"/")
                    else:
                        # compress all files in folder
                        compress_files(gpath+year+"/"+month+"/"+day+"/")
                        
                        # delete old files
                        delete_files(gpath+year+"/"+month+"/"+day+"/")

 

Python: Snippet – Kaputten UTF-8 String reparieren

Ich habe aus der Datenbank einen String zurückbekommen der UTF-8 war aber falsch kodiert zurückgegeben wurde. So wurde aus Geschäftsstelle -> Gesch├ñftsstelle

Folgendes Snippet kann einen kaputten UTF-8 String neu auf UTF-8 kodieren:

name_kaputt = 'Gesch\xc3\xa4ftsstelle'

name = ''.join(chr(ord(c)) for c in name_kaputt).decode("utf-8")

print name_kaputt
print name

Ergebnis:

Python 2.7.13 (v2.7.13:a06454b1afa1, Dec 17 2016, 20:42:59) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> name_kaputt = 'Gesch\xc3\xa4ftsstelle'
>>> name = ''.join(chr(ord(c)) for c in name_kaputt).decode("utf-8")
>>> print name_kaputt
Geschäftsstelle
>>> print name
Geschäftsstelle

 

 

Python: Snippet – Datum / Zeitstempel älter als X Tage z.B. 90 Tage

Mit dem Typ datetime lässt sich direkt rechnen was das ganze sehr bequem macht.

Beispiel:

import datetime

old_time = datetime.datetime(2016, 4, 11, 10, 57, 23)
today = datetime.datetime.today()

age = today - old_time

if age.days > 90:
    print "Older than 90 days"
else:
    print "Not older than 90 days"

Beispiel Datei Alter:

import datetime
import os

file_mod_time = datetime.datetime.fromtimestamp(os.path.getmtime('foobar.txt'))
today = datetime.datetime.today()

age = today - file_mod_time

if age.days > 90:
    print "File older than 90 days"
else:
    print "File not older than 90 days"

 

Check_MK: Automation via Web Service

Wenn man Dinge automatisieren möchte kann man das über die Webservices von Check_MK machen. Dazu gibt es einen schönen Artikel von Check_MK selbst: https://mathias-kettner.de/checkmk_multisite_automation.html

Leider ist es aus der URL nicht leicht rauszufinden welche Variablen man mitgeben muss um z.B. einen Service zu Ack’en. Es gibt aber einen Trick um das einfach herauszufinden.

In den Global Settings unter User Interface -> Debug mode aktivieren.

Danach gewünschte Aktion ausführen und sich die Debug Ausgabe ansehen:

Hier sind jetzt alle Variablen aufgelistet die bei dem Request übergeben werden.

Viel Spaß beim automatisieren 😉

Einfacher Random String Generator in Python

''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(N))

N = Anzahl der Stellen

z.B. Uppercase + Digits mit 16 Stellen:

OMD[dev1]:~$ python
Python 2.7.13 (default, Jul 24 2017, 12:14:45) 
[GCC 6.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information. 
>>> import string
>>> import random
>>> ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(16))
'J8J3D3UMASJ33B1M'

Quelle/Mehr: https://stackoverflow.com/questions/2257441/random-string-generation-with-upper-case-letters-and-digits-in-python

Subnetze und IP Adressen extrahieren aus SPF Records (z.B. Office365 oder Google Apps for Business)

Wenn man bei Office365 oder Google Apps for Business einen eigenen Mailserver (Postfix) vorschalten möchte beim versenden/empfangen muss man die Mailserver von Microsoft/Google Whitelisten in den mynetworks bei Postfix.

Das Script löst alle SPF Record includes auf und generiert CIDR Maps die sich in Postfix einbinden lassen.

Beispiel:

max@dev1:~$ python get_subnets_of_spf_record_mynetwoks.py
Working on job office365
Working on job google

Es werden 2 Files erzeugt:

max@dev1:~$ cat /etc/postfix/networks/google 
64.18.0.0/20 OK
64.233.160.0/19 OK
66.102.0.0/20 OK
66.249.80.0/20 OK
72.14.192.0/18 OK
74.125.0.0/16 OK
108.177.8.0/21 OK
173.194.0.0/16 OK
207.126.144.0/20 OK
209.85.128.0/17 OK
216.58.192.0/19 OK
216.239.32.0/19 OK
[2001:4860:4000::]/36 OK
[2404:6800:4000::]/36 OK
[2607:f8b0:4000::]/36 OK
[2800:3f0:4000::]/36 OK
[2a00:1450:4000::]/36 OK
[2c0f:fb50:4000::]/36 OK
172.217.0.0/19 OK
108.177.96.0/19 OK
max@dev1:~/test$ cat /etc/postfix/networks/office365
207.46.101.128/26 OK
207.46.100.0/24 OK
207.46.163.0/24 OK
65.55.169.0/24 OK
157.56.110.0/23 OK
157.55.234.0/24 OK
213.199.154.0/24 OK
213.199.180.0/24 OK
157.56.112.0/24 OK
207.46.51.64/26 OK
157.55.158.0/23 OK
64.4.22.64/26 OK
40.92.0.0/14 OK
40.107.0.0/17 OK
40.107.128.0/17 OK
134.170.140.0/24 OK
[2a01:111:f400::]/48 OK
23.103.128.0/19 OK
23.103.198.0/23 OK
65.55.88.0/24 OK
104.47.0.0/17 OK
23.103.200.0/21 OK
23.103.208.0/21 OK
23.103.191.0/24 OK
216.32.180.0/23 OK
94.245.120.64/26 OK
[2001:489a:2202::]/48 OK

In Posftix werden sie in der main.cf eingebunden:

# ----------------------------------------------------------------------
# My Networks
# ----------------------------------------------------------------------
mynetworks =
        cidr:/etc/postfix/networks/local
        cidr:/etc/postfix/networks/other
        cidr:/etc/postfix/networks/google
        cidr:/etc/postfix/networks/office365

Da sich zwischendurch die Records auch mal ändern können empfiehlt es sich einen Cronjob dafür einzurichten. Ich habe eine Variante mit diff die nur patcht wenn das Resultat nicht null ist.

Das Script lässt sich auch noch für andere Dienste / etc. anpassen:

lookup_spf = {
# Google Apps for Business
"google": {
          "domain": "google.com",
          "file"  : "/etc/postfix/networks/google",
          },

# Office365
"office365": {
          "domain": "spf.protection.outlook.com",
          "file"  : "/etc/postfix/networks/office365",
          },

# Example
"example": {
          "domain": "example.com",
          "file"  : "/etc/postfix/networks/example",
          },

}

Sourcecode:

#!/usr/bin/env python

#
# get_subnets_of_spf_record_mynetwoks.py
# Resolve all known ip addresses from spf record and generate cidr map for postfix
#
# Version 1.0
# Written by Maximilian Thoma (http://www.lanbugs.de)
#
# The generated files can be used in postfix config with for example mynetworks = cidr:/etc/postfix/<generated_file>
#
# This program is free software; you can redistribute it and/or modify it under the terms of the
# GNU General Public License as published by the Free Software Foundation;
# either version 2 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with this program;
# if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, 
# MA 02110, USA
#

#
# Requirements:
# dnspython module  -> pip install dnspython
#

import dns.resolver
from dns.exception import DNSException
import re
import sys

# Look for DNS Record at:
#
# "jobname": {
#            "domain": "domainname",
#            "file": "output_file",
#            }
#

# 

lookup_spf = {
# Google Apps for Business
"google": {
          "domain": "google.com",
          "file"  : "/etc/postfix/networks/google",
          },

# Office365
"office365": {
          "domain": "spf.protection.outlook.com",
          "file"  : "/etc/postfix/networks/office365",
          },
}

############################################################################################

def getspf(record, filehandler):
    # Init Resolver
    myResolver = dns.resolver.Resolver()

    try:
        # Try to lookup TXT record
        myAnswer = myResolver.query(record,"TXT")

    except DNSException:
        sys.stderr.write("Failed to query record, SPF broken.")
        return

    results = []

    for rdata in myAnswer:
        # Get string out of records
        for txt_string in rdata.strings:
            # Append to SPF Records buffer if "spf" in string
            if "spf" in txt_string:
                results.append(txt_string)

    # If results >=1
    if len(results) >= 1:
        # Work on records
        for spf in results:
            # Split parts
            parts = spf.split(" ")
            # Check parts
            for part in parts:

                s_include = re.match(r"^include:(?P<domain>.*)$", part)
                s_ip4 = re.match(r"^ip4:(?P<ip4>.*)$", part)
                s_ip6 = re.match(r"^ip6:(?P<ip6>.*)$", part)

                # If in part "include" found, next round
                if s_include:
                    getspf(s_include.group('domain'), filehandler)
                # elif ip4 found
                elif s_ip4:
                    filehandler.write(s_ip4.group('ip4') + " OK\n")
                # elif ip6 found
                elif s_ip6:
                    filehandler.write("[" + s_ip6.group('ip6').replace("/","]/") + " OK\n")
                # else no valid record
                else:
                    pass
    # no results 
    else:
        sys.stderr.write("No results")
        pass

def main():
    # Working on jobs
    for jobname, config in lookup_spf.iteritems():

        print "Working on job %s" % jobname

        # open file
        filehandler = open(config['file'], 'w')
        # start query spf records
        getspf(config['domain'], filehandler)
        # close file
        filehandler.close()


#getspf(lookup_spf)

if __name__ == "__main__":
    main()

 

oder

https://gist.github.com/lanbugs/b223e16ae52e681bb5c9401c36fd5f1a