209 lines
8.2 KiB
Markdown
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. |