การจัดการ User บนเซิร์ฟเวอร์หลายเครื่องพร้อมกันเป็นงานที่ใช้เวลามากหากทำด้วยตนเอง ไม่ว่าจะเป็นการสร้าง account สำหรับ developer ใหม่, กำหนด sudo permissions, ตั้งค่า SSH key หรือลบ account ที่ไม่ใช้แล้ว การทำผิดพลาดเพียงครั้งเดียวอาจเปิดช่องโหว่ด้าน security ได้
บทความนี้จะแสดงวิธีเขียน Playbook สำหรับจัดการ User และ Permissions อย่างเป็นระบบ ครอบคลุมตั้งแต่สร้าง User, กำหนด Group, ตั้งค่า SSH key, ไปจนถึงลบ account และ rotate password ทั้งหมดในไฟล์เดียว
สร้าง User และกำหนด Group
Ansible ใช้ module user สำหรับจัดการ account บนระบบ Linux รองรับ Ubuntu, CentOS และ distro อื่นทุกตัว
---
- name: User Management Playbook
hosts: all
become: true
vars:
developers:
- name: alice
groups: ["sudo", "docker"]
shell: /bin/bash
- name: bob
groups: ["docker"]
shell: /bin/bash
- name: carol
groups: ["www-data"]
shell: /bin/bash
tasks:
- name: Create developer accounts
ansible.builtin.user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
shell: "{{ item.shell }}"
create_home: yes
state: present
loop: "{{ developers }}"
- name: Create shared group for project
ansible.builtin.group:
name: devteam
state: present
- name: Add all developers to shared group
ansible.builtin.user:
name: "{{ item.name }}"
groups: devteam
append: yes
loop: "{{ developers }}"
การใช้ append: yes มีความสำคัญมาก — ถ้าไม่ใส่ Ansible จะ replace groups ทั้งหมดด้วย group ที่ระบุ ทำให้ user อาจเสีย permissions ที่มีอยู่เดิมโดยไม่ตั้งใจ
ตั้งค่า SSH Key สำหรับ Users
การ deploy SSH public key ไปยังเซิร์ฟเวอร์หลายเครื่องพร้อมกันเป็นหนึ่งในงานที่ทำบ่อยที่สุด โดยเฉพาะเมื่อ developer คนใหม่เข้าทีม
---
- name: Deploy SSH Keys
hosts: all
become: true
vars:
ssh_users:
- username: alice
pubkey: "ssh-ed25519 AAAA...alice_key... alice@laptop"
- username: bob
pubkey: "ssh-ed25519 AAAA...bob_key... bob@workstation"
tasks:
- name: Deploy SSH public keys
ansible.posix.authorized_key:
user: "{{ item.username }}"
key: "{{ item.pubkey }}"
state: present
exclusive: no
loop: "{{ ssh_users }}"
- name: Disable password authentication (enforce key-only)
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^PasswordAuthentication'
line: 'PasswordAuthentication no'
state: present
notify: Restart SSH
handlers:
- name: Restart SSH
ansible.builtin.service:
name: sshd
state: restarted
การใช้ exclusive: no จะ append key ใหม่เข้าไปโดยไม่ลบ key เดิม ถ้าตั้งเป็น exclusive: yes จะแทนที่ key ทั้งหมดด้วย key ที่ระบุ ซึ่งอาจทำให้ตัดการเชื่อมต่อของ user อื่นได้
กำหนด sudo Permissions แบบ Fine-grained
การให้ sudo access ทั้งหมด (ALL) เป็นแนวทางที่ไม่ปลอดภัย ควรกำหนด permission เฉพาะ command ที่จำเป็นเท่านั้น
---
- name: Configure sudo permissions
hosts: all
become: true
tasks:
- name: Grant full sudo to sysadmin group
ansible.builtin.copy:
dest: /etc/sudoers.d/sysadmin
content: |
%sysadmin ALL=(ALL) NOPASSWD: ALL
owner: root
group: root
mode: '0440'
validate: visudo -cf %s
- name: Grant limited sudo to developer group (restart services only)
ansible.builtin.copy:
dest: /etc/sudoers.d/developers
content: |
%developers ALL=(ALL) NOPASSWD: /bin/systemctl restart nginx, /bin/systemctl restart php-fpm
owner: root
group: root
mode: '0440'
validate: visudo -cf %s
- name: Grant deploy user permission to run deploy script
ansible.builtin.copy:
dest: /etc/sudoers.d/deploy
content: |
deploy ALL=(ALL) NOPASSWD: /opt/scripts/deploy.sh
owner: root
group: root
mode: '0440'
validate: visudo -cf %s
การใช้ validate: visudo -cf %s จะตรวจสอบ syntax ก่อน deploy จริง ป้องกันการ break sudoers file ที่อาจทำให้ lock ตัวเองออกจากระบบ
ลบ User และ Revoke Access
เมื่อ developer ลาออกหรือเปลี่ยนทีม การลบ account และ revoke access ทุก server ต้องทำอย่างรวดเร็วและครบถ้วน
---
- name: Offboard user
hosts: all
become: true
vars:
offboard_user: "alice"
tasks:
- name: Remove SSH authorized keys
ansible.posix.authorized_key:
user: "{{ offboard_user }}"
key: ""
state: absent
exclusive: yes
- name: Disable account (lock password)
ansible.builtin.user:
name: "{{ offboard_user }}"
password_lock: yes
- name: Kill active sessions
ansible.builtin.command:
cmd: "pkill -u {{ offboard_user }}"
ignore_errors: yes
changed_when: false
- name: Remove sudoers entry
ansible.builtin.file:
path: "/etc/sudoers.d/{{ offboard_user }}"
state: absent
- name: Archive home directory before deletion
ansible.builtin.archive:
path: "/home/{{ offboard_user }}"
dest: "/backup/{{ offboard_user }}_{{ ansible_date_time.date }}.tar.gz"
ignore_errors: yes
- name: Delete user account
ansible.builtin.user:
name: "{{ offboard_user }}"
state: absent
remove: yes
Playbook นี้ทำ offboarding อย่างปลอดภัย: ลบ SSH key ก่อน, lock password, kill sessions ที่ active อยู่, ลบ sudoers entry, backup home directory, แล้วจึงลบ account ทั้งหมด
Rotate Password สำหรับ Service Accounts
Service accounts เช่น deploy user หรือ monitoring user ควร rotate password เป็นประจำตาม security policy
---
- name: Rotate service account passwords
hosts: all
become: true
vars:
service_users:
- deploy
- monitoring
tasks:
- name: Generate random password
ansible.builtin.set_fact:
new_password: "{{ lookup('password', '/dev/null length=20 chars=ascii_letters,digits') }}"
- name: Update password for service accounts
ansible.builtin.user:
name: "{{ item }}"
password: "{{ new_password | password_hash('sha512') }}"
update_password: always
loop: "{{ service_users }}"
no_log: true
- name: Store new password in vault file
ansible.builtin.lineinfile:
path: /etc/ansible-vault/passwords.txt
regexp: "^{{ item }}:"
line: "{{ item }}: {{ new_password }}"
create: yes
mode: '0600'
loop: "{{ service_users }}"
no_log: true
การใช้ no_log: true จะซ่อน password ไม่ให้แสดงใน Ansible output หรือ log files ซึ่งสำคัญมากเมื่อทำงานกับข้อมูล sensitive
User Management แบบ Idempotent ด้วย Variable Files
สำหรับทีมขนาดใหญ่ ควรเก็บรายชื่อ user ไว้ใน variable file แยกต่างหาก ทำให้ Ops team แก้ไข user list ได้โดยไม่ต้องแตะ Playbook
# vars/users.yml
users:
- name: alice
uid: 1001
comment: "Alice Developer"
groups: ["sudo", "docker"]
ssh_key: "ssh-ed25519 AAAA... alice@company"
state: present
- name: bob
uid: 1002
comment: "Bob Developer"
groups: ["docker"]
ssh_key: "ssh-ed25519 AAAA... bob@company"
state: present
- name: olduser
uid: 1003
comment: "Former Employee"
groups: []
state: absent # จะถูกลบออก
# playbook.yml
---
- name: Sync user list
hosts: all
become: true
vars_files:
- vars/users.yml
tasks:
- name: Manage users from variable file
ansible.builtin.user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
comment: "{{ item.comment }}"
groups: "{{ item.groups }}"
append: yes
create_home: yes
state: "{{ item.state }}"
loop: "{{ users }}"
- name: Deploy SSH keys for active users
ansible.posix.authorized_key:
user: "{{ item.name }}"
key: "{{ item.ssh_key }}"
state: present
loop: "{{ users | selectattr('state', 'equalto', 'present') | list }}"
when: item.ssh_key is defined
การใช้ Jinja2 filter selectattr('state', 'equalto', 'present') กรองเฉพาะ user ที่ active ก่อน deploy SSH key ป้องกันการ deploy key ให้ user ที่ถูกลบไปแล้ว
สรุป
การจัดการ User ด้วย Automation ทำให้มั่นใจได้ว่าทุกเซิร์ฟเวอร์มี account, permissions, และ SSH key ที่ถูกต้องสม่ำเสมอ และเมื่อต้องลบ access สามารถทำได้ครบทุก server ในครั้งเดียวโดยไม่ตกหล่น
จุดสำคัญที่ต้องระวัง: ใส่ append: yes เสมอเมื่อเพิ่ม group, ใช้ validate: visudo -cf %s ก่อน deploy sudoers, และใช้ no_log: true ทุกครั้งที่ task เกี่ยวข้องกับ password

