Shell-Skripte in Ansible

Angenommen, ein Kunde hat Sie gebeten, bei der Skriptmigration für die Bereitstellung einer zentralisierten Sudoers-Datei auf RHEL- und AIX-Servern zu helfen.



Nun, dies ist ein sehr häufiges Szenario, und anhand seines Beispiels können Sie die Verwendung der erweiterten Funktionen von Ansible sowie die Änderungen des Ansatzes demonstrieren - von einem Skript, das eine bestimmte Aufgabe ausführt, bis zu einer idempotenten Beschreibung (ohne Änderungen vorzunehmen) und der Überwachung der Einhaltung des Status der Instanz.

Nimm das Drehbuch:

#!/bin/sh # Desc: Distribute unified copy of /etc/sudoers # # $Id: $ #set -x export ODMDIR=/etc/repos # # perform any cleanup actions we need to do, and then exit with the # passed status/return code # clean_exit() { cd / test -f "$tmpfile" && rm $tmpfile exit $1 } #Set variables PROG=`basename $0` PLAT=`uname -s|awk '{print $1}'` HOSTNAME=`uname -n | awk -F. '{print $1}'` HOSTPFX=$(echo $HOSTNAME |cut -c 1-2) NFSserver="nfs-server" NFSdir="/NFS/AIXSOFT_NFS" MOUNTPT="/mnt.$$" MAILTO="unix@company.com" DSTRING=$(date +%Y%m%d%H%M) LOGFILE="/tmp/${PROG}.dist_sudoers.${DSTRING}.log" BKUPFILE=/etc/sudoers.${DSTRING} SRCFILE=${MOUNTPT}/skel/sudoers-uni MD5FILE="/.sudoers.md5" echo "Starting ${PROG} on ${HOSTNAME}" >> ${LOGFILE} 2>&1 # Make sure we run as root runas=`id | awk -F'(' '{print $1}' | awk -F'=' '{print $2}'` if [ $runas -ne 0 ] ; then echo "$PROG: you must be root to run this script." >> ${LOGFILE} 2>&1 exit 1 fi case "$PLAT" in SunOS) export PINGP=" -t 7 $NFSserver " export MOUNTP=" -F nfs -o vers=3,soft " export PATH="/usr/sbin:/usr/bin" echo "SunOS" >> ${LOGFILE} 2>&1 exit 0 ;; AIX) export PINGP=" -T 7 $NFSserver 2 2" export MOUNTP=" -o vers=3,bsy,soft " export PATH="/usr/bin:/etc:/usr/sbin:/usr/ucb:/usr/bin/X11:/sbin:/usr/java5/jre/bin:/usr/java5/bin" printf "Continuing on AIX...\n\n" >> ${LOGFILE} 2>&1 ;; Linux) export PINGP=" -t 7 -c 2 $NFSserver" export MOUNTP=" -o nfsvers=3,soft " export PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin" printf "Continuing on Linux...\n\n" >> ${LOGFILE} 2>&1 ;; *) echo "Unsupported Platform." >> ${LOGFILE} 2>&1 exit 1 esac ## ## Exclude Lawson Hosts ## if [ ${HOSTPFX} = "la" ] then echo "Exiting Lawson host ${HOSTNAME} with no changes." >> ${LOGFILE} 2>&1 exit 0 fi ## ## * NFS Mount Section * ## ## Check to make sure NFS host is up printf "Current PATH is..." >> ${LOGFILE} 2>&1 echo $PATH >> $LOGFILE 2>&1 ping $PINGP >> $LOGFILE 2>&1 if [ $? -ne 0 ]; then echo " NFS server is DOWN ... ABORTING SCRIPT ... Please check server..." >> $LOGFILE echo "$PROG failed on $HOSTNAME ... NFS server is DOWN ... ABORTING SCRIPT ... Please check server ... " | mailx -s "$PROG Failed on $HOSTNAME" $MAILTO exit 1 else echo " NFS server is UP ... We will continue..." >> $LOGFILE fi ## ## Mount NFS share to HOSTNAME. We do this using a soft mount in case it is lost during a backup ## mkdir $MOUNTPT mount $MOUNTP $NFSserver:${NFSdir} $MOUNTPT >> $LOGFILE 2>&1 ## ## Check to make sure mount command returned 0. If it did not odds are something else is mounted on /mnt.$$ ## if [ $? -ne 0 ]; then echo " Mount command did not work ... Please check server ... Odds are something is mounted on $MOUNTPT ..." >> $LOGFILE echo " $PROG failed on $HOSTNAME ... Mount command did not work ... Please check server ... Odds are something is mounted on $MOUNTPT ..." | mailx -s "$PROG Failed on $HOSTNAME" $MAILTO exit 1 else echo " Mount command returned a good status which means $MOUNPT was free for us to use ... We will now continue ..." >> $LOGFILE fi ## ## Now check to see if the mount worked ## if [ ! -f ${SRCFILE} ]; then echo " File ${SRCFILE} is missing... Maybe NFS mount did NOT WORK ... Please check server ..." >> $LOGFILE echo " $PROG failed on $HOSTNAME ... File ${SRCFILE} is missing... Maybe NFS mount did NOT WORK ... Please check server ..." | mailx -s "$PROG Failed on $HOSTNAME" $MA ILTO umount -f $MOUNTPT >> $LOGFILE rmdir $MOUNTPT >> $LOGFILE exit 1 else echo " NFS mount worked we are going to continue ..." >> $LOGFILE fi ## ## * Main Section * ## if [ ! -f ${BKUPFILE} ] then cp -p /etc/sudoers ${BKUPFILE} else echo "Backup file already exists$" >> ${LOGFILE} 2>&1 exit 1 fi if [ -f "$SRCFILE" ] then echo "Copying in new sudoers file from $SRCFILE." >> ${LOGFILE} 2>&1 cp -p $SRCFILE /etc/sudoers chmod 440 /etc/sudoers else echo "Source file not found" >> ${LOGFILE} 2>&1 exit 1 fi echo >> ${LOGFILE} 2>&1 visudo -c |tee -a ${LOGFILE} if [ $? -ne 0 ] then echo "sudoers syntax error on $HOSTNAME." >> ${LOGFILE} 2>&1 mailx -s "${PROG}: sudoers syntax error on $HOSTNAME" "$MAILTO" << EOF Syntax error /etc/sudoers on $HOSTNAME. Reverting changes Please investigate. EOF echo "Reverting changes." >> ${LOGFILE} 2>&1 cp -p ${BKUPFILE} /etc/sudoers else # # Update checksum file # grep -v '/etc/sudoers' ${MD5FILE} > ${MD5FILE}.tmp csum /etc/sudoers >> ${MD5FILE}.tmp mv ${MD5FILE}.tmp ${MD5FILE} chmod 600 ${MD5FILE} fi echo >> ${LOGFILE} 2>&1 if [ "${HOSTPFX}" = "hd" ] then printf "\nAppending #includedir /etc/sudoers.d at end of file.\n" >> ${LOGFILE} 2>&1 echo "" >> /etc/sudoers echo "## Read drop-in files from /etc/sudoers.d (the # here does not mean a comment)" >> /etc/sudoers echo "#includedir /etc/sudoers.d" >> /etc/sudoers fi ## ## * NFS Un-mount Section * ## ## ## Unmount /mnt.$$ directory ## umount ${MOUNTPT} >> $LOGFILE 2>&1 if [ -d ${MOUNTPT} ]; then rmdir ${MOUNTPT} >> $LOGFILE 2>&1 fi ## ## Make sure that /mnt.$$ got unmounted ## if [ -f ${SRCFILE} ]; then echo " The umount command failed to unmount ${MOUNTPT} ... We will not force the unmount ..." >> $LOGFILE umount -f ${MOUNTPT} >> $LOGFILE 2>&1 if [ -d ${MOUNTPT} ]; then rmdir ${MOUNTPT} >> $LOGFILE 2>&1 fi else echo " $MOUNTPT was unmounted ... There is no need for user intervention on $HOSTNAME ..." >> $LOGFILE fi # # as always, exit cleanly # clean_exit 0 

