Files

209 lines
8.2 KiB
Markdown

# 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:
```bash
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
```bash
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:
```yaml
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
```bash
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-`
> `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.