我有一個 Ansible 劇本,用於在我們的環境中管理 sudoers 文件。我們喜歡在 /etc/sudoers 中保留一個最小的 sudoers 文件,然後我們想要添加的任何內容都會放入 /etc/sudoers.d 下的單獨文件中。
我的 Ansible 劇本包含以下用於推送這些文件的任務:
- name: copy sudoers files
copy:
src: "{{ item }}"
dest: "/etc/sudoers.d/{{ item }}"
backup: yes
owner: root
group: root
mode: 0440
validate: /usr/sbin/visudo -cf %s
with_items:
- admins
- apache
- monitor
該任務包含一個驗證子句,用於在提交文件之前確保文件有效,這通常效果很好。然而,今天我遇到了一個更新破壞了 sudo 的問題。該文件通過了驗證步驟,但包含與主 /etc/sudoers 文件中的 User_Alias 名稱相同的 User_Alias。此後任何運行 sudo 的嘗試都會導致解析錯誤。
我的問題是 - 如何測試 Ansible 對 sudoers 檔案的更新,以捕獲此類錯誤?文件就位後,可以透過運行來捕獲錯誤visudo -c
,但將其放入驗證步驟中不起作用。 Ansible 需要%s
佔位符,即使沒有,驗證也會在將檔案複製到位之前完成,這樣visudo -c
就不會捕獲它。
答案1
你有沒有嘗試過這個:
- copy:
src: '{{ item }}'
dest: '/etc/sudoers.d/{{ item }}'
owner: root
group: root
mode: 0440
validate: 'bash -c "cat /etc/sudoers %s | visudo -cf-"'
這個對我有用。
答案2
我成功了。這就是我所做的。首先,我新增了一組 Ansible 任務,在 /etc/sudoers.stage.d 處建立一個暫存目錄,並將 /etc/sudoers.d 的內容複製到其中。然後,我將檔案上傳到此暫存區域,如果其中任何檔案發生更改,請執行自訂腳本來啟動它們。
這就是劇本中的邏輯現在的樣子
- name: delete staging area
file:
path: "/etc/sudoers.stage.d"
state: absent
changed_when: false
- name: copy /etc/sudoers.d to staging area
shell: "cp -rp /etc/sudoers.d /etc/sudoers.stage.d"
changed_when: false
- name: stage sudoers files
copy:
src: "{{item}}"
dest: "/etc/sudoers.stage.d/{{item}}"
backup: yes
owner: root
group: root
mode: 0440
validate: /usr/sbin/visudo -cf %s
with_items:
- admins
- apache
- monitor
register: sudoers_d
- block:
- name: push out activate script
copy:
src: activate_sudoers.sh
dest: /usr/local/bin/activate_sudoers.sh
owner: root
group: root
mode: 0700
- name: activate change
shell: /bin/sh /usr/local/bin/activate_sudoers.sh /etc/sudoers.stage.d
when: sudoers_d.changed
這是 activate_sudoers.sh 腳本的樣子。
#!/bin/sh
function usage {
echo "Usage: $0 <stage directory>" >&2
exit 1
}
function abort {
echo "*** Error detected" >&2
[ "$#" -gt 0 ] && echo "***" $@ >&2
exit 1
}
PATH=/usr/bin:/bin:/usr/sbin:/sbin
export PATH
test $# -eq 1 || usage
test -d "$1" || abort "Stage directory $1: missing or not a directory"
test -d /etc/sudoers.old.d && rm -rf /etc/sudoers.old.d
test -d /etc/sudoers.old.d && abort "Failed to remove /etc/sudoers.old.d"
mv /etc/sudoers.d /etc/sudoers.old.d \
&& mv "$1" /etc/sudoers.d \
&& visudo -c
if [ $? -eq 0 ]; then
# Success - clean up
rm -rf /etc/sudoers.old.d
exit 0
else
# Failure - roll back
rm -rf /etc/sudoers.d
mv /etc/sudoers.old.d /etc/sudoers.d
abort "sudoers update failed"
fi
它比我希望的要長一些,也更複雜一些,但它完成了工作。希望這對遇到相同問題的其他人有用。
答案3
僅使用 Ansible 模組執行相同的操作。
- name: Creating assemble directory
file:
path: /etc/sudoers.stage.d
state: directory
mode: 0600
- name: Applying sudoers.d files on assemble directory
template:
src: "{{ item }}.j2"
dest: /etc/sudoers.stage.d/{{ item }}
mode: 0400
loop:
- 10-wheel
- 20-sys
register: check
- name: Copying original sudoers to the assemble directory
copy:
remote_src: yes
src: /etc/sudoers
dest: /etc/sudoers.stage.d/99-sudoers
when: check.changed
- name: Removing include line from 99-sudoers
lineinfile:
path: /etc/sudoers.stage.d/99-sudoers
state: absent
regex: ^#includedir /etc/sudoers.d
when: check.changed
- name: Assembling unique config for validation
assemble:
src: /etc/sudoers.stage.d
dest: /etc/sudoers.stage
mode: 0600
validate: visudo -cf %s
when: check.changed
- name: Applying sudoers configurations
template:
src: "{{ item }}.j2"
dest: /etc/sudoers.d/{{ item }}
mode: 0440
loop:
- 10-wheel
- 20-sys
when: check.changed