From 856bf72560516ab0d8f4fbac6b35410d9ac1d335 Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Sat, 22 Aug 2026 19:27:33 +0300 Subject: [PATCH] =?UTF-8?q?owner:=20=D1=80=D0=BE=D0=BB=D1=8C=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=D1=87=D1=91=D1=81=D0=B0=D0=BD=D0=B0=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B4=20=D0=BA=D0=BE=D0=BD=D0=B2=D0=B5=D0=BD=D1=86=D0=B8=D0=B8?= =?UTF-8?q?=20eget=20=D0=B8=20secrets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - валидация аргументов через assert вместо пары fail с одинаковым именем - with_items/with_dict заменены на loop, имена задач без точки на конце - добавлены meta/main.yml и README с таблицей переменных - задача ansible-owner-role-cleanup удалена из беклога --- docs/backlog/README.md | 1 - docs/backlog/ansible-owner-role-cleanup.md | 20 --------- roles/owner/README.md | 52 ++++++++++++++++++++++ roles/owner/meta/main.yml | 20 +++++++++ roles/owner/tasks/main.yml | 48 +++++++++++--------- 5 files changed, 100 insertions(+), 41 deletions(-) delete mode 100644 docs/backlog/ansible-owner-role-cleanup.md create mode 100644 roles/owner/README.md create mode 100644 roles/owner/meta/main.yml diff --git a/docs/backlog/README.md b/docs/backlog/README.md index df302f5..af67b75 100644 --- a/docs/backlog/README.md +++ b/docs/backlog/README.md @@ -42,7 +42,6 @@ _(sec-ревью)_ — три независимых security-ревью на м - [Мелкий хардненинг контейнеров](container-hardening-nits.md) — `no-new-privileges`, non-root `user:`, homepage off `:latest`, соглашение о `127.0.0.1:` портах _(sec-ревью)_ - [Унифицировать ACL техпанелей Authelia](authelia-acl-unify.md) — rssbridge (SSRF) и dashboard за `one_factor` без subject; свести к `group:admins` + `two_factor` _(sec-ревью)_ -- [Причесать роль `owner` под конвенции](ansible-owner-role-cleanup.md) — `assert` вместо `fail`+`when`, `loop` вместо `with_*`, добавить `meta`/README _(ансибл-ревью)_ - [Инвентарь: `host_vars`, именованные группы, точечный `become`](ansible-inventory-hostvars.md) — хост-специфику в `host_vars/server.yml`, хост в именованную группу, глобальный root → точечный become _(ансибл-ревью)_ - [Фоновая зачистка стиля и конфигурации Ansible](ansible-style-nits.md) — sudoers.d, профиль ansible-lint, `ansible.cfg`, кавычки, `cache_valid_time` _(ансибл-ревью)_ - [Logrotate для логов бэкапов](backup-logs-logrotate.md) — логи `backup-all.py`/restic копятся без ротации _(tududi)_ diff --git a/docs/backlog/ansible-owner-role-cleanup.md b/docs/backlog/ansible-owner-role-cleanup.md deleted file mode 100644 index cf1d4e7..0000000 --- a/docs/backlog/ansible-owner-role-cleanup.md +++ /dev/null @@ -1,20 +0,0 @@ -# Причесать роль `owner` под конвенции - -**Приоритет:** низкий - -Роль `owner` разошлась по стилю с `eget`/`secrets`: - -- **`roles/owner/tasks/main.yml:2-10`** — валидация аргументов через `fail` + - `when`, причём две задачи с **идентичным именем**. `eget` для того же делает - `assert` (`roles/eget/tasks/main.yml:15`). Привести к одному стилю — `assert` - либо декларативный `meta/argument_specs.yml`. -- **`roles/owner/tasks/main.yml:32,53`** — устаревшие `with_items`/`with_dict`; - конвенция — `loop` (`loop: "{{ owner_ssh_keys }}"`, - `loop: "{{ owner_env_dict | dict2items }}"`). -- У `owner` нет `meta/main.yml` и README, тогда как у `eget` и `secrets` есть. -- Имена задач с точкой на конце (`"Prepare env variables."`) — ansible-lint в - строгом профиле это ловит. - -Контекст: [docs/drafts/ansible-review.md](../drafts/ansible-review.md) §5. - -Связано: roles/owner, roles/eget (образец), roles/secrets. diff --git a/roles/owner/README.md b/roles/owner/README.md new file mode 100644 index 0000000..288dbc9 --- /dev/null +++ b/roles/owner/README.md @@ -0,0 +1,52 @@ +# owner + +Заводит системного пользователя и группу под одно приложение: фиксированные +uid/gid, ssh-ключи и переменные окружения в `~/.env`. + +Один сервис — один пользователь: контейнеры приложения работают от его uid/gid, +и файлы в `base_dir` принадлежат ему же. Роль вызывается первой задачей почти +каждого `playbook-.yml`. + +## Что делает + +1. **Группа и пользователь.** Создаёт группу `owner_group` с `owner_gid` и + пользователя `owner_name` с `owner_uid` и шеллом `/bin/bash`. Номера задаёт + плейбук приложения (соглашение по нумерации — в `AGENTS.md`). +2. **SSH-ключи.** Прописывает `owner_ssh_keys` в `authorized_keys`. +3. **Переменные окружения.** Рендерит `~/.env` из `owner_env` плюс `USER_UID` и + `USER_GID` — их подставляют docker-compose шаблоны приложений. В `.bashrc` + добавляет строку, подгружающую `~/.env` целиком, и вычищает старые построчные + `export`-ы, оставшиеся от прежней схемы. + +## Результат для плейбука + +Роль регистрирует `owner_create_result` — результат модуля `user`. Плейбуки и +compose-шаблоны берут оттуда `owner_create_result.uid` и +`owner_create_result.group` (это gid) для `user:` в docker-compose. + +## Переменные + +Полный список и значения по умолчанию — в `defaults/main.yml`. + +| Переменная | Обязательная | Описание | +| --------------------- | ------------ | --------------------------------------------------- | +| `owner_name` | да | Имя пользователя; по умолчанию оно же имя группы | +| `owner_uid` | да | Uid пользователя | +| `owner_group` | нет | Имя группы, по умолчанию `owner_name` | +| `owner_gid` | нет | Gid группы, по умолчанию `owner_uid` | +| `owner_extra_groups` | нет | Дополнительные группы (обычно `["docker"]`) | +| `owner_ssh_keys` | нет | Список публичных ключей для `authorized_keys` | +| `owner_env` | нет | Словарь переменных окружения, попадающих в `~/.env` | + +## Пример использования + +```yaml +- name: "Create user and environment" + ansible.builtin.import_role: + name: owner + vars: + owner_name: "{{ app_user }}" + owner_uid: "{{ app_owner_uid }}" + owner_gid: "{{ app_owner_gid }}" + owner_extra_groups: ["docker"] +``` diff --git a/roles/owner/meta/main.yml b/roles/owner/meta/main.yml new file mode 100644 index 0000000..b2cec9d --- /dev/null +++ b/roles/owner/meta/main.yml @@ -0,0 +1,20 @@ +--- +galaxy_info: + role_name: owner + author: "Anton Vakhrushev" + description: "Системный пользователь приложения: группа, uid/gid, ssh-ключи и переменные окружения" + license: "MIT" + min_ansible_version: "2.14" + platforms: + - name: Ubuntu + versions: + - all + - name: Debian + versions: + - all + galaxy_tags: + - users + - system + - environment + +dependencies: [] diff --git a/roles/owner/tasks/main.yml b/roles/owner/tasks/main.yml index 39c6d0f..2ab98bf 100644 --- a/roles/owner/tasks/main.yml +++ b/roles/owner/tasks/main.yml @@ -1,21 +1,24 @@ --- -- name: 'Check app requirements for user "{{ owner_name }}".' - ansible.builtin.fail: - msg: You must set owner name. - when: not owner_name +# tasks file for owner -- name: 'Check app requirements for user "{{ owner_name }}".' - ansible.builtin.fail: - msg: You must set owner uid. - when: not owner_uid +- name: "Validate owner role arguments" + ansible.builtin.assert: + that: + - owner_name is string and owner_name | length > 0 + - owner_uid | int > 0 + - owner_gid | int > 0 + fail_msg: >- + Роль owner требует непустой owner_name и положительные owner_uid/owner_gid + (owner_name={{ owner_name }}, owner_uid={{ owner_uid }}, owner_gid={{ owner_gid }}) + quiet: true -- name: 'Create group "{{ owner_group }}".' +- name: "Create group {{ owner_group }}" ansible.builtin.group: name: "{{ owner_group }}" gid: "{{ owner_gid }}" state: present -- name: 'Create user "{{ owner_name }}".' +- name: "Create user {{ owner_name }}" ansible.builtin.user: name: "{{ owner_name }}" group: "{{ owner_group }}" @@ -24,20 +27,21 @@ shell: /bin/bash register: owner_create_result -- name: 'Set up user ssh keys for user "{{ owner_name }}".' +- name: "Set up ssh keys for user {{ owner_name }}" ansible.posix.authorized_key: user: "{{ owner_name }}" key: "{{ item }}" state: present - with_items: "{{ owner_ssh_keys }}" - when: owner_ssh_keys | length > 0 + loop: "{{ owner_ssh_keys }}" -- name: "Prepare env variables." +# USER_UID/USER_GID кладём в окружение всегда: их подставляют docker-compose +# шаблоны приложений, чтобы контейнер работал от того же пользователя. +- name: "Prepare env variables" ansible.builtin.set_fact: - # yamllint disable-line rule:line-length - owner_env_dict: '{{ owner_env | combine({"USER_UID": owner_create_result.uid, "USER_GID": owner_create_result.group}) }}' + owner_env_dict: >- + {{ owner_env | combine({"USER_UID": owner_create_result.uid, "USER_GID": owner_create_result.group}) }} -- name: 'Set up environment variables for user "{{ owner_name }}".' +- name: "Set up environment variables for user {{ owner_name }}" ansible.builtin.template: src: env.template dest: "/home/{{ owner_name }}/.env" @@ -45,14 +49,18 @@ group: "{{ owner_group }}" mode: "0640" -- name: 'Remove from bashrc absent environment variables for user "{{ owner_name }}".' +# Раньше переменные писались в .bashrc построчно; теперь источник истины — +# ~/.env, поэтому старые строки вычищаем. +- name: "Remove from bashrc absent environment variables for user {{ owner_name }}" ansible.builtin.lineinfile: path: "/home/{{ owner_name }}/.bashrc" regexp: "^export {{ item.key }}=" state: absent - with_dict: "{{ owner_env_dict }}" + loop: "{{ owner_env_dict | dict2items }}" + loop_control: + label: "{{ item.key }}" -- name: 'Include in bashrc environment variables for user "{{ owner_name }}".' +- name: "Include in bashrc environment variables for user {{ owner_name }}" ansible.builtin.lineinfile: path: "/home/{{ owner_name }}/.bashrc" regexp: "^export \\$\\(grep -v"