Skip to content

Commit 3c56ccb

Browse files
HLD for persistent local user management
1 parent 977fbb0 commit 3c56ccb

1 file changed

Lines changed: 344 additions & 0 deletions

File tree

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
# Declarative Local User Management
2+
3+
## Revision
4+
5+
| Rev | Date | Author | Change Description |
6+
|:---:|:--------------:|:-----------:|:----------------------------------------------------------------------------------------------------------------------------------------------------:|
7+
| 1.0 | June 16, 2025 | Manoharan Sundaramoorthy | Initial HLD |
8+
9+
10+
## 1. Scope
11+
This document describes the high-level design for a new feature that provides a declarative and persistent method for managing local user accounts on a SONiC device. This feature allows administrators to define local users, including their roles, password hashes, SSH keys, and **security policies** including **login attempt limits**.
12+
13+
This ensures that local user accounts are consistently applied with robust security postures and persist across reboots and upgrades.
14+
15+
## 2. Definitions/Abbreviations
16+
17+
| Abbreviation | Definition |
18+
|:------------:|:--------------------------------------------------------------------------|
19+
| **`userd`** | The new User Daemon proposed in this design. |
20+
| **PAM** | Pluggable Authentication Modules |
21+
22+
23+
## 3. Overview
24+
This feature introduces a new dedicated daemon, **`userd`**, which manages the full lifecycle of local users based on definitions in `CONFIG_DB`. It simplifies management by providing a user-friendly CLI, mapping abstract roles (`administrator`, `operator`) to specific Linux groups, and enforcing security policies. This provides a solution for managing secure, persistent local user accounts.
25+
26+
## 4. Requirements
27+
### 4.1 Functional Requirements
28+
1. The system must allow an administrator to define a local user account declaratively.
29+
2. The user definition must support:
30+
* Username and a pre-hashed password.
31+
* A role, limited to either **`administrator`** or **`operator`**.
32+
* Authorized SSH keys (statically defined).
33+
3. The system will **auto-generate** a unique UID for each new user.
34+
4. **Default Admin User:**
35+
* The **`admin`** user (or the user specified during compilation) must be included in the `init_cfg.json` with default settings.
36+
* This ensures the admin user is always present in CONFIG_DB by default and can be managed like any other user.
37+
* The admin user can be modified or disabled through standard CONFIG_DB operations.
38+
5. **Security Policy Requirements:**
39+
* **Login Attempts:** The system must support configuring a global maximum number of failed login attempts per role before accounts are temporarily locked.
40+
6. The system must map roles to underlying Linux groups:
41+
* `administrator`: members of `sudo`, `docker`, `redis` and `admin` groups.
42+
* `operator`: members of a standard, non-privileged and would belong to `users` group.
43+
7. User accounts and their configurations must persist across system reboots and upgrades.
44+
8. **System Consistency:** On startup, the system must perform a consistency check to ensure Linux users match CONFIG_DB definitions and automatically remove any users that were added directly to Linux (bypassing CONFIG_DB).
45+
46+
## 5. Architecture Design
47+
The architecture centers on the new `userd` daemon. This daemon will now interact with several core Linux subsystems to enforce the configured security policies.
48+
49+
**`userd`'s Points of Interaction:**
50+
1. **CONFIG_DB:** Single source of truth for user configuration and global security policies.
51+
2. **Core User Files:** `/etc/passwd`, `/etc/shadow`, `/etc/group` for basic user identity.
52+
3. **PAM Configuration (`/etc/security/faillock.conf`):** To manage global failed login attempt policies per role via `pam_faillock`.
53+
54+
## 6. High-Level Design
55+
56+
### 6.1 `userd` Daemon
57+
The `userd` daemon's logic will be expanded to manage security configurations idempotently.
58+
59+
**Startup Consistency Check:**
60+
* **System Reconciliation:** On startup, `userd` will perform a consistency check to ensure that all local users in the Linux system (`/etc/passwd`, `/etc/shadow`, `/etc/group`) match the definitions in CONFIG_DB.
61+
* **Cleanup of Unmanaged Users:** Any users found in the Linux system that are not defined in CONFIG_DB (except for system users like `root`, `admin`, `daemon`, etc.) will be automatically removed to maintain consistency.
62+
* **CONFIG_DB as Source of Truth:** This ensures that CONFIG_DB remains the single source of truth for user management and prevents configuration drift.
63+
64+
**Default Admin User:**
65+
* **Init Config Integration:** The `admin` user (or the user specified during compilation) is included in `init_cfg.json` with default settings, ensuring it's always present in CONFIG_DB.
66+
* **Standard Management:** The admin user is managed through the same CONFIG_DB interface as other users, with no special exception handling required.
67+
* **Default Configuration:** The admin user in `init_cfg.json` will use the username and password hash from the `USERNAME` and `PASSWORD` environment variables during build, with fallback defaults if not specified.
68+
69+
**Configuration File Hierarchy:**
70+
* **`init_cfg.json`:** Contains base system defaults including the default admin user. This file is generated at build-time from a Jinja2 template and is always present. It ensures every SONiC system has essential user management configuration.
71+
* **`golden_config_db.json`:** Optional site-specific configuration file that can override defaults when used with `config load_minigraph --override_config`. This allows deployments to customize user configurations without modifying the base system defaults.
72+
* **Configuration Precedence:** When both files are present, `golden_config_db.json` takes precedence over `init_cfg.json` during minigraph reload operations with override enabled.
73+
74+
**User Account Management:**
75+
* **Account Status Control:** `userd` will monitor the `enabled` attribute for each user. When `enabled` is `false`, it will prepend `!` to the password hash in `/etc/shadow` to disable password-based login. When `enabled` is `true`, it will restore the original password hash.
76+
* **Password Hash Preservation:** When disabling a user, `userd` will store the original password hash and restore it when the user is re-enabled, ensuring seamless account management.
77+
78+
**New Logic for Security Policies:**
79+
80+
* **To enforce Login Attempts:**
81+
* `userd` will manage the PAM configuration file at `/etc/security/faillock.conf`.
82+
* For global role-based limits (e.g., administrators with limit of 5), it will ensure appropriate configuration in the global section.
83+
* `userd` will configure PAM to apply different limits based on user group membership (administrator vs operator roles).
84+
* `userd` will be responsible for ensuring the PAM stack is configured to use `pam_faillock` with role-based policies.
85+
86+
## 7. SAI API
87+
No SAI API changes are required.
88+
89+
## 8. Configuration and Management
90+
### 8.1 Config DB Enhancements
91+
The `USER` table schema is defined for individual user accounts, and a new `USER_SECURITY_POLICY` table is added for global role-based security policies.
92+
93+
**Schema:**
94+
```json
95+
// Example for CONFIG_DB
96+
{
97+
"USER": {
98+
"admin": {
99+
"role": "administrator",
100+
"password_hash": "$6$salt$hash_of_YourPaSsWoRd",
101+
"ssh_keys": ["ssh-rsa AAA..."],
102+
"enabled": true
103+
},
104+
"newadmin": {
105+
"role": "administrator",
106+
"password_hash": "hashed_password",
107+
"ssh_keys": ["ssh-rsa BBB..."],
108+
"enabled": true
109+
},
110+
"showuser": {
111+
"role": "operator",
112+
"password_hash": "hashed_password",
113+
"ssh_keys": ["ssh-rsa CCC..."],
114+
"enabled": false
115+
}
116+
},
117+
"USER_SECURITY_POLICY": {
118+
"administrator": {
119+
"max_login_attempts": 5
120+
},
121+
"operator": {
122+
"max_login_attempts": 3
123+
}
124+
}
125+
}
126+
```
127+
128+
**USER Table:**
129+
* `role` (string, required): `administrator` or `operator`.
130+
* `password_hash` (string, required): The hashed password.
131+
* `ssh_keys` (optional): List of SSH public keys for the user.
132+
* `enabled` (boolean, optional): Whether the user account is enabled. Defaults to `true` if not specified.
133+
134+
**USER_SECURITY_POLICY Table:**
135+
* **`max_login_attempts`** (integer, optional): Number of failed login attempts before accounts with this role are locked.
136+
137+
**Notes:**
138+
* Session timeouts are managed by the system's default timeout policy and are not configurable per user.
139+
* Password hashes can be generated using `mkpasswd` command-line utility or programmatically using libraries like `passlib` in Python for NETCONF/RESTCONF implementations.
140+
* The CLI provides both `--password-hash` (for pre-hashed passwords) and `--password-prompt` (for interactive secure password entry) options for improved security and usability.
141+
* **CONFIG_DB as Source of Truth:** On startup, `userd` performs a consistency check and removes any users that were added directly to Linux (bypassing CONFIG_DB) to ensure CONFIG_DB remains the authoritative source for user management.
142+
* **User Account Status:** When `enabled` is set to `false`, `userd` will prepend `!` to the password hash in `/etc/shadow` to disable password-based login while preserving SSH key access. When `enabled` is `true` (default), the original password hash is restored.
143+
* **Password Hash Format:** Password hashes in CONFIG_DB cannot start with `!` as this is managed automatically by the `enabled` attribute. Use `enabled: false` instead of manually prepending `!` to disable accounts.
144+
* **Administrator Availability:** At least one administrator user must remain enabled at all times to prevent complete loss of administrative access. This constraint is enforced by the Yang model.
145+
* **Default Admin User:** The `admin` user (or the user specified during compilation) is included in `init_cfg.json` with default settings, ensuring it's always available and manageable through standard CONFIG_DB operations.
146+
147+
### 8.2 Yang Model Enhancements
148+
```
149+
module sonic-user {
150+
yang-version 1.1;
151+
namespace "http://sonicproject.com/sonic-user";
152+
prefix "sonic-user";
153+
154+
import sonic-ext {
155+
prefix "sonic-ext";
156+
}
157+
158+
// Common typedef for user roles
159+
typedef user-role {
160+
type enumeration {
161+
enum "administrator" {
162+
description "Grants administrative privileges (e.g., member of sudo, docker groups).";
163+
}
164+
enum "operator" {
165+
description "Grants operator-level (read-only or limited) privileges.";
166+
}
167+
}
168+
description "User role that determines group memberships, privileges, and applicable security policies.";
169+
}
170+
171+
// Top-level container for the User feature
172+
container sonic-user {
173+
description "Top-level container for local user management configuration";
174+
175+
list USER_LIST {
176+
key "username";
177+
description "List of declaratively managed local users.";
178+
179+
must "count(../USER_LIST[role='administrator' and (not(enabled) or enabled='true')]) >= 1" {
180+
error-message "At least one administrator user must remain enabled.";
181+
}
182+
183+
leaf username {
184+
type string {
185+
pattern '[a-z_][a-z0-9_-]*[$]?' {
186+
error-message "Invalid username. Must start with a lowercase letter or underscore, followed by lowercase letters, numbers, underscores, or hyphens.";
187+
}
188+
must ". != 'root'" {
189+
error-message "Username cannot be 'root'.";
190+
};
191+
length 1..32;
192+
}
193+
description "The username for the local account.";
194+
}
195+
196+
leaf role {
197+
type user-role;
198+
mandatory true;
199+
description "The role assigned to the user, which determines their group memberships and privileges.";
200+
}
201+
202+
leaf password_hash {
203+
type string;
204+
mandatory true;
205+
must "not(starts-with(., '!'))" {
206+
error-message "Password hash cannot start with '!'. Use the 'enabled' attribute to disable user accounts.";
207+
}
208+
description "The hashed password string for the user, as found in /etc/shadow. Password hashes can be generated using 'mkpasswd' utility or programmatically using libraries like 'passlib'. To disable an account, use the 'enabled' attribute instead of prepending '!' to the password hash.";
209+
}
210+
211+
leaf-list ssh_keys {
212+
type string;
213+
description "A list of full public SSH key strings.";
214+
}
215+
216+
leaf enabled {
217+
type boolean;
218+
default true;
219+
description "Whether the user account is enabled. When false, the password is disabled by prepending '!' to prevent password-based login while preserving SSH key access.";
220+
}
221+
}
222+
223+
list USER_SECURITY_POLICY_LIST {
224+
key "role";
225+
description "Global security policies applied to users based on their role.";
226+
227+
leaf role {
228+
type user-role;
229+
description "The role for which this security policy applies.";
230+
}
231+
232+
leaf max_login_attempts {
233+
type uint32 {
234+
range "1..1000";
235+
}
236+
description "Maximum number of failed login attempts before accounts with this role are locked. If not set, system defaults apply.";
237+
}
238+
}
239+
}
240+
}
241+
```
242+
243+
### 8.3 CLI Enhancements
244+
The CLI is enhanced with user management and global security policy commands.
245+
246+
**User Add Command:**
247+
```
248+
config user add <username> --role <role>
249+
[--password-hash <hash> | --password-prompt]
250+
[--ssh-key <key>]
251+
[--disabled]
252+
```
253+
* Multiple `--ssh-key` flags can be provided to build a list.
254+
* Use `--password-hash` to provide a pre-hashed password directly.
255+
* Use `--password-prompt` to enter the password securely through an interactive prompt (password will be hashed automatically).
256+
* Use `--disabled` to create the account in disabled state. Accounts are enabled by default.
257+
* If neither password option is provided, the user account will be created with password login disabled (password set to `!`).
258+
259+
**User Delete Command:**
260+
```
261+
config user del <username>
262+
```
263+
264+
**User Modify Command:**
265+
```
266+
config user modify <username> [--password-hash <hash> | --password-prompt]
267+
[--ssh-key <key>]
268+
[--enabled | --disabled]
269+
```
270+
271+
* Use `--password-hash` to provide a pre-hashed password directly.
272+
* Use `--password-prompt` to enter the password securely through an interactive prompt (password will be hashed automatically).
273+
* Use `--enabled` or `--disabled` to change the account status.
274+
275+
**Security Policy Commands:**
276+
```
277+
config user security-policy <role> --max-login-attempts <count>
278+
```
279+
* Configure global login attempt limits for a specific role (`administrator` or `operator`).
280+
281+
```
282+
show user security-policy [<role>]
283+
```
284+
* Display current security policies for all roles or a specific role.
285+
286+
**Password Prompt Implementation:**
287+
When `--password-prompt` is used, the CLI will:
288+
1. Display a secure password prompt (e.g., "Enter password for user <username>:")
289+
2. Hide password input (no echo to terminal)
290+
3. Prompt for password confirmation (e.g., "Confirm password:")
291+
4. Validate that both entries match
292+
5. Hash the password using the same algorithm as `mkpasswd` (e.g., SHA-512)
293+
6. Store only the hashed password in CONFIG_DB
294+
7. Clear the plaintext password from memory immediately after hashing
295+
296+
**Other commands (e.g., `show user`) remain as previously defined.**
297+
298+
## 9. Testing Requirements
299+
### New System Test cases
300+
* **Global Login Attempts Policy:**
301+
* Configure `administrator` role with `max_login_attempts` of 5 and `operator` role with 3.
302+
* Create users with both roles and verify that login attempt limits are enforced according to their role.
303+
* Attempt to log in with incorrect passwords and verify accounts are locked based on their role's policy.
304+
* Verify `faillock --user <username>` shows the correct state for users of different roles.
305+
306+
* **Password Management:**
307+
* Test user creation with `--password-hash` option and verify the hash is stored correctly.
308+
* Test user creation with `--password-prompt` option and verify the password is hashed and stored securely.
309+
* Verify that passwords entered via prompt are not logged in command history.
310+
* Test password modification using both hash and prompt methods.
311+
* Attempt to create a user with a password hash starting with `!` and verify it fails with an appropriate error message.
312+
313+
* **Account Status Management:**
314+
* Create a user with `--disabled` and verify the password hash is prepended with `!` in `/etc/shadow`.
315+
* Enable a disabled user and verify the original password hash is restored.
316+
* Test that disabled users cannot login with password but can still use SSH keys.
317+
* Verify that the `enabled` attribute defaults to `true` when not specified.
318+
319+
* **Administrator Availability Constraint:**
320+
* Attempt to disable the last remaining enabled administrator user and verify it fails with an appropriate error.
321+
* Verify that at least one administrator user can always be enabled when multiple administrators exist.
322+
* Test that the constraint is enforced during user deletion of administrator accounts.
323+
324+
* **Default Admin User:**
325+
* Verify the `admin` user exists by default in `init_cfg.json` and is present in CONFIG_DB after system initialization.
326+
* Verify the `admin` user can be modified, disabled, or deleted through standard CONFIG_DB operations.
327+
* Test that the `admin` user has the configured username/password from build-time environment variables and administrator role when first created from init config.
328+
329+
* **Startup Consistency Check:**
330+
* Create a user directly in Linux using `useradd` (bypassing CONFIG_DB).
331+
* Restart the `userd` daemon and verify the manually created user is automatically removed.
332+
* Verify that users defined in CONFIG_DB are preserved and properly configured.
333+
* Ensure system users (root, daemon, etc.) are not affected by the cleanup process.
334+
335+
* **User Management:** Create users with different roles and verify all security policies are enforced correctly based on role-based policies.
336+
337+
## 10. Future Enhancements
338+
339+
### 10.1 Remote SSH Key Management
340+
Support for dynamically fetching SSH keys from remote URLs could be added in future versions to enable centralized SSH key management.
341+
342+
## 11. Open/Resolved Issues
343+
344+
None

0 commit comments

Comments
 (0)