Skip to content

Commit db52f3b

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

File tree

1 file changed

+345
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)