Es gibt 212 Codezeilen ohne Versionskontrolle in der sudoers-Datei. Der Kunde hat bereits einen Prozess, der einmal pro Woche ausgeführt wird und die Prüfsumme der Datei überprüft, um die Sicherheit zu gewährleisten. Obwohl das Skript einen Verweis auf Solaris enthält, mussten wir diese Anforderung für diesen Kunden nicht übertragen.

Beginnen wir damit, eine Rolle zu erstellen und die sudoers-Datei zur Versionskontrolle in Git abzulegen. Auf diese Weise können wir unter anderem die Notwendigkeit beseitigen, NFS-Volumes bereitzustellen.

Mit den Parametern "validieren" und "sichern" für die Kopier- und Vorlagenmodule können wir das Schreiben von Code zum Sichern und Wiederherstellen der Datei vermeiden. In diesem Fall wird die Validierung durchgeführt, bevor die Datei am Zielpunkt abgelegt wird. Wenn die Validierung fehlschlägt, gibt das Modul einen Fehler aus.

Für jede Rolle müssen Aufgaben, Vorlagen und Variablen angegeben werden. Hier ist die Struktur der entsprechenden Datei:



Die Rollenskriptdatei (Playbook) sudoers.yml hat eine einfache Struktur:

---
##
# Role playbook
##
- hosts: all
roles:
- sudoers
...

