نصوص شل في Ansible

لنفترض أن أحد العملاء طلب منك المساعدة في ترحيل البرنامج النصي لنشر ملف sudoers مركزي على خوادم RHEL و AIX.



حسنًا ، هذا سيناريو شائع جدًا ، ويمكنك من خلال مثاله توضيح استخدام الميزات المتقدمة لـ Ansible ، وكذلك كيفية تغيير النهج - من برنامج نصي يؤدي مهمة معينة ، إلى وصف عاطفي (دون إجراء تغييرات) ومراقبة الامتثال لحالة المثيل.

خذ السيناريو:

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

هناك 212 سطرًا من التعليمات البرمجية ، مع عدم وجود تحكم في الإصدار في ملف sudoers. لدى العميل بالفعل عملية يتم تشغيلها مرة واحدة في الأسبوع ويتحقق من المجموع الاختباري للملف لضمان الأمان. على الرغم من أن البرنامج النصي يحتوي على إشارة إلى Solaris ، لهذا العميل لم يكن علينا نقل هذا المطلب.

لنبدأ بإنشاء دور ووضع ملف sudoers في Git للتحكم في الإصدار. من بين أشياء أخرى ، سيسمح لنا ذلك بالتخلص من الحاجة إلى تركيب وحدات تخزين NFS.

باستخدام معلمات التحقق والنسخ الاحتياطي لوحدات النسخ والقالب ، يمكننا التخلص من الحاجة إلى كتابة التعليمات البرمجية لإجراء نسخ احتياطي واستعادة الملف. في هذه الحالة ، يتم إجراء التحقق من الصحة قبل وضع الملف في نقطة الوجهة ، وإذا فشلت عملية التحقق من الصحة ، فإن الوحدة النمطية تلقي خطأ.

لكل دور ، نحتاج إلى تحديد المهام والقوالب والمتغيرات. هنا هو هيكل الملف المقابل:



يحتوي ملف نصي الدور (playbook) ، sudoers.yml ، على بنية بسيطة:

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

توجد متغيرات الدور في ملف vars / main.yml . يحتوي هذا الملف على ملف المجموع الاختباري ويتضمن / يستثني التوجيهات التي سيتم استخدامها لإنشاء منطق خاص لتخطي مضيفي Lawson وتضمين ملف sudoers.d فقط في مضيفي hd.

فيما يلي محتويات ملف vars / main.yml :

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

إذا استخدمنا وحدات النسخ و lineinfile ، فلن يكون الدور ملائماً. ستقوم وحدة النسخ بتثبيت الملف الأساسي ، وسيشمل ملف lineinfile كل مرة يبدأ تشغيله. نظرًا لأن هذا الدور سيتم إطلاقه في برج أنسبل ، فإن الشغور أمر لا بد منه. سنقوم بتحويل الملف إلى قالب jinja2.

في السطر الأول ، نضيف الأمر التالي للتحكم في المسافات والمسافات البادئة :

#jinja2: lstrip_blocks: True, trim_blocks: True

لاحظ أن الإصدارات الأحدث من الوحدة النمطية للقالب تتضمن معلمات لكتل trim_blocks (أضيفت في Ansible 2.4).

إليك الرمز الذي يُدرج سطر التضمين في نهاية الملف:

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

نستخدم البنية الشرطية ({٪ if٪} ، {٪ endif٪}) لأمر shell الذي يقوم بإدراج سطر للمضيفين الذين تبدأ أسماؤهم بالحرف "hd". نستخدم حقائق Ansible وفلتر [0: 2] لتحليل اسم المضيف.

الآن دعنا ننتقل إلى المهام. أولاً ، تحتاج إلى إنشاء حقيقة لتحليل اسم المضيف. سوف نستخدم حقيقة "parhost" في البناء الشرطي.

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

لا توجد معلمة csum على خادم مخزون RHEL. إذا لزم الأمر ، يمكننا استخدام حقيقة أخرى للإشارة المشروطة إلى اسم الملف الثنائي مع المجموع الاختباري. يرجى ملاحظة أنه قد تكون هناك حاجة إلى رمز إضافي إذا كانت هذه الميزات مختلفة على AIX و Solaris و Linux.

بالإضافة إلى ذلك ، يتعين علينا حل المشكلة مع الاختلافات في مجموعات الجذر على AIX و 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' }}"

باستخدام الكتل يسمح لنا بتعيين الشرط للمهمة بأكملها. سنستخدم الشرط في نهاية الكتلة لاستبعاد مضيفي "la".

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

وحدة القالب بالتحقق من صحة وتثبيت الملف. نصلح النتيجة حتى نتمكن من تحديد ما إذا كانت المهمة قد تغيرت. يتيح لك استخدام معلمة التحقق من الصحة في هذه الوحدة التحقق من صحة ملف sudoer الجديد قبل وضعه على المضيف.

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

إذا تم تثبيت قالب جديد ، قم بتشغيل البرنامج النصي shell لإنشاء ملف المجموع الاختباري. يقوم التحديث الشرطي بتحديث ملف المجموع الاختباري عند تثبيت قالب sudoers ، أو في حالة عدم وجود ملف المجموع الاختباري. نظرًا لأن عملية التشغيل تراقب أيضًا الملفات الأخرى ، فإننا نستخدم كود shell الموضح في البرنامج النصي المصدر:

- 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

تتحقق وحدة الملفات من تثبيت الأذونات اللازمة:

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

نظرًا لأن المعلمة الاحتياطية لا توفر أي خيارات لمعالجة النسخ الاحتياطية السابقة ، فسوف يتعين علينا الاهتمام بإنشاء الشفرة المناسبة بأنفسنا. في المثال أدناه ، نستخدم المعلمة "تسجيل" وحقل "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 != ""

إتمام الحظر:

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

حالة الاستخدام المقصود هي لعب هذا الدور على برج أنسبل. يمكن تكوين تنبيهات برج Ansible بحيث في حالة حدوث فشل في تنفيذ المهمة ، سيتم إرسال التنبيهات إلى البريد الإلكتروني أو Slack أو بطريقة أخرى. يعمل هذا الدور في Ansible أو Ansible Engine أو Ansible Tower.

نتيجةً لذلك ، أزلنا كل شيء غير ضروري من البرنامج النصي ، وقمنا بإنشاء دور متعطش تمامًا يمكن أن يوفر الحالة المطلوبة لملف sudoers. يتيح استخدام SCM التحكم في الإصدار ، ويوفر إدارة أكثر فعالية للتغيير والشفافية. تتيح لك CI / CD مع Jenkins أو أدوات أخرى إعداد اختبار تلقائي لرمز Ansible للتغييرات المستقبلية. يسمح لك دور Auditor في Ansible Tower بمراقبة وتطبيق المتطلبات التنظيمية.

يمكن إزالة رمز العمل مع المجموع الاختباري من البرنامج النصي ، ولكن لهذا العميل سيحتاج أولاً إلى استشارة خدمة الأمان الخاصة به. إذا لزم الأمر ، يمكن حماية نمط sudoers مع Ansible Vault. أخيرًا ، استخدام المجموعات يتجنب كتابة المنطق باستخدام ويشمل ويستبعد.

→ يمكنك تنزيل الدور من GitHub على هذا الرابط

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


All Articles