core.user
Manage local OS users: account creation and removal.
core.user brings the set of local accounts to the required composition via useradd / userdel. present has present-or-create semantics: if the user already exists, the step reconciles nothing (uid/shell/home/groups are not changed) and reports changed=false, while the optional parameters apply only at creation (usermod is not called). Requires root.
Requirements
- Rootrequired
- Side
soul-side - Collection
soulstack.system - Category
system
run_as_rootexec_subprocessuseradd / userdel — a busybox-compatible subset of flags; on Alpine this is the shadow package or busybox's built-in commands, both of which understand the flags used.
States
core.user.present — The user exists (created via useradd if it is missing).
The user did not exist and was created.
The user already exists — uid/shell/home/groups/system/group are not reconciled.
Parameters
| Param | Type | Required / default | Description |
|---|---|---|---|
name | string | required | User name. |
uid | int | optional | Explicit uid (useradd -u). Applies only on creation. |
shell | string | optional | Login shell (useradd -s). Applies only on creation. |
home | string | optional | Home directory (useradd -d). Applies only on creation. |
groups | list<string> | optional | Supplementary groups (useradd -G). Applies only on creation. |
system | bool | optional | System account (useradd -r) for service accounts. Only on creation. |
group | string | optional | Primary group (useradd -g); must already exist. Only on creation. |
Example — System service account (primary group created BEFORE)
# The primary group is created BEFORE the user (core.user -g requires an existing one).- name: Ensure the app system group exists module: core.group.present params: name: appsvc system: true
- name: Ensure the app system user exists module: core.user.present params: name: appsvc system: true group: appsvc shell: /usr/sbin/nologin home: /var/lib/appsvcOutput
| Field | Type | Description |
|---|---|---|
name | string | |
exists | true | |
created | bool |
core.user.absent — The user is removed.
The user existed and was removed (userdel).
The user does not exist.
Parameters
| Param | Type | Required / default | Description |
|---|---|---|---|
name | string | required | User name. |
Output
| Field | Type | Description |
|---|---|---|
name | string | |
exists | false |
Notes
- present — present-or-create: an existing user is not reconciled (uid/shell/home/groups/system/group are not checked or changed), the optional parameters are applied only at creation — usermod is not called. To rebuild an account, remove it via absent and create it again.
- home is passed with the -M flag: the module does not create the home directory. The primary group (group) must exist beforehand — create it via core.group BEFORE the user.
- The module validates the shape of the input without tightening useradd's constraints: name/group/groups follow shadow-utils NAME_REGEX (^[a-z_][a-z0-9_-]*\$?$, length ≤ 32), uid is in the range [0, 2147483647], shell/home is an absolute path (file and directory existence is not checked). The positional name is preceded by -- (useradd -- <name>), and argv is built directly without sh — this cuts off arg-injection and plainly malformed input.
Security & defaults
- The shape of the input is checked, not whether its meaning is privileged. uid: 0 creates a second root-equivalent account; groups: ["sudo"] / ["wheel"] / ["docker"] gives its holder an effective path to root — both values are valid by format. Format validation cuts off injection but not "dangerous yet valid" meaning: this is not authorization.
- Privileged values are part of the attack surface: if uid/groups/name come from input.* / register.* / soulprint.*, they must be trusted — set by the Destiny/scenario author, not by external input. For a service account, pin safe values — system: true, no sudo/wheel/docker, shell /usr/sbin/nologin.
- present — present-or-create: you cannot lower the privileges of a previously created account with a repeated present (uid/groups are not reconciled). Revoking privileges is only possible by recreating through absent.
- required_capabilities [run_as_root, exec_subprocess] is a declaration for soul-lint's static check against the host's allowed_capabilities, not a runtime privilege escalation: useradd/userdel run with the soul agent process's privileges (as root).