Schlagwort-Archive: ansible

Gitolite mit ansible-playbook post-update hook zum Verteilen von Konfigurationen etc.

Gitolite ist ein einfaches Script um einen GIT Repo Server aufzusetzen. Zusammen mit Ansible nutze ich es um z.B. Zonenfiles auf Bind9 Server zu verteilen. Das werde ich in diesem Beispiel zeigen. Ansible und Gitolite laufen auf dem gleichen Server.

Installation Gitolite

apt install gitolite3

Während der Installation wird nach dem Public SSH Key des Admins gefragt. Dieser Key ist später berechtigt das gitolite-admin Repo welches die Konfiguration der Repos beinhaltet zu benutzen.

Klonen von gitolite-admin und neues Repo dns anlegen

git clone gitolite3@10.1.1.1:gitolite-admin

gitolite-admin/conf/gitolite.conf

repo gitolite-admin
    RW+    =     admin

repo testing
    RW+    =     @all

repo dns
    option hook.post-update = dns-deploy
    RW+    =     admin

Hier wurde bereits ein post-update Hook definiert. Es müssen aber noch weitere Einstellungen angepasst werden damit dieser funktioniert.

Neue User legt man an in dem Verzeichnis gitolite-admin/keydir <username>.pub mit dem entsprechenden SSH Key anlegt. Die Usernamen können dann in der gitolite.conf verwendet werden. Ausführliche Doku zu gitolite -> http://gitolite.com/gitolite/index.html

Installation ansible 

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

Konfiguration ansible

Ich verwende auf den Zielsystemen eigene User um Dinge per Ansible zu deployen. Im Beispiel heist der „vader“.

/etc/ansible/hosts

[dns-servers]
ns1.example.com
ns2.example.com

/etc/ansible/group_vars/dns-servers

---
ansible_ssh_private_key_file: /etc/ansible/sshkey/id_rsa
ansible_ssh_user: vader
ansible_sudo_pass: lukeiamyourfather

SSH Key für vader erzeugen

mkdir -p /etc/ansible/sshkey
ssh-keygen -t rsa -f /etC/ansible/sshkey/id_rsa

Den SSH Key ohne Passwort ablegen.

Anpassen der Verzeichnis Rechte:

chmod 600 /etc/ansible/sshkey
chmod 600 /etc/ansible/sshkey/id_rsa
chmod 644 /etc/ansible/sshkey/id_rsa.pub
chown root:root -R /etc/ansible/sshkey

Auf den Zielsystemen muss jeweils der User vader mit dem Passwort eingerichtet werden und der Public Key des SSH Keys muss als authorized_key hinterlegt werden. Der User muss in der sudo Gruppe sein.

gitolite hook aktivieren

/var/lib/gitolite3/.gitolite.rc

Bei folgenden Zeilen Kommentierung entfernen.

LOCAL_CODE                =>  "$ENV{HOME}/local",

# allow repo-specific hooks to be added
            'repo-specific-hooks',

Verzeichnis anlegen für Hooks

mkdir -p /var/lib/gitolite3/local/hooks/repo-specific

dns-deploy hook anlegen

/var/lib/gitolite3/local/hooks/repo-specific/dns-deploy

sudo /opt/ansible/dns.sh

Owner und Rechte ändern

chown gitolite3:gitolite3 dns-deploy
chmod +x dns-deploy

Das Hook Script liegt in /opt/ansible:

mkdir -p /opt/ansible

/opt/ansible/dns.sh

#!/bin/bash


REPO=/opt/ansible/dns

if [ ! -d $REPO ]
then
   ssh-agent bash -c 'ssh-add /etc/ansible/sshkey/id_rsa; git clone gitolite3@10.1.1.1:dns'
else
    cd $REPO
    ssh-agent bash -c 'ssh-add /etc/ansible/sshkey/id_rsa; git pull'
fi


cd $REPO
ansible-playbook dns.yaml

# End

