Files

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: root satisfies "owned by user root only"
  • the acl entry 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 (chown is privileged), which the play already has via become: 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.txtgroup:tony:r--
  • stapp02 /opt/data/story.txtuser:steve:rw-
  • stapp03 /opt/data/media.txtgroup:banner:rw-

getfacl conveniently shows both requirements at once — the owner/group header lines and the supplementary ACL entries. Check the user: vs group: prefix carefully. "Missing sudo password" ⇒ inventory needs ansible_become_pass. "No hosts matched" ⇒ inventory hostnames don't match the hosts: values.