Rollenvariablen befinden sich in der Datei vars / main.yml . Diese Datei enthält die Prüfsummendatei und die Anweisungen include / exclude, mit denen eine spezielle Logik zum Überspringen der Lawson-Hosts erstellt und die Datei sudoers.d nur in die HD-Hosts aufgenommen wird.

Hier ist der Inhalt der Datei vars / main.yml :

---
MD5FILE: /root/.sudoer.md5
EXCLUDE: la
INCLUDE: hd
...

Wenn wir die Module copy und lineinfile verwenden , ist die Rolle nicht idempotent. Das Kopiermodul installiert die Basisdatei und lineinfile fügt include bei jedem Start erneut ein. Da diese Rolle auf Ansible Tower gestartet wird, ist Idempotenz ein Muss. Wir werden die Datei in eine jinja2-Vorlage konvertieren.

In der ersten Zeile fügen wir den folgenden Befehl hinzu, um Leerzeichen und Einrückungen zu steuern :

#jinja2: lstrip_blocks: True, trim_blocks: True

Beachten Sie, dass neuere Versionen des Vorlagenmoduls Parameter für trim_blocks enthalten (hinzugefügt in Ansible 2.4).

Hier ist der Code, der die Include- Zeile am Ende der Datei einfügt:

{% if ansible_hostname[0:2] == INCLUDE %}
#includedir /etc/sudoers.d
{% endif %}

Wir verwenden das bedingte Konstrukt ({% if%}, {% endif%}) für den Shell-Befehl, der eine Zeile für Hosts einfügt, deren Namen mit den Zeichen "hd" beginnen. Wir verwenden Ansible-Fakten und einen Filter [0: 2], um den Hostnamen zu analysieren.

Kommen wir nun zu den Aufgaben. Zunächst müssen Sie eine Tatsache zum Parsen des Hostnamens festlegen. Wir werden die Tatsache "parhost" im bedingten Konstrukt verwenden.

---
##
# Parse hostnames to grab 1st 2 characters
##
- name: "Parse hostname's 1st 2 characters"
set_fact: parhost={{ ansible_hostname[0:2] }}

Auf dem RHEL- Aktienserver gibt es keinen csum- Parameter. Bei Bedarf können wir eine andere Tatsache verwenden, um den Namen der Binärdatei mit der Prüfsumme bedingt anzugeben. Bitte beachten Sie, dass möglicherweise zusätzlicher Code erforderlich ist, wenn sich diese Funktionen unter AIX, Solaris und Linux unterscheiden.

Außerdem müssen wir das Problem mit Unterschieden in den Stammgruppen unter AIX und RHEL beheben.

##
# Conditionally set name of checksum binary
##
- name: "set checksum binary"
set_fact:
csbin: "{{ 'cksum' if (ansible_distribution == 'RedHat') else 'csum' }}"

##
# Conditionally set name of root group
##
- name: "set system group"
set_fact:
sysgroup: "{{ 'root' if (ansible_distribution == 'RedHat') else 'sys' }}"

Durch die Verwendung von Blöcken können wir die Bedingung für die gesamte Aufgabe festlegen. Wir werden die Bedingung am Ende des Blocks verwenden, um "la" -Hosts auszuschließen.