Das Hook Script klont das Repo dns nach /opt/ansible/dns und führt das ansible playbook aus dem Repo aus.

Anpassen der Rechte

chown root:root /opt/ansible/dns.sh
chmod 700 /opt/ansible/dns.sh

Den gitolite3 User berechtigen das Script ohne Passwort per Sudo auszuführen

/etc/sudoers

gitolite3 ALL = (root) NOPASSWD: /opt/ansible/dns.sh

Das Repo dns

git clone gitolite3@10.1.1.1:dns
cd dns

Anlegen des Playbooks

dns.yaml

---

- hosts: dns-servers

  tasks:

  - name: create or update named.conf.local
    become: true
    register: named
    template:
      src: /opt/ansible/dns/conf/named.conf.local.j2
      dest: /etc/bind/named.conf.local
    vars:
      zones:
      - lanbugs.de

  - name: create or update zone files
    become: true
    register: zones
    template: src={{ item }} dest=/etc/bind/{{ item | basename | regex_replace('\.j2','') }} owner=root group=root mode=0644
    with_fileglob:
      - /opt/ansible/dns/conf/db.*.j2

  - name: restart bind
    become: true
    service:
      name: bind9
      state: restarted
    when: zones.changed or named.changed

DNS Files

mkdir conf

named.conf.local.j2

{% for zone in zones %}
zone "{{ zone }}" {
    type master;
    file "/etc/bind/db.{{ zone}}";
};
{% endfor %}

db.lanbugs.de.j2

; BIND db file for lanbugs.de

$TTL 86400

{% include "zone_header.j2" %}

@               1800    IN      TXT     "v=spf1 a mx ptr ~all"

test            1800    IN      A       1.2.3.4


$ORIGIN lanbugs.de.

zone_header.j2

@       IN      SOA     ns1.example.com.      admin.example.com. (
                        2018111101      ; serial number YYMMDDNN
                        28800           ; Refresh
                        7200            ; Retry
                        864000          ; Expire
                        86400           ; Min TTL
                        )

@       1800    IN      NS      ns1.example.com.
@       1800    IN      NS      ns2.example.com.

@       1800    IN      MX      10 mail1.example.com.
@       1800    IN      MX      20 mail2.example.com.

Files zu dns Repo hinzufügen und pushen

git add -A
git commit -m "initial commit"

git push
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 311 bytes | 311.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Identity added: /etc/ansible/sshkey/id_rsa (Ansible vader)
remote: From 10.1.1.1:dns
remote:    63bf7b0..63d2dc5  master     -> origin/master
remote: Updating 63bf7b0..63d2dc5
remote: Fast-forward
remote:  conf/db.lanbugs.de.j2 | 1 -
remote:  1 file changed, 1 deletion(-)
remote: 
remote: PLAY [dns-servers] *************************************************************
remote: 
remote: TASK [Gathering Facts] *********************************************************
remote: ok: [ns1.example.com]
remote: ok: [ns2.example.com]
remote: 
remote: TASK [create or update named.conf.local] ***************************************
remote: ok: [ns1.example.com]
remote: ok: [ns2.example.com]
remote: 
remote: TASK [create or update zone files] *********************************************
remote: changed: [ns1.example.com] => (item=/opt/ansible/dns/conf/db.lanbugs.de.j2)
remote: changed: [ns2.example.comc] => (item=/opt/ansible/dns/conf/db.lanbugs.de.j2)
remote: 
remote: TASK [restart bind] ************************************************************
remote: changed: [ns1.example.com]
remote: changed: [ns2.example.com]
remote: 
remote: PLAY RECAP *********************************************************************
remote: ns1.example.com        : ok=4    changed=2    unreachable=0    failed=0   
remote: ns2.example.com        : ok=4    changed=2    unreachable=0    failed=0   
remote: 
To 10.1.1.1:/dns
   63bf7b0..63d2dc5  master -> master

Have fun 🙂

 

 

 

 

 

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ß 🙂