ldap-manager 1.0.12__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. ldap_manager-1.0.12/LICENSE +8 -0
  2. ldap_manager-1.0.12/PKG-INFO +24 -0
  3. ldap_manager-1.0.12/README.md +282 -0
  4. ldap_manager-1.0.12/ldap_manager/__init__.py +3 -0
  5. ldap_manager-1.0.12/ldap_manager/audit.py +145 -0
  6. ldap_manager-1.0.12/ldap_manager/backup.py +300 -0
  7. ldap_manager-1.0.12/ldap_manager/batch.py +247 -0
  8. ldap_manager-1.0.12/ldap_manager/cli.py +1987 -0
  9. ldap_manager-1.0.12/ldap_manager/config.py +146 -0
  10. ldap_manager-1.0.12/ldap_manager/connection.py +68 -0
  11. ldap_manager-1.0.12/ldap_manager/groups.py +235 -0
  12. ldap_manager-1.0.12/ldap_manager/ldif_ops.py +240 -0
  13. ldap_manager-1.0.12/ldap_manager/passwords.py +93 -0
  14. ldap_manager-1.0.12/ldap_manager/ppolicy.py +304 -0
  15. ldap_manager-1.0.12/ldap_manager/server.py +276 -0
  16. ldap_manager-1.0.12/ldap_manager/sshkeys.py +137 -0
  17. ldap_manager-1.0.12/ldap_manager/tree.py +217 -0
  18. ldap_manager-1.0.12/ldap_manager/users.py +496 -0
  19. ldap_manager-1.0.12/ldap_manager.egg-info/PKG-INFO +24 -0
  20. ldap_manager-1.0.12/ldap_manager.egg-info/SOURCES.txt +37 -0
  21. ldap_manager-1.0.12/ldap_manager.egg-info/dependency_links.txt +1 -0
  22. ldap_manager-1.0.12/ldap_manager.egg-info/entry_points.txt +2 -0
  23. ldap_manager-1.0.12/ldap_manager.egg-info/requires.txt +13 -0
  24. ldap_manager-1.0.12/ldap_manager.egg-info/top_level.txt +1 -0
  25. ldap_manager-1.0.12/pyproject.toml +124 -0
  26. ldap_manager-1.0.12/setup.cfg +4 -0
  27. ldap_manager-1.0.12/tests/test_audit.py +88 -0
  28. ldap_manager-1.0.12/tests/test_backup.py +145 -0
  29. ldap_manager-1.0.12/tests/test_batch.py +144 -0
  30. ldap_manager-1.0.12/tests/test_cli.py +178 -0
  31. ldap_manager-1.0.12/tests/test_config.py +67 -0
  32. ldap_manager-1.0.12/tests/test_groups.py +151 -0
  33. ldap_manager-1.0.12/tests/test_ldif_ops.py +160 -0
  34. ldap_manager-1.0.12/tests/test_passwords.py +71 -0
  35. ldap_manager-1.0.12/tests/test_ppolicy.py +181 -0
  36. ldap_manager-1.0.12/tests/test_server.py +129 -0
  37. ldap_manager-1.0.12/tests/test_sshkeys.py +143 -0
  38. ldap_manager-1.0.12/tests/test_tree.py +116 -0
  39. ldap_manager-1.0.12/tests/test_users.py +290 -0