##
# Enclose in block so we can use parhost to exclude hosts
##
- block:

Das Vorlagenmodul überprüft und installiert die Datei. Wir korrigieren das Ergebnis, damit wir feststellen können, ob sich die Aufgabe geändert hat. Mit dem Parameter validate in diesem Modul können Sie die Gültigkeit der neuen sudoer-Datei überprüfen, bevor Sie sie auf dem Host ablegen.

##
# Validate will prevent bad files, no need to revert
# Jinja2 template will add include line
##
- name: Ensure sudoers file
template:
src: sudoers.j2
dest: /etc/sudoers
owner: root
group: "{{ sysgroup }}"
mode: 0440
backup: yes
validate: /usr/sbin/visudo -cf %s
register: sudochg

Wenn eine neue Vorlage installiert wurde, führen Sie das Shell-Skript aus, um die Prüfsummendatei zu generieren. Das bedingte Konstrukt aktualisiert die Prüfsummendatei, wenn die sudoers-Vorlage installiert wird oder wenn die Prüfsummendatei fehlt. Da der laufende Prozess auch andere Dateien überwacht, verwenden wir den im Quellenskript bereitgestellten Shell-Code:

- name: sudoers checksum
shell: "grep -v '/etc/sudoers' {{ MD5FILE }} > {{ MD5FILE }}.tmp ; {{ csbin }} /etc/sudoers >> {{ MD5FILE }} ; mv {{ MD5FILE }}.tmp {{ MD5FILE }}"
when: sudochg.changed or MD5STAT.exists == false

Das Dateimodul überprüft die Installation der erforderlichen Berechtigungen:

- name: Ensure MD5FILE permissions
file:
path: "{{ MD5FILE }}"
owner: root
group: "{{ sysgroup }}"
mode: 0600
state: file

Da der Sicherungsparameter keine Optionen für die Verarbeitung früherer Sicherungen bietet, müssen wir uns darum kümmern, den entsprechenden Code selbst zu erstellen. Im folgenden Beispiel verwenden wir dazu den Parameter "register" und das Feld "stdout_lines".

##
# List and clean up backup files. Retain 3 copies.
##
- name: List /etc/sudoers.*~ files
shell: "ls -t /etc/sudoers*~ |tail -n +4"
register: LIST_SUDOERS
changed_when: false

- name: Cleanup /etc/sudoers.*~ files
file:
path: "{{ item }}"
state: absent
loop: "{{ LIST_SUDOERS.stdout_lines }}"
when: LIST_SUDOERS.stdout_lines != ""

Blockvervollständigung:

##
# This conditional restricts what hosts this block runs on
##
when: parhost != EXCLUDE
...

Der beabsichtigte Anwendungsfall besteht darin, diese Rolle auf Ansible Tower zu spielen. Ansible Tower-Warnungen können so konfiguriert werden, dass im Falle eines Fehlers bei der Jobausführung Warnungen an E-Mail, an Slack oder auf andere Weise gesendet werden. Diese Rolle wird in Ansible, Ansible Engine oder Ansible Tower ausgeführt.

Infolgedessen haben wir alles Überflüssige aus dem Skript entfernt und eine vollständig idempotente Rolle erstellt, die den gewünschten Status der sudoers-Datei bereitstellen kann. Die Verwendung von SCM ermöglicht die Versionskontrolle, bietet ein effizienteres Änderungsmanagement und Transparenz. Mit CI / CDs mit Jenkins oder anderen Tools können Sie automatisierte Tests von Ansible-Code für zukünftige Änderungen einrichten. Mit der Auditor-Rolle in Ansible Tower können Sie organisatorische Anforderungen überwachen und durchsetzen.

Der Code für die Arbeit mit Prüfsummen könnte aus dem Skript entfernt werden. Dazu müsste der Kunde jedoch zuerst seinen Sicherheitsdienst konsultieren. Bei Bedarf kann das Sudoers-Muster mit Ansible Vault geschützt werden. Schließlich wird durch die Verwendung von Gruppen das Schreiben von Logik mithilfe von Ein- und Ausschlüssen vermieden.

→ Sie können die Rolle von GitHub unter diesem Link herunterladen

Source: https://habr.com/ru/post/de433792/


All Articles