8.2 KiB
Assignment
There are some files that need to be created on all app servers in Stratos DC. The Nautilus DevOps team want these files to be owned by user root only however, they also want that the app specific user to have a set of permissions on these files. All tasks must be done using Ansible only, so they need to create a playbook. Below you can find more information about the task.
Create a playbook named playbook.yml under /home/thor/ansible directory on jump host, an inventory file is already present under /home/thor/ansible directory on Jump Server itself.
Create an empty file blog.txt under /opt/data/ directory on app server 1. Set some acl properties for this file. Using acl provide read '(r)' permissions to group tony (i.e entity is tony and etype is group).
Create an empty file story.txt under /opt/data/ directory on app server 2. Set some acl properties for this file. Using acl provide read + write '(rw)' permissions to user steve (i.e entity is steve and etype is user).
Create an empty file media.txt under /opt/data/ on app server 3. Set some acl properties for this file. Using acl provide read + write '(rw)' permissions to group banner (i.e entity is banner and etype is group).
Note: Validation will try to run the playbook using command ansible-playbook -i inventory playbook.yml so please make sure the playbook works this way, without passing any extra arguments.
Solution
Ansible Playbook — root-owned files with per-user ACLs
Create /home/thor/ansible/playbook.yml so ansible-playbook -i inventory playbook.yml creates a
root-owned file on each app server and grants an app-specific user or group extra access via ACLs —
with no extra arguments.
Note: this is an Ansible task, not Kubernetes — no manifests to pipe into
kubectl. The heredoc below writes the playbook.
Step 0 — Check the existing inventory
The inventory is already present. Don't overwrite it — but confirm the hostnames, since the playbook targets each server by name:
cat /home/thor/ansible/inventory
You need stapp01, stapp02, stapp03 (match the playbook's hosts: values to whatever names it
uses). Also confirm ansible_become_pass is present, since the play requires root and you can't
pass -K.
Step 1 — Playbook
cat > /home/thor/ansible/playbook.yml <<'EOF'
---
- name: Configure blog.txt on App Server 1
hosts: stapp01
become: yes
tasks:
- name: Create /opt/data/blog.txt owned by root
ansible.builtin.file:
path: /opt/data/blog.txt
state: touch
owner: root
group: root
- name: Grant read permission to group tony
acl:
path: /opt/data/blog.txt
entity: tony
etype: group
permissions: r
state: present
- name: Configure story.txt on App Server 2
hosts: stapp02
become: yes
tasks:
- name: Create /opt/data/story.txt owned by root
ansible.builtin.file:
path: /opt/data/story.txt
state: touch
owner: root
group: root
- name: Grant read+write permission to user steve
acl:
path: /opt/data/story.txt
entity: steve
etype: user
permissions: rw
state: present
- name: Configure media.txt on App Server 3
hosts: stapp03
become: yes
tasks:
- name: Create /opt/data/media.txt owned by root
ansible.builtin.file:
path: /opt/data/media.txt
state: touch
owner: root
group: root
- name: Grant read+write permission to group banner
acl:
path: /opt/data/media.txt
entity: banner
etype: group
permissions: rw
state: present
EOF
How it works
The core idea: root ownership + ACLs for everyone else
This is exactly the problem ACLs solve. Traditional Unix permissions give a file one owner and
one group — so if root must own the file, there's no way to also grant tony or steve
specific access through chmod alone without opening it to "other" (everyone).
ACLs add supplementary entries on top of the standard permission bits, letting you name additional users or groups and give each its own rights. So:
owner: root/group: rootsatisfies "owned by user root only"- the
aclentry satisfies "app specific user should have a set of permissions"
Both requirements hold simultaneously, which plain permissions couldn't achieve.
Why three separate plays
Each server needs a different filename, entity, etype, and permission set — four
things varying at once with no shared pattern. Three plays scoped with hosts: stapp01 /
stapp02 / stapp03 express that directly. A single play with
when: inventory_hostname == "stapp01" conditionals would work too, but needs six conditional tasks
and buries the intent.
One playbook file can hold multiple plays; they execute top to bottom, each against its own host set.
The file task
state: touch— creates the file if absent, leaving it empty ("create an empty file").owner: root/group: root— the explicit ownership requirement. Setting ownership requires root privileges (chownis privileged), which the play already has viabecome: yes.
Note state: touch bumps timestamps each run, so re-runs report changed rather than ok. Fine
here.
The acl module
The module maps directly onto setfacl:
| Parameter | Meaning | Values across the three servers |
|---|---|---|
path |
File to modify | /opt/data/blog.txt, story.txt, media.txt |
entity |
Who the rule applies to | tony, steve, banner |
etype |
Entity kind: user, group, other, mask |
group, user, group |
permissions |
Rights as rwx letters |
r, rw, rw |
state: present |
Ensure the entry exists | — |
Watch the pairing — servers 1 and 3 both use etype: group but with different permissions
(r vs rw), while server 2 uses etype: user. Transposing user and group is the most common
way to fail this task.
The shell equivalent for the first play is setfacl -m g:tony:r /opt/data/blog.txt; the module makes
it declarative and idempotent.
Module naming
The short name acl is used for compatibility. On modern Ansible the module lives in the
ansible.posix collection and the short name routes there automatically when that collection is
installed (standard in a full Ansible install). If you hit "couldn't resolve module/action 'acl'",
switch to the fully-qualified name:
ansible.posix.acl:
installing it if needed with ansible-galaxy collection install ansible.posix.
Why become: yes
Three things here need root: creating files under the root-owned /opt/data, setting owner/group
to root (chown), and applying ACLs with setfacl. The play escalates once at play level, and the
sudo password comes from ansible_become_pass in the inventory since -K can't be passed.
Prerequisite
ACL support needs the acl package (setfacl/getfacl) on the managed nodes and a filesystem
mounted with ACL support. Both are standard on RHEL/CentOS-family systems. If a play fails with
"setfacl not found," installing the acl package on the target resolves it.
Verify
cd /home/thor/ansible
# Connectivity
ansible -i inventory all -m ping
# The actual validation command
ansible-playbook -i inventory playbook.yml
# Ownership and ACLs per server
ansible -i inventory stapp01 -b -m command -a "getfacl /opt/data/blog.txt"
ansible -i inventory stapp02 -b -m command -a "getfacl /opt/data/story.txt"
ansible -i inventory stapp03 -b -m command -a "getfacl /opt/data/media.txt"
Expected — the playbook finishing with failed=0 across all three plays, and getfacl output
showing # owner: root / # group: root at the top plus:
- stapp01
/opt/data/blog.txt→group:tony:r-- - stapp02
/opt/data/story.txt→user:steve:rw- - stapp03
/opt/data/media.txt→group:banner:rw-
getfaclconveniently shows both requirements at once — the owner/group header lines and the supplementary ACL entries. Check theuser:vsgroup:prefix carefully. "Missing sudo password" ⇒ inventory needsansible_become_pass. "No hosts matched" ⇒ inventory hostnames don't match thehosts:values.