@@ -0,0 +1,8 @@
1
+ Copyright 2026 Israel Hen
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8
+
@@ -0,0 +1,24 @@
1
+ Metadata-Version: 2.4
2
+ Name: ldap-manager
3
+ Version: 1.0.12
4
+ Summary: CLI power tools for sysadmins who run OpenLDAP and refuse to touch a web UI.
5
+ Author-email: Israel Hen <israelhen153@gmail.com>
6
+ Project-URL: Homepage, https://github.com/israelhen153/ldap-manager
7
+ Project-URL: Repository, https://github.com/israelhen153/ldap-manager
8
+ Project-URL: Issues, https://github.com/israelhen153/ldap-manager/issues
9
+ Project-URL: Documentation, https://github.com/israelhen153/ldap-manager#readme
10
+ Requires-Python: >=3.10
11
+ License-File: LICENSE
12
+ Requires-Dist: python-ldap>=3.4
13
+ Requires-Dist: click>=8.1
14
+ Requires-Dist: pyyaml>=6.0
15
+ Requires-Dist: passlib>=1.7
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest>=7.0; extra == "dev"
18
+ Requires-Dist: pytest-mock; extra == "dev"
19
+ Requires-Dist: pytest-cov; extra == "dev"
20
+ Requires-Dist: ruff>=0.4; extra == "dev"
21
+ Requires-Dist: mypy>=1.10; extra == "dev"
22
+ Requires-Dist: types-PyYAML; extra == "dev"
23
+ Requires-Dist: bandit>=1.7; extra == "dev"
24
+ Dynamic: license-file
@@ -0,0 +1,282 @@
1
+ # ldap-manager
2
+
3
+ # Tests:
4
+ [![CI](https://github.com/israelhen153/ldap-manager/actions/workflows/makefile.yml/badge.svg)](https://github.com/israelhen153/ldap-manager/actions/workflows/makefile.yml)
5
+
6
+
7
+ **CLI power tools for sysadmins who run OpenLDAP and refuse to touch a web UI.**
8
+
9
+ Manage users, groups, backups, SSH keys, password policies, and server operations — all from one command. Replace your pile of bash scripts and raw `ldapmodify` commands with something that actually has `--dry-run`.
10
+
11
+ ```bash
12
+ pip install ldap-manager
13
+ ldap-manager user list --enabled --json
14
+ ```
15
+
16
+ ---
17
+
18
+ ## Why?
19
+
20
+ If you manage OpenLDAP, your options are:
21
+
22
+ | Tool | Problem |
23
+ |------|---------|
24
+ | `ldapmodify` / `ldapsearch` | LDIF by hand for every operation |
25
+ | phpLDAPadmin | Abandoned, PHP, browser-only |
26
+ | LDAP Account Manager | Java, $$$, overkill for CLI admins |
27
+ | Your 15 bash scripts | Fragile, no error handling, no dry-run |
28
+
29
+ **ldap-manager** is the missing CLI. One tool, tab completion, JSON output, dry-run on destructive operations, and actual tests.
30
+
31
+ ---
32
+
33
+ ## Features
34
+
35
+ | Category | What you get |
36
+ |----------|-------------|
37
+ | **Users** | Create, update, delete, enable/disable, search with filters, dump as JSON |
38
+ | **Groups** | Create, delete, add/remove members, list, supports posixGroup and groupOfNames |
39
+ | **Backup & Restore** | Full `slapcat`/`slapadd` dumps with gzip, metadata, and config backup |
40
+ | **Batch Operations** | Bulk create/update/delete from JSON/CSV/TSV with `--dry-run` |
41
+ | **Password Reset** | Reset all users at once, CSV output with new passwords |
42
+ | **SSH Keys** | Add, remove, list `ldapPublicKey` attributes per user |
43
+ | **Server Ops** | Status, start/stop/restart, reindex with `--auto` |
44
+ | **Password Policy** | View policy config, check user expiry/lockout status |
45
+ | **LDIF Export/Import** | Standards-compliant RFC 2849 export and import with dry-run |
46
+ | **Tree Management** | List/create/delete OUs, visualize your DIT |
47
+ | **Audit Logging** | JSON-lines audit trail of all operations |
48
+ | **JSON Output** | `--json` flag on 12+ commands for scripting and pipelines |
49
+
50
+ ---
51
+
52
+ ## Quickstart
53
+
54
+ ### Install
55
+
56
+ ```bash
57
+ # Rocky/RHEL 8+
58
+ dnf install epel-release -y
59
+ dnf install python3-ldap python3-pyyaml python3-click python3-passlib \
60
+ openldap-clients openldap-servers -y
61
+
62
+ pip install ldap-manager
63
+ ```
64
+
65
+ ### Configure
66
+
67
+ ```bash
68
+ cp config.example.yaml /etc/ldap-manager/config.yaml
69
+ vim /etc/ldap-manager/config.yaml
70
+ ```
71
+
72
+ Or use environment variables:
73
+
74
+ ```bash
75
+ export LDAP_URI="ldap://localhost:389"
76
+ export LDAP_BIND_DN="cn=admin,dc=example,dc=com"
77
+ export LDAP_BIND_PASSWORD="secret"
78
+ export LDAP_BASE_DN="dc=example,dc=com"
79
+ ```
80
+
81
+ ### Go
82
+
83
+ ```bash
84
+ ldap-manager user list
85
+ ```
86
+
87
+ ---
88
+
89
+ ## Usage
90
+
91
+ ### Users
92
+
93
+ ```bash
94
+ ldap-manager user list --enabled --json
95
+ ldap-manager user get jdoe
96
+ ldap-manager user create jdoe --cn "John Doe" --mail john@example.com
97
+ ldap-manager user update jdoe --set mail=new@example.com --set loginShell=/bin/zsh
98
+ ldap-manager user delete jdoe --yes
99
+ ldap-manager user disable jdoe
100
+ ldap-manager user enable jdoe
101
+ ldap-manager user passwd jdoe
102
+
103
+ # Search with filters
104
+ ldap-manager user search --uid "j*"
105
+ ldap-manager user search --mail "*@engineering.com" --enabled
106
+ ldap-manager user search --filter "(description=contractor*)" --json
107
+ ```
108
+
109
+ ### Groups
110
+
111
+ ```bash
112
+ ldap-manager group list
113
+ ldap-manager group create devops --gid 5000
114
+ ldap-manager group add-member devops jdoe
115
+ ldap-manager group remove-member devops jdoe
116
+ ldap-manager group members devops --json
117
+ ldap-manager group delete old_team --yes
118
+ ```
119
+
120
+ ### Backup & Restore
121
+
122
+ ```bash
123
+ ldap-manager backup dump --tag pre-migration
124
+ ldap-manager backup list
125
+ ldap-manager backup restore /var/backups/ldap/ldap_backup_20240101_120000
126
+ ldap-manager backup restore /path/to/backup --with-config --yes
127
+ ```
128
+
129
+ ### Batch Operations
130
+
131
+ ```bash
132
+ # Bulk create from CSV/JSON
133
+ ldap-manager batch create users.csv --dry-run
134
+ ldap-manager batch create users.json --yes
135
+
136
+ # Bulk delete
137
+ ldap-manager batch delete terminations.csv --dry-run
138
+ ```
139
+
140
+ ### Bulk Password Reset
141
+
142
+ ```bash
143
+ ldap-manager passwd-all --dry-run
144
+ ldap-manager passwd-all --output /secure/passwords.csv --length 24
145
+ ```
146
+
147
+ ### SSH Keys
148
+
149
+ ```bash
150
+ ldap-manager user ssh-key-list jdoe
151
+ ldap-manager user ssh-key-add jdoe ~/.ssh/id_ed25519.pub
152
+ ldap-manager user ssh-key-remove jdoe 1
153
+ ```
154
+
155
+ ### Server Operations
156
+
157
+ ```bash
158
+ ldap-manager server status
159
+ ldap-manager server start
160
+ ldap-manager server stop
161
+ ldap-manager server restart
162
+ ldap-manager server reindex --auto # stops slapd, reindexes, restarts
163
+ ```
164
+
165
+ ### Password Policy
166
+
167
+ ```bash
168
+ ldap-manager ppolicy status jdoe # expiry, lockout, grace logins
169
+ ldap-manager ppolicy policy # view current policy config
170
+ ldap-manager ppolicy check-all # find expired/locked accounts
171
+ ```
172
+
173
+ ### LDIF Export & Import
174
+
175
+ ```bash
176
+ ldap-manager user export --format ldif --scope all -o backup.ldif
177
+ ldap-manager user export --format json --enabled -o active_users.json
178
+ ldap-manager import users.ldif --dry-run
179
+ ```
180
+
181
+ ### Tree Management
182
+
183
+ ```bash
184
+ ldap-manager tree show # visualize DIT
185
+ ldap-manager tree list-ous
186
+ ldap-manager tree create-ou "ou=Contractors,dc=example,dc=com"
187
+ ldap-manager tree delete-ou "ou=OldDept,dc=example,dc=com" --recursive
188
+ ```
189
+
190
+ ### Audit Log
191
+
192
+ ```bash
193
+ ldap-manager audit log --since 2024-01-01
194
+ ldap-manager audit log --action create --target jdoe
195
+ ldap-manager audit status
196
+ ```
197
+
198
+ ### Global Options
199
+
200
+ ```bash
201
+ ldap-manager -c /path/to/config.yaml user list # custom config
202
+ ldap-manager -v user list # verbose logging
203
+ ```
204
+
205
+ ---
206
+
207
+ ## Configuration
208
+
209
+ Config is loaded from (in order, later overrides earlier):
210
+
211
+ 1. `/etc/ldap-manager/config.yaml` (system)
212
+ 2. `~/.ldap-manager.yaml` (user)
213
+ 3. `--config` flag (explicit)
214
+ 4. Environment variables (highest priority)
215
+
216
+ See `config.example.yaml` for all options.
217
+
218
+ ---
219
+
220
+ ## Development
221
+
222
+ ```bash
223
+ git clone https://github.com/YOURUSERNAME/ldap-manager.git
224
+ cd ldap-manager
225
+ make install # creates venv, installs deps
226
+ make ci # lint + typecheck + security + tests
227
+ ```
228
+
229
+ Tests use mocked LDAP connections — no live server needed.
230
+
231
+ ```
232
+ make lint # ruff check + format
233
+ make typecheck # mypy
234
+ make security # bandit
235
+ make test # pytest with coverage
236
+ make ci # all of the above
237
+ ```
238
+
239
+ ---
240
+
241
+ ## Project Structure
242
+
243
+ ```
244
+ ldap_manager/
245
+ ├── __init__.py # Package metadata
246
+ ├── cli.py # Click CLI — 40+ commands
247
+ ├── config.py # YAML + env config loading
248
+ ├── connection.py # LDAP connection context manager
249
+ ├── users.py # User CRUD, enable/disable, search
250
+ ├── groups.py # Group management (posixGroup + groupOfNames)
251
+ ├── passwords.py # Bulk password generation + reset
252
+ ├── backup.py # slapcat/slapadd dump + restore
253
+ ├── batch.py # Bulk operations from CSV/JSON/TSV
254
+ ├── server.py # Server status, start/stop, reindex
255
+ ├── sshkeys.py # SSH public key management
256
+ ├── ppolicy.py # Password policy status + checks
257
+ ├── ldif_ops.py # RFC 2849 LDIF export/import
258
+ ├── tree.py # OU/DIT management + visualization
259
+ └── audit.py # JSON-lines audit logging
260
+ ```
261
+
262
+ ---
263
+
264
+ ## Design Decisions
265
+
266
+ - **`slapcat`/`slapadd` for backup** — protocol-level export (`ldapsearch`) is lossy. It misses `cn=config`, overlays, ACLs, and operational attributes. `slapcat` captures everything.
267
+ - **`loginShell` for enable/disable** — simpler and more portable than `shadowExpire` or `nsAccountLock`. No overlay needed.
268
+ - **SSHA passwords** — most universally supported LDAP hash. Change `hash_scheme` in config if your server has `pw-argon2`.
269
+ - **Dry-run on destructive ops** — batch, import, delete, and password reset all support `--dry-run`.
270
+ - **JSON output everywhere** — `--json` on 12+ commands for piping to `jq`, scripts, and monitoring.
271
+
272
+ ---
273
+
274
+ ## Requirements
275
+
276
+ - Python 3.10+
277
+ - OpenLDAP client libraries (`libldap2-dev` / `openldap-devel`)
278
+ - OpenLDAP server tools on the LDAP host (for backup/restore and server ops)
279
+
280
+ ## License
281
+
282
+ MIT
@@ -0,0 +1,3 @@
1
+ """ldap-manager: LDAP server management CLI."""
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1,145 @@
1
+ """Structured audit logging for LDAP operations.
2
+
3
+ Writes a JSON-lines audit log of every modification operation.
4
+ Each line is a self-contained JSON object with timestamp, action,
5
+ target, operator, and details.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import logging
12
+ import os
13
+ from datetime import UTC, datetime
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ log = logging.getLogger(__name__)
18
+
19
+ # Default audit log location
20
+ DEFAULT_AUDIT_LOG = "/var/log/ldap-manager/audit.jsonl"
21
+
22
+
23
+ class AuditLogger:
24
+ """Append-only JSON-lines audit logger."""
25
+
26
+ def __init__(self, log_path: str | Path | None = None) -> None:
27
+ if log_path is None:
28
+ log_path = os.environ.get("LDAP_MANAGER_AUDIT_LOG", DEFAULT_AUDIT_LOG)
29
+ self._path = Path(log_path)
30
+ self._enabled = True
31
+
32
+ # Try to create the log directory and file
33
+ try:
34
+ self._path.parent.mkdir(parents=True, exist_ok=True)
35
+ # Touch the file to verify we can write
36
+ if not self._path.exists():
37
+ self._path.touch(mode=0o640)
38
+ except PermissionError:
39
+ log.warning(
40
+ "Cannot write audit log to %s — audit logging disabled. "
41
+ "Create the directory or set LDAP_MANAGER_AUDIT_LOG env var.",
42
+ self._path,
43
+ )
44
+ self._enabled = False
45
+
46
+ @property
47
+ def enabled(self) -> bool:
48
+ return self._enabled
49
+
50
+ @property
51
+ def path(self) -> Path:
52
+ return self._path
53
+
54
+ def log(
55
+ self,
56
+ action: str,
57
+ target: str,
58
+ *,
59
+ operator: str = "",
60
+ details: dict[str, Any] | None = None,
61
+ success: bool = True,
62
+ error: str = "",
63
+ ) -> None:
64
+ """Write an audit log entry.
65
+
66
+ Args:
67
+ action: Operation performed (e.g. "user.create", "group.add_member")
68
+ target: DN or identifier of the target (e.g. "uid=jdoe,ou=People,...")
69
+ operator: Who performed the action (bind DN, defaults to config bind_dn)
70
+ details: Additional structured data about the operation
71
+ success: Whether the operation succeeded
72
+ error: Error message if failed
73
+ """
74
+ if not self._enabled:
75
+ return
76
+
77
+ entry = {
78
+ "timestamp": datetime.now(UTC).isoformat(),
79
+ "action": action,
80
+ "target": target,
81
+ "operator": operator,
82
+ "success": success,
83
+ "hostname": os.uname().nodename,
84
+ }
85
+
86
+ if details:
87
+ entry["details"] = details
88
+ if error:
89
+ entry["error"] = error
90
+
91
+ try:
92
+ line = json.dumps(entry, ensure_ascii=False, separators=(",", ":"))
93
+ with open(self._path, "a", encoding="utf-8") as f:
94
+ f.write(line + "\n")
95
+ except (OSError, PermissionError) as exc:
96
+ log.warning("Failed to write audit log: %s", exc)
97
+
98
+ def query(
99
+ self,
100
+ *,
101
+ action: str | None = None,
102
+ target: str | None = None,
103
+ since: str | None = None,
104
+ limit: int = 50,
105
+ ) -> list[dict[str, Any]]:
106
+ """Query the audit log.
107
+
108
+ Args:
109
+ action: Filter by action prefix (e.g. "user" matches "user.create")
110
+ target: Filter by target substring
111
+ since: ISO timestamp — only entries after this time
112
+ limit: Max entries to return (newest first)
113
+
114
+ Returns:
115
+ List of audit log entries, newest first.
116
+ """
117
+ if not self._path.is_file():
118
+ return []
119
+
120
+ entries: list[dict[str, Any]] = []
121
+ try:
122
+ with open(self._path, encoding="utf-8") as f:
123
+ for line in f:
124
+ line = line.strip()
125
+ if not line:
126
+ continue
127
+ try:
128
+ entry = json.loads(line)
129
+ except json.JSONDecodeError:
130
+ continue
131
+
132
+ if action and not entry.get("action", "").startswith(action):
133
+ continue
134
+ if target and target not in entry.get("target", ""):
135
+ continue
136
+ if since and entry.get("timestamp", "") < since:
137
+ continue
138
+
139
+ entries.append(entry)
140
+ except OSError:
141
+ return []
142
+
143
+ # Newest first, limited
144
+ entries.reverse()
145
+ return entries[:limit]