Skip to content

Commit 6653490

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

File tree

1 file changed

+347
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)