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

 

 

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 😉