Skrip shell dalam Ansible

Misalkan pelanggan meminta Anda untuk membantu dengan migrasi skrip untuk menyebarkan file sudoers terpusat pada server RHEL dan AIX.



Nah, ini adalah skenario yang cukup umum, dan dengan contohnya Anda dapat menunjukkan penggunaan fitur lanjutan dari Ansible, serta bagaimana pendekatannya berubah - dari skrip yang melakukan tugas tertentu, hingga deskripsi idempoten (tanpa membuat perubahan) dan memantau kepatuhan dengan keadaan instance.

Ambil skripnya:

#!/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 

Ada 212 baris kode, tanpa kontrol versi di file sudoers. Pelanggan sudah memiliki proses yang berjalan seminggu sekali dan memeriksa checksum file untuk memastikan keamanan. Meskipun skrip berisi referensi ke Solaris, untuk pelanggan ini kami tidak harus mentransfer persyaratan ini.

Mari kita mulai dengan membuat peran dan meletakkan file sudoers di Git untuk kontrol versi. Antara lain, ini akan memungkinkan kita untuk menyingkirkan kebutuhan untuk me-mount volume NFS.

Dengan parameter validasi dan cadangan untuk modul salin dan templat , kita dapat menghilangkan kebutuhan untuk menulis kode untuk membuat cadangan dan memulihkan file. Dalam hal ini, validasi dilakukan sebelum file ditempatkan di titik tujuan, dan jika validasi gagal, modul melempar kesalahan.

Untuk setiap peran, kita perlu menentukan tugas, templat, dan variabel. Berikut adalah struktur file yang sesuai:



File skrip peran (playbook), sudoers.yml , memiliki struktur sederhana:

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

Variabel peran terletak di file vars / main.yml . File ini berisi file checksum dan termasuk / tidak termasuk arahan yang akan digunakan untuk membuat logika khusus untuk melewati host Lawson dan menyertakan file sudoers.d hanya di host hd.

Berikut ini isi dari file vars / main.yml :

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

Jika kita menggunakan modul copy dan lineinfile , maka perannya tidak akan idempoten. Modul copy akan menginstal file dasar, dan lineinfile akan memasukkan kembali setiap kali dimulai. Karena peran ini akan diluncurkan di Ansible Tower , idempotensi adalah suatu keharusan. Kami akan mengonversi file ke templat jinja2.

Di baris pertama, kami menambahkan perintah berikut untuk mengontrol spasi dan indentasi :

#jinja2: lstrip_blocks: True, trim_blocks: True

Perhatikan bahwa versi yang lebih baru dari modul template menyertakan parameter untuk trim_blocks (ditambahkan pada Ansible 2.4).

Berikut adalah kode yang menyisipkan baris sertakan di akhir file:

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

Kami menggunakan konstruksi kondisional ({% if%}, {% endif%}) untuk perintah shell yang menyisipkan baris untuk host yang namanya dimulai dengan karakter "hd". Kami menggunakan Fakta yang mungkin dan filter [0: 2] untuk mengurai nama host.

Sekarang mari kita beralih ke tugas. Pertama, Anda perlu menetapkan fakta untuk parsing nama host. Kami akan menggunakan fakta "parhost" dalam konstruk bersyarat.

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

Tidak ada parameter csum di server stok RHEL. Jika perlu, kita bisa menggunakan fakta lain untuk menunjukkan secara kondisional nama file biner dengan checksum. Harap dicatat bahwa kode tambahan mungkin diperlukan jika fitur-fitur ini berbeda pada AIX, Solaris, dan Linux.

Selain itu, kami harus menyelesaikan masalah dengan perbedaan dalam grup root pada AIX dan RHEL.

##
# 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' }}"

Menggunakan blok memungkinkan kita untuk mengatur kondisi untuk seluruh tugas. Kami akan menggunakan kondisi di akhir blok untuk mengecualikan host "la".

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

Modul template memvalidasi dan menginstal file. Kami memperbaiki hasilnya sehingga kami dapat menentukan apakah tugas telah berubah. Menggunakan parameter validasi dalam modul ini memungkinkan Anda memverifikasi keabsahan file sudoer baru sebelum menempatkannya di host.

##
# 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

Jika templat baru telah diinstal, jalankan skrip shell untuk menghasilkan file checksum. Konstruk bersyarat memperbarui file checksum ketika menginstal templat sudoers, atau jika file checksum hilang. Karena proses yang berjalan juga memantau file lain, kami menggunakan kode shell yang disediakan dalam skrip sumber:

- 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

Modul file memverifikasi pemasangan izin yang diperlukan:

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

Karena parameter cadangan tidak menyediakan opsi apa pun untuk memproses cadangan sebelumnya, kami harus mengurus sendiri pembuatan kode yang sesuai. Dalam contoh di bawah ini, kami menggunakan parameter "register" dan bidang "stdout_lines" untuk ini.

##
# 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 != ""

Penyelesaian Blok:

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

Kasing yang dimaksudkan adalah memainkan peran ini di Ansible Tower. Peringatan Menara yang Mungkin Dikonfigurasi sehingga jika terjadi kegagalan dalam pelaksanaan pekerjaan, peringatan akan dikirim ke email, ke Slack, atau dengan cara lain. Peran ini berjalan di Ansible, Ansible Engine, atau Ansible Tower.

Sebagai hasilnya, kami menghapus semua yang tidak perlu dari skrip dan menciptakan peran yang benar-benar idempoten yang dapat memberikan keadaan yang diinginkan dari file sudoers. Menggunakan SCM memungkinkan kontrol versi, memberikan manajemen perubahan dan transparansi yang lebih efisien. CI / CD dengan Jenkins atau alat lain memungkinkan Anda mengatur pengujian otomatis atas kode yang mungkin untuk perubahan di masa mendatang. Peran Auditor di Ansible Tower memungkinkan Anda untuk memantau dan menegakkan persyaratan organisasi.

Kode untuk bekerja dengan checksum dapat dihapus dari skrip, tetapi untuk ini pelanggan harus terlebih dahulu berkonsultasi dengan layanan keamanannya. Jika perlu, pola sudoers dapat dilindungi dengan Vault Ansible. Akhirnya, menggunakan grup menghindari menulis logika menggunakan include dan exclude.

→ Anda dapat mengunduh peran dari GitHub di tautan ini

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


All Articles