python4cpm 1.1.8__tar.gz → 1.1.9__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.
- python4cpm-1.1.9/PKG-INFO +330 -0
- python4cpm-1.1.9/README.md +319 -0
- {python4cpm-1.1.8 → python4cpm-1.1.9}/pyproject.toml +2 -2
- {python4cpm-1.1.8 → python4cpm-1.1.9}/src/python4cpm/devhelper.py +15 -6
- python4cpm-1.1.9/src/python4cpm.egg-info/PKG-INFO +330 -0
- python4cpm-1.1.8/PKG-INFO +0 -215
- python4cpm-1.1.8/README.md +0 -204
- python4cpm-1.1.8/src/python4cpm.egg-info/PKG-INFO +0 -215
- {python4cpm-1.1.8 → python4cpm-1.1.9}/LICENSE +0 -0
- {python4cpm-1.1.8 → python4cpm-1.1.9}/setup.cfg +0 -0
- {python4cpm-1.1.8 → python4cpm-1.1.9}/src/python4cpm/__init__.py +0 -0
- {python4cpm-1.1.8 → python4cpm-1.1.9}/src/python4cpm/accounts.py +0 -0
- {python4cpm-1.1.8 → python4cpm-1.1.9}/src/python4cpm/args.py +0 -0
- {python4cpm-1.1.8 → python4cpm-1.1.9}/src/python4cpm/crypto.py +0 -0
- {python4cpm-1.1.8 → python4cpm-1.1.9}/src/python4cpm/envhandler.py +0 -0
- {python4cpm-1.1.8 → python4cpm-1.1.9}/src/python4cpm/exithandler.py +0 -0
- {python4cpm-1.1.8 → python4cpm-1.1.9}/src/python4cpm/loggerhandler.py +0 -0
- {python4cpm-1.1.8 → python4cpm-1.1.9}/src/python4cpm/python4cpm.py +0 -0
- {python4cpm-1.1.8 → python4cpm-1.1.9}/src/python4cpm/python4cpmhandler.py +0 -0
- {python4cpm-1.1.8 → python4cpm-1.1.9}/src/python4cpm/secrets.py +0 -0
- {python4cpm-1.1.8 → python4cpm-1.1.9}/src/python4cpm.egg-info/SOURCES.txt +0 -0
- {python4cpm-1.1.8 → python4cpm-1.1.9}/src/python4cpm.egg-info/dependency_links.txt +0 -0
- {python4cpm-1.1.8 → python4cpm-1.1.9}/src/python4cpm.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python4cpm
|
|
3
|
+
Version: 1.1.9
|
|
4
|
+
Summary: Python for CPM
|
|
5
|
+
Author-email: Gonzalo Atienza Rela <gonatienza@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Dynamic: license-file
|
|
11
|
+
|
|
12
|
+
# Python4CPM
|
|
13
|
+
|
|
14
|
+
A simple and secure way of using python scripts with Idira/CyberArk CPM/SRS password rotations.
|
|
15
|
+
|
|
16
|
+
## How it works
|
|
17
|
+
|
|
18
|
+
This module leverages the [Credential Management .NET SDK](https://docs.cyberark.com/privilege-cloud-standard/latest/en/content/pasimp/plug-in-netinvoker.htm) from CyberArk to securely offload a password rotation logic into Python.
|
|
19
|
+
|
|
20
|
+
All objects are collected from the SDK and sent as environment context to be picked up by the `python4cpm` module during the subprocess execution of python. All secrets of such environment are protected and encrypted by [Data Protection API (DPAPI)](https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection), until they are explicitely retrieved in your python script runtime, invoking the `Secret.get()` method. Finally, python controls the termination signal sent back to the SDK, which is consequently used as the return code to CPM/SRS. Such as a successful or failed (recoverable or not) result of the requested action.
|
|
21
|
+
|
|
22
|
+
This platform allows you to duplicate it multiple times, simply changing its settings (from Privilege Cloud/PVWA) to point to different venvs and/or python scripts.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
### Preparing Python
|
|
27
|
+
|
|
28
|
+
1. Install Python 3.11 or later along CPM or the SRS Connector Management Agent.
|
|
29
|
+
- **Python must be installed for all users**. Follow the custom install steps from the installation wizard to check the checkbox.
|
|
30
|
+
2. Create a venv in the server, by running `py -m venv c:\venv`. If desired, use a custom location and adjust any future references.
|
|
31
|
+
3. Install `python4cpm` in your venv:
|
|
32
|
+
- If your CPM/SRS can connect to the internet, install with `c:\venv\Scripts\pip.exe install python4cpm`.
|
|
33
|
+
- If your CPM/SRS cannot connect to the internet:
|
|
34
|
+
- Download the latest `python4cpm-*.whl` file from the [pypi project files](https://pypi.org/project/python4cpm/#files).
|
|
35
|
+
- Copy the file to the server into a temporary directory called `python4cpm-wheel`.
|
|
36
|
+
- From the parent directory of `python4cpm-wheel` run `c:\venv\Scripts\pip.exe install --no-index --find-links=.\python4cpm-wheel python4cpm`.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
### Importing the platform
|
|
40
|
+
|
|
41
|
+
#### If you are using CPM (SaaS or Self-Hosted):
|
|
42
|
+
1. Download the latest [Credential Management .NET SDK](https://community.cyberark.com/marketplace/s/#a3550000000EkA0AAK-a3950000000jjoOAAQ) and place its content in the bin folder of CPM (`C:\Program Files (x86)\CyberArk\Password Manager\bin`). The files for this may already be present.
|
|
43
|
+
2. Download the `python4cpm-platform-*.zip` asset from the latest [release](https://github.com/gonatienza/python4cpm/releases).
|
|
44
|
+
3. Import the platform zip file into Privilege Cloud/PVWA `(Administration -> Platform Management -> Import platform)`.
|
|
45
|
+
4. Craft your python script and place it within a folder in CPM (e.g., `C:\python4cpm-scripts`).
|
|
46
|
+
5. Duplicate the imported platform in Privilege Cloud/PVWA `(Administration -> Platform Management -> Application -> Python for CPM)` and name it after your application (e.g., My App).
|
|
47
|
+
6. Edit the duplicated platform and specify the path of your script, under `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonScriptPath -> Value` (e.g., `C:\python4cpm-scripts\myapp.py`).
|
|
48
|
+
7. Also update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonExePath -> Value` with the custom path for the venv's `python.exe` file (e.g., `c:\venv\Scripts\python.exe`).
|
|
49
|
+
8. If you want to change the logging level, update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonLoggingLevel -> Value` to `critical`, `error`, `warning`, `info` or `debug`.
|
|
50
|
+
9. For new applications repeat steps from 4 to 8.
|
|
51
|
+
|
|
52
|
+
#### If you are using SRS (SaaS only):
|
|
53
|
+
1. Download the `python4cpm-platform-*.zip` asset from the latest [release](https://github.com/gonatienza/python4cpm/releases).
|
|
54
|
+
2. Import the platform zip file into Privilege Cloud `(Administration -> Platform Management -> Import platform)`.
|
|
55
|
+
3. Craft your python script and place it within a folder in the Cloud Connector (where the SRS Management Agent runs) (e.g., `C:\python4cpm-scripts`).
|
|
56
|
+
4. Duplicate the imported platform in Privilege Cloud/PVWA `(Administration -> Platform Management -> Application -> Python for CPM)` and name it after your application (e.g., My App).
|
|
57
|
+
5. Edit the duplicated platform and specify the path of your script, under `Plugin Settings -> Additional Parameters -> PythonScriptPath` (e.g., `C:\python4cpm-scripts\myapp.py`).
|
|
58
|
+
6. Also update `Plugin Settings -> Additional Parameters -> PythonExePath` with the custom path for the venv's `python.exe` file (e.g., `c:\venv\Scripts\python.exe`).
|
|
59
|
+
7. If you want to change the logging level, update `Plugin Settings -> Additional Parameters -> PythonLoggingLevel -> Value` to `critical`, `error`, `warning`, `info` or `debug`.
|
|
60
|
+
8. For new applications repeat steps from 3 to 7.
|
|
61
|
+
|
|
62
|
+
### Platform defaults
|
|
63
|
+
|
|
64
|
+
**Automatic verification and change are enabled by default.** Only assign accounts to a duplicated platform that has `PythonExePath` and `PythonScriptPath` set. The imported platform ships with the placeholder `ReplaceWithAbsolutePath`, which fails with `8900` without starting Python. Adjust these defaults to match your application:
|
|
65
|
+
|
|
66
|
+
| Setting | Default | Meaning |
|
|
67
|
+
| --- | --- | --- |
|
|
68
|
+
| `PerformPeriodicChange` | `Yes` | Rotate automatically |
|
|
69
|
+
| `ExpirationPeriod` | `90` | Days before a password expires |
|
|
70
|
+
| `VFPerformPeriodicVerification` | `Yes` | Verify automatically |
|
|
71
|
+
| `Interval` | `1440` | Minutes |
|
|
72
|
+
| `Timeout` | `120` | Seconds |
|
|
73
|
+
| `MaximumRetries` | `5` | Retries |
|
|
74
|
+
| `MinDelayBetweenRetries` | `90` | Minutes between retries |
|
|
75
|
+
| `UnrecoverableErrors` | `8900` | Return codes treated as unrecoverable |
|
|
76
|
+
| `RCAutomaticReconcileWhenUnsynched` | `Yes` | Reconcile when unsynched |
|
|
77
|
+
| `PasswordLength`, `MinUpperCase`, `MinLowerCase`, `MinDigit`, `MinSpecial` | `12`, `2`, `2`, `1`, `1` | Rules for generating `new_password` |
|
|
78
|
+
| `PasswordForbiddenChars` | Empty | Characters the new password must not contain |
|
|
79
|
+
|
|
80
|
+
The new password is generated for you, so **make the password rules match the target system's policy**. Your script only applies it.
|
|
81
|
+
|
|
82
|
+
Accounts require `Username` and `Address`, and `Port` is optional. Link a `LogonAccount` and a `ReconcileAccount` if your script needs them.
|
|
83
|
+
|
|
84
|
+
## Python Script
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from python4cpm import Python4CPMHandler
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class CredManager(Python4CPMHandler):
|
|
91
|
+
"""
|
|
92
|
+
Properties:
|
|
93
|
+
target_account (TargetAccount): Account being managed.
|
|
94
|
+
.username (str): Account username.
|
|
95
|
+
.address (str): Target address.
|
|
96
|
+
.port (str | None): Target port.
|
|
97
|
+
.policy_id (str): Platform ID.
|
|
98
|
+
.safe_name (str): Safe name.
|
|
99
|
+
.object_name (str): Account name in the vault.
|
|
100
|
+
.password (Secret | None): Current password. Call .get() to retrieve value as many times as needed.
|
|
101
|
+
.new_password (Secret | None): Replacement password. Call .get() to retrieve value as many times as needed.
|
|
102
|
+
|
|
103
|
+
logon_account (LogonAccount | None): Linked Logon Account.
|
|
104
|
+
.username (str): Account username.
|
|
105
|
+
.password (Secret | None): Logon password. Call .get() to retrieve value.
|
|
106
|
+
|
|
107
|
+
reconcile_account (ReconcileAccount | None): Linked Reconcile Account.
|
|
108
|
+
.username (str): Account username.
|
|
109
|
+
.password (Secret | None): Reconcile password. Call .get() to retrieve value.
|
|
110
|
+
|
|
111
|
+
args (Args): Execution arguments.
|
|
112
|
+
.action (str): Requested action.
|
|
113
|
+
.logging_level (str | None): Value of PythonLoggingLevel.
|
|
114
|
+
|
|
115
|
+
logger (logging.Logger): Logger instance.
|
|
116
|
+
|
|
117
|
+
Methods:
|
|
118
|
+
close_success(): Signal successful completion and terminate.
|
|
119
|
+
close_fail(): Signal failed completion and terminate.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def verify(self):
|
|
124
|
+
"""
|
|
125
|
+
REQUIRED METHOD
|
|
126
|
+
"""
|
|
127
|
+
# TODO: use account objects for your logic
|
|
128
|
+
self.close_success()
|
|
129
|
+
|
|
130
|
+
def logon(self):
|
|
131
|
+
"""
|
|
132
|
+
REQUIRED METHOD
|
|
133
|
+
"""
|
|
134
|
+
# TODO: use account objects for your logic
|
|
135
|
+
self.close_success()
|
|
136
|
+
|
|
137
|
+
def change(self):
|
|
138
|
+
"""
|
|
139
|
+
REQUIRED METHOD
|
|
140
|
+
"""
|
|
141
|
+
# TODO: use account objects for your logic
|
|
142
|
+
self.close_success()
|
|
143
|
+
|
|
144
|
+
def prereconcile(self):
|
|
145
|
+
"""
|
|
146
|
+
REQUIRED METHOD
|
|
147
|
+
"""
|
|
148
|
+
# TODO: use account objects for your logic
|
|
149
|
+
self.close_success()
|
|
150
|
+
|
|
151
|
+
def reconcile(self):
|
|
152
|
+
"""
|
|
153
|
+
REQUIRED METHOD
|
|
154
|
+
"""
|
|
155
|
+
# TODO: use account objects for your logic
|
|
156
|
+
self.close_success()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
if __name__ == "__main__":
|
|
160
|
+
CredManager().run() # initializes the class and calls the action that was requested from CPM/SRS.
|
|
161
|
+
```
|
|
162
|
+
(*) More realistic examples can be found [here](https://github.com/gonatienza/python4cpm/blob/main/examples).
|
|
163
|
+
|
|
164
|
+
When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
|
|
165
|
+
1. Verify -> the script will be executed once running the `verify()` method.
|
|
166
|
+
2. Change -> the script will be executed twice: first `logon()`, then `change()`.
|
|
167
|
+
3. Reconcile -> the script will be executed twice: first `prereconcile()`, then `reconcile()`.
|
|
168
|
+
4. When calling `verify()`, `logon()` or `prereconcile()`: `target_account.new_password` will always return `None`.
|
|
169
|
+
5. If a logon account is not linked, `logon_account` will return `None`.
|
|
170
|
+
6. If a reconcile account is not linked, `reconcile_account` will return `None`.
|
|
171
|
+
7. **Always** use the `close_success` or `close_fail` methods to signal the proper termination for all actions.
|
|
172
|
+
- `close_fail` takes the optional argument of `unrecoverable` as a bool flag (by default set to `False`). It signals CPM whether the error you encountered cannot be solved through the rotation logic or requires manual intervention. E.g., a regular rotation fails (which is still recoverable) and when called for a reconciliation process it fails too (at this point the error encountered is not recoverable).
|
|
173
|
+
- If any action is not terminated with a termination method, CPM/SRS will see this as a `close_fail(unrecoverable=True)`. This happens regardless of whether it was terminated by an exception or no exception was raised. **If you rotate a credential successfully and do not close with success, you will have a rotated credential that will be lost and not saved into the vault**.
|
|
174
|
+
8. The python `Logger` places its logs in the `Logs/ThirdParty` directory.
|
|
175
|
+
|
|
176
|
+
### Writing a rotation script
|
|
177
|
+
|
|
178
|
+
#### What each method should do
|
|
179
|
+
|
|
180
|
+
| Method | Action | `new_password` | What to do |
|
|
181
|
+
| --- | --- | --- | --- |
|
|
182
|
+
| `verify()` | `verifypass` | `None` | Authenticate with `target_account`. |
|
|
183
|
+
| `logon()` | `logon` | `None` | Prepare for `change()`. It can just call `close_success()`. |
|
|
184
|
+
| `change()` | `changepass` | Set | Authenticate, then set `target_account.new_password` on the target. |
|
|
185
|
+
| `prereconcile()` | `prereconcilepass` | `None` | Authenticate with `reconcile_account`. |
|
|
186
|
+
| `reconcile()` | `reconcilepass` | Set | Authenticate with `reconcile_account`, then set `target_account.new_password` on `target_account.username`. |
|
|
187
|
+
|
|
188
|
+
Use `reconcile_account` for `prereconcile()` and `reconcile()`, because `target_account.password` is not expected to work when reconciling.
|
|
189
|
+
|
|
190
|
+
#### Return codes
|
|
191
|
+
|
|
192
|
+
| Script result | Python exit code | Returned to CPM/SRS |
|
|
193
|
+
| --- | --- | --- |
|
|
194
|
+
| `close_success()` | `10` | `0` (success) |
|
|
195
|
+
| `close_fail()` | `81` | `8100` (recoverable) |
|
|
196
|
+
| `close_fail(unrecoverable=True)` | `89` | `8900` (unrecoverable) |
|
|
197
|
+
| Unhandled exception | `1` | `8900` (unrecoverable) |
|
|
198
|
+
| No close method called | `0` | `8900` (unrecoverable) |
|
|
199
|
+
| Any other exit code | That code | `8900` (unrecoverable) |
|
|
200
|
+
| `PythonExePath` or `PythonScriptPath` not found | Python is not started | `8900` (unrecoverable) |
|
|
201
|
+
|
|
202
|
+
#### Rules for your script
|
|
203
|
+
|
|
204
|
+
- **Every method runs in a new python process.** Nothing in memory is shared between `logon()` and `change()`, or between `prereconcile()` and `reconcile()`.
|
|
205
|
+
- **Implement all five methods.** A missing method raises a `TypeError` when the class is created, which returns `8900`.
|
|
206
|
+
- **If you override `__init__`, call `super().__init__()` first.**
|
|
207
|
+
- **Check for `None`.** Empty values are not sent, so these can all be `None`: `port`, any `password`, and `new_password`. A linked account can have a username and no password. `.get()` on `None` raises an `AttributeError`.
|
|
208
|
+
- **`address` is the raw value from the account.** Build URLs yourself, and pick a default for when `port` is `None`.
|
|
209
|
+
- **Change the password last.** Close with `close_success()` straight after the target accepts it, and do nothing that can fail in between.
|
|
210
|
+
- **Close methods raise `SystemExit`.** They are safe inside `try/except Exception`, and code after them does not run, except `finally` blocks. A bare `except:` or `except BaseException:` catches them, and that action then returns `8900`.
|
|
211
|
+
- **Set a timeout on every network call.** The plugin waits for python with no time limit of its own, so keep each action under the platform's `Timeout`.
|
|
212
|
+
- **Use `self.logger`, not `print`.** Standard output is not captured. Standard error is written to the plugin log when an action fails.
|
|
213
|
+
- **Never log the result of `.get()`.** Logging a `Secret` itself is safe and shows `[ENCRYPTED]`. Keep secrets out of exception messages too, as tracebacks reach the plugin log.
|
|
214
|
+
- **Use absolute paths.** The working directory is inherited from the CPM/SRS process, not taken from your script's folder.
|
|
215
|
+
- **Keep TLS verification on in production.** The `simpleauthcredmanager.py` example turns it off.
|
|
216
|
+
|
|
217
|
+
Common failures can be handled like this:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
def verify(self):
|
|
221
|
+
try:
|
|
222
|
+
self._login(self.target_account)
|
|
223
|
+
except Exception as e:
|
|
224
|
+
self.logger.error(f"{type(e).__name__}: {e}")
|
|
225
|
+
self.close_fail()
|
|
226
|
+
self.close_success()
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Use `close_fail()` for what a later attempt could fix, like a timeout or a rejected password. Use `close_fail(unrecoverable=True)` for what needs manual intervention.
|
|
230
|
+
|
|
231
|
+
#### Logs
|
|
232
|
+
|
|
233
|
+
All logs are in `Logs/ThirdParty`, relative to the working directory.
|
|
234
|
+
|
|
235
|
+
| File | Content |
|
|
236
|
+
| --- | --- |
|
|
237
|
+
| `python4cpm.loggerhandler_<level>_<PolicyID>-<SafeName>-<ObjectName>.log` | `self.logger` output. Rotates at 1 MB and keeps one backup. |
|
|
238
|
+
| `<PolicyID>-<SafeName>-<FolderName>-<ObjectName>.Log` | The last error, with its error code. |
|
|
239
|
+
| `Debug_<PolicyID>-<SafeName>-<FolderName>-<ObjectName>_CANetPluginInvoker_<date>.log` | Plugin trace, written with `Debug=Yes` in a manual run, including paths, values and standard error. |
|
|
240
|
+
|
|
241
|
+
The default level is `error`, so `self.logger.info()` and `self.logger.debug()` write nothing until you change `PythonLoggingLevel`. An unknown level falls back to `error`, and `critical` is the quietest. Logging cannot be turned off.
|
|
242
|
+
|
|
243
|
+
#### Troubleshooting
|
|
244
|
+
|
|
245
|
+
| Log message | Cause |
|
|
246
|
+
| --- | --- |
|
|
247
|
+
| `PythonExePath: '...' not found` or `PythonScriptPath: '...' not found` | Wrong path, or the account is still on the imported platform. |
|
|
248
|
+
| `Python closed with exit code: 0` and `No close signal called` | A method ended without a close method. |
|
|
249
|
+
| `Python closed with exit code: 1` | Unhandled exception. The traceback follows in `StdErr Output`. |
|
|
250
|
+
| `Python closed with failed recoverable` | The script called `close_fail()`. |
|
|
251
|
+
| `Python closed with failed unrecoverable` | The script called `close_fail(unrecoverable=True)`. |
|
|
252
|
+
| `can't open file '...\ C:\...'` | A manual `.ini` file has spaces around `=`. |
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
### Installing dependencies in python venv
|
|
256
|
+
|
|
257
|
+
As with any python venv, you can install dependencies in your venv.
|
|
258
|
+
1. If your CPM can connect to the internet:
|
|
259
|
+
- You can use regular pip install commands (e.g., `c:\venv\Scripts\pip.exe install requests`).
|
|
260
|
+
2. If your CPM cannot connect to the internet:
|
|
261
|
+
- You can download packages for an offline install. More info [here](https://pip.pypa.io/en/stable/cli/pip_download/).
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
## Dev Helper:
|
|
265
|
+
|
|
266
|
+
For dev purposes, `DevHelper` is a companion helper to test your scripts without CPM/SRS. It simplifies the instantiation of the `Python4CPM` or `Python4CPMHandler` objects by simulating how the plugin creates the environment context for the python module.
|
|
267
|
+
|
|
268
|
+
**Note**: As CPM and the SRS management agent run in Windows, the plugin was built to encrypt secrets using DPAPI (a windows only library). For dev purposes in Linux/Mac dev workstations, those secrets put in the environment context by `DevHelper` will be in plaintext. In windows dev workstations, `DevHelper` encrypts the secrets as the .NET plugin does. This is informational only, **the module will use its encryption/decryption capabilities automatically based on the platform** it is running on and you do not have to do anything specific to enable it.
|
|
269
|
+
|
|
270
|
+
### Example:
|
|
271
|
+
|
|
272
|
+
#### Set your arguments and secrets:
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
from python4cpm import DevHelper, Python4CPM, Python4CPMHandler
|
|
276
|
+
|
|
277
|
+
DevHelper.set(
|
|
278
|
+
action=Python4CPM.ACTION_CHANGE, # use actions from Python4CPM.ACTION_*
|
|
279
|
+
logging_level="debug",
|
|
280
|
+
target_policy_id="DevHelper", # name of platform
|
|
281
|
+
target_safe_name="Safename", # name of safe
|
|
282
|
+
target_object_name="Objectname", # name of account
|
|
283
|
+
target_username="jdoe",
|
|
284
|
+
target_address="myapp.corp.local",
|
|
285
|
+
target_port="8443",
|
|
286
|
+
logon_username="ldoe",
|
|
287
|
+
reconcile_username="rdoe",
|
|
288
|
+
target_password="", # str value of target password (required by most scripts)
|
|
289
|
+
logon_password="", # str value of logon password (leave out if not linked)
|
|
290
|
+
reconcile_password="", # str value of reconcile password (leave out if not linked)
|
|
291
|
+
target_new_password="" # str value of new password (only for change and reconcile)
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
class CredManager(Python4CPMHandler):
|
|
295
|
+
def verify(self):
|
|
296
|
+
# TODO: Add your logic here
|
|
297
|
+
self.close_success()
|
|
298
|
+
|
|
299
|
+
def logon(self):
|
|
300
|
+
# TODO: Add your logic here
|
|
301
|
+
self.close_success()
|
|
302
|
+
|
|
303
|
+
def change(self):
|
|
304
|
+
# TODO: Add your logic here
|
|
305
|
+
self.close_success()
|
|
306
|
+
|
|
307
|
+
def prereconcile(self):
|
|
308
|
+
# TODO: Add your logic here
|
|
309
|
+
self.close_success()
|
|
310
|
+
|
|
311
|
+
def reconcile(self):
|
|
312
|
+
# TODO: Add your logic here
|
|
313
|
+
self.close_success()
|
|
314
|
+
|
|
315
|
+
CredManager().run()
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
#### Remember when using `DevHelper`:
|
|
319
|
+
|
|
320
|
+
- `target_policy_id`, `target_safe_name` and `target_object_name` are required. The log file name is built from them.
|
|
321
|
+
- A password left out or set to `""` becomes `None`, as in production. Leave out `target_new_password` when testing `verify()`, `logon()` or `prereconcile()`.
|
|
322
|
+
- Each `DevHelper.set()` call replaces the previous one, so anything left out is cleared.
|
|
323
|
+
- Each run tests one `action`, so set it to test each of the five methods.
|
|
324
|
+
- Exit code `10` means success, and `81` or `89` mean the script called `close_fail()`. Anything else returns `8900` in production.
|
|
325
|
+
- **Your script runs for real.** `change()` and `reconcile()` change the password on the target, so use a test account.
|
|
326
|
+
|
|
327
|
+
#### Remember for your final script:
|
|
328
|
+
|
|
329
|
+
- Remove the import of `DevHelper`.
|
|
330
|
+
- Remove the `DevHelper.set()` call.
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# Python4CPM
|
|
2
|
+
|
|
3
|
+
A simple and secure way of using python scripts with Idira/CyberArk CPM/SRS password rotations.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
This module leverages the [Credential Management .NET SDK](https://docs.cyberark.com/privilege-cloud-standard/latest/en/content/pasimp/plug-in-netinvoker.htm) from CyberArk to securely offload a password rotation logic into Python.
|
|
8
|
+
|
|
9
|
+
All objects are collected from the SDK and sent as environment context to be picked up by the `python4cpm` module during the subprocess execution of python. All secrets of such environment are protected and encrypted by [Data Protection API (DPAPI)](https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection), until they are explicitely retrieved in your python script runtime, invoking the `Secret.get()` method. Finally, python controls the termination signal sent back to the SDK, which is consequently used as the return code to CPM/SRS. Such as a successful or failed (recoverable or not) result of the requested action.
|
|
10
|
+
|
|
11
|
+
This platform allows you to duplicate it multiple times, simply changing its settings (from Privilege Cloud/PVWA) to point to different venvs and/or python scripts.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
### Preparing Python
|
|
16
|
+
|
|
17
|
+
1. Install Python 3.11 or later along CPM or the SRS Connector Management Agent.
|
|
18
|
+
- **Python must be installed for all users**. Follow the custom install steps from the installation wizard to check the checkbox.
|
|
19
|
+
2. Create a venv in the server, by running `py -m venv c:\venv`. If desired, use a custom location and adjust any future references.
|
|
20
|
+
3. Install `python4cpm` in your venv:
|
|
21
|
+
- If your CPM/SRS can connect to the internet, install with `c:\venv\Scripts\pip.exe install python4cpm`.
|
|
22
|
+
- If your CPM/SRS cannot connect to the internet:
|
|
23
|
+
- Download the latest `python4cpm-*.whl` file from the [pypi project files](https://pypi.org/project/python4cpm/#files).
|
|
24
|
+
- Copy the file to the server into a temporary directory called `python4cpm-wheel`.
|
|
25
|
+
- From the parent directory of `python4cpm-wheel` run `c:\venv\Scripts\pip.exe install --no-index --find-links=.\python4cpm-wheel python4cpm`.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Importing the platform
|
|
29
|
+
|
|
30
|
+
#### If you are using CPM (SaaS or Self-Hosted):
|
|
31
|
+
1. Download the latest [Credential Management .NET SDK](https://community.cyberark.com/marketplace/s/#a3550000000EkA0AAK-a3950000000jjoOAAQ) and place its content in the bin folder of CPM (`C:\Program Files (x86)\CyberArk\Password Manager\bin`). The files for this may already be present.
|
|
32
|
+
2. Download the `python4cpm-platform-*.zip` asset from the latest [release](https://github.com/gonatienza/python4cpm/releases).
|
|
33
|
+
3. Import the platform zip file into Privilege Cloud/PVWA `(Administration -> Platform Management -> Import platform)`.
|
|
34
|
+
4. Craft your python script and place it within a folder in CPM (e.g., `C:\python4cpm-scripts`).
|
|
35
|
+
5. Duplicate the imported platform in Privilege Cloud/PVWA `(Administration -> Platform Management -> Application -> Python for CPM)` and name it after your application (e.g., My App).
|
|
36
|
+
6. Edit the duplicated platform and specify the path of your script, under `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonScriptPath -> Value` (e.g., `C:\python4cpm-scripts\myapp.py`).
|
|
37
|
+
7. Also update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonExePath -> Value` with the custom path for the venv's `python.exe` file (e.g., `c:\venv\Scripts\python.exe`).
|
|
38
|
+
8. If you want to change the logging level, update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonLoggingLevel -> Value` to `critical`, `error`, `warning`, `info` or `debug`.
|
|
39
|
+
9. For new applications repeat steps from 4 to 8.
|
|
40
|
+
|
|
41
|
+
#### If you are using SRS (SaaS only):
|
|
42
|
+
1. Download the `python4cpm-platform-*.zip` asset from the latest [release](https://github.com/gonatienza/python4cpm/releases).
|
|
43
|
+
2. Import the platform zip file into Privilege Cloud `(Administration -> Platform Management -> Import platform)`.
|
|
44
|
+
3. Craft your python script and place it within a folder in the Cloud Connector (where the SRS Management Agent runs) (e.g., `C:\python4cpm-scripts`).
|
|
45
|
+
4. Duplicate the imported platform in Privilege Cloud/PVWA `(Administration -> Platform Management -> Application -> Python for CPM)` and name it after your application (e.g., My App).
|
|
46
|
+
5. Edit the duplicated platform and specify the path of your script, under `Plugin Settings -> Additional Parameters -> PythonScriptPath` (e.g., `C:\python4cpm-scripts\myapp.py`).
|
|
47
|
+
6. Also update `Plugin Settings -> Additional Parameters -> PythonExePath` with the custom path for the venv's `python.exe` file (e.g., `c:\venv\Scripts\python.exe`).
|
|
48
|
+
7. If you want to change the logging level, update `Plugin Settings -> Additional Parameters -> PythonLoggingLevel -> Value` to `critical`, `error`, `warning`, `info` or `debug`.
|
|
49
|
+
8. For new applications repeat steps from 3 to 7.
|
|
50
|
+
|
|
51
|
+
### Platform defaults
|
|
52
|
+
|
|
53
|
+
**Automatic verification and change are enabled by default.** Only assign accounts to a duplicated platform that has `PythonExePath` and `PythonScriptPath` set. The imported platform ships with the placeholder `ReplaceWithAbsolutePath`, which fails with `8900` without starting Python. Adjust these defaults to match your application:
|
|
54
|
+
|
|
55
|
+
| Setting | Default | Meaning |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `PerformPeriodicChange` | `Yes` | Rotate automatically |
|
|
58
|
+
| `ExpirationPeriod` | `90` | Days before a password expires |
|
|
59
|
+
| `VFPerformPeriodicVerification` | `Yes` | Verify automatically |
|
|
60
|
+
| `Interval` | `1440` | Minutes |
|
|
61
|
+
| `Timeout` | `120` | Seconds |
|
|
62
|
+
| `MaximumRetries` | `5` | Retries |
|
|
63
|
+
| `MinDelayBetweenRetries` | `90` | Minutes between retries |
|
|
64
|
+
| `UnrecoverableErrors` | `8900` | Return codes treated as unrecoverable |
|
|
65
|
+
| `RCAutomaticReconcileWhenUnsynched` | `Yes` | Reconcile when unsynched |
|
|
66
|
+
| `PasswordLength`, `MinUpperCase`, `MinLowerCase`, `MinDigit`, `MinSpecial` | `12`, `2`, `2`, `1`, `1` | Rules for generating `new_password` |
|
|
67
|
+
| `PasswordForbiddenChars` | Empty | Characters the new password must not contain |
|
|
68
|
+
|
|
69
|
+
The new password is generated for you, so **make the password rules match the target system's policy**. Your script only applies it.
|
|
70
|
+
|
|
71
|
+
Accounts require `Username` and `Address`, and `Port` is optional. Link a `LogonAccount` and a `ReconcileAccount` if your script needs them.
|
|
72
|
+
|
|
73
|
+
## Python Script
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from python4cpm import Python4CPMHandler
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class CredManager(Python4CPMHandler):
|
|
80
|
+
"""
|
|
81
|
+
Properties:
|
|
82
|
+
target_account (TargetAccount): Account being managed.
|
|
83
|
+
.username (str): Account username.
|
|
84
|
+
.address (str): Target address.
|
|
85
|
+
.port (str | None): Target port.
|
|
86
|
+
.policy_id (str): Platform ID.
|
|
87
|
+
.safe_name (str): Safe name.
|
|
88
|
+
.object_name (str): Account name in the vault.
|
|
89
|
+
.password (Secret | None): Current password. Call .get() to retrieve value as many times as needed.
|
|
90
|
+
.new_password (Secret | None): Replacement password. Call .get() to retrieve value as many times as needed.
|
|
91
|
+
|
|
92
|
+
logon_account (LogonAccount | None): Linked Logon Account.
|
|
93
|
+
.username (str): Account username.
|
|
94
|
+
.password (Secret | None): Logon password. Call .get() to retrieve value.
|
|
95
|
+
|
|
96
|
+
reconcile_account (ReconcileAccount | None): Linked Reconcile Account.
|
|
97
|
+
.username (str): Account username.
|
|
98
|
+
.password (Secret | None): Reconcile password. Call .get() to retrieve value.
|
|
99
|
+
|
|
100
|
+
args (Args): Execution arguments.
|
|
101
|
+
.action (str): Requested action.
|
|
102
|
+
.logging_level (str | None): Value of PythonLoggingLevel.
|
|
103
|
+
|
|
104
|
+
logger (logging.Logger): Logger instance.
|
|
105
|
+
|
|
106
|
+
Methods:
|
|
107
|
+
close_success(): Signal successful completion and terminate.
|
|
108
|
+
close_fail(): Signal failed completion and terminate.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def verify(self):
|
|
113
|
+
"""
|
|
114
|
+
REQUIRED METHOD
|
|
115
|
+
"""
|
|
116
|
+
# TODO: use account objects for your logic
|
|
117
|
+
self.close_success()
|
|
118
|
+
|
|
119
|
+
def logon(self):
|
|
120
|
+
"""
|
|
121
|
+
REQUIRED METHOD
|
|
122
|
+
"""
|
|
123
|
+
# TODO: use account objects for your logic
|
|
124
|
+
self.close_success()
|
|
125
|
+
|
|
126
|
+
def change(self):
|
|
127
|
+
"""
|
|
128
|
+
REQUIRED METHOD
|
|
129
|
+
"""
|
|
130
|
+
# TODO: use account objects for your logic
|
|
131
|
+
self.close_success()
|
|
132
|
+
|
|
133
|
+
def prereconcile(self):
|
|
134
|
+
"""
|
|
135
|
+
REQUIRED METHOD
|
|
136
|
+
"""
|
|
137
|
+
# TODO: use account objects for your logic
|
|
138
|
+
self.close_success()
|
|
139
|
+
|
|
140
|
+
def reconcile(self):
|
|
141
|
+
"""
|
|
142
|
+
REQUIRED METHOD
|
|
143
|
+
"""
|
|
144
|
+
# TODO: use account objects for your logic
|
|
145
|
+
self.close_success()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
if __name__ == "__main__":
|
|
149
|
+
CredManager().run() # initializes the class and calls the action that was requested from CPM/SRS.
|
|
150
|
+
```
|
|
151
|
+
(*) More realistic examples can be found [here](https://github.com/gonatienza/python4cpm/blob/main/examples).
|
|
152
|
+
|
|
153
|
+
When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
|
|
154
|
+
1. Verify -> the script will be executed once running the `verify()` method.
|
|
155
|
+
2. Change -> the script will be executed twice: first `logon()`, then `change()`.
|
|
156
|
+
3. Reconcile -> the script will be executed twice: first `prereconcile()`, then `reconcile()`.
|
|
157
|
+
4. When calling `verify()`, `logon()` or `prereconcile()`: `target_account.new_password` will always return `None`.
|
|
158
|
+
5. If a logon account is not linked, `logon_account` will return `None`.
|
|
159
|
+
6. If a reconcile account is not linked, `reconcile_account` will return `None`.
|
|
160
|
+
7. **Always** use the `close_success` or `close_fail` methods to signal the proper termination for all actions.
|
|
161
|
+
- `close_fail` takes the optional argument of `unrecoverable` as a bool flag (by default set to `False`). It signals CPM whether the error you encountered cannot be solved through the rotation logic or requires manual intervention. E.g., a regular rotation fails (which is still recoverable) and when called for a reconciliation process it fails too (at this point the error encountered is not recoverable).
|
|
162
|
+
- If any action is not terminated with a termination method, CPM/SRS will see this as a `close_fail(unrecoverable=True)`. This happens regardless of whether it was terminated by an exception or no exception was raised. **If you rotate a credential successfully and do not close with success, you will have a rotated credential that will be lost and not saved into the vault**.
|
|
163
|
+
8. The python `Logger` places its logs in the `Logs/ThirdParty` directory.
|
|
164
|
+
|
|
165
|
+
### Writing a rotation script
|
|
166
|
+
|
|
167
|
+
#### What each method should do
|
|
168
|
+
|
|
169
|
+
| Method | Action | `new_password` | What to do |
|
|
170
|
+
| --- | --- | --- | --- |
|
|
171
|
+
| `verify()` | `verifypass` | `None` | Authenticate with `target_account`. |
|
|
172
|
+
| `logon()` | `logon` | `None` | Prepare for `change()`. It can just call `close_success()`. |
|
|
173
|
+
| `change()` | `changepass` | Set | Authenticate, then set `target_account.new_password` on the target. |
|
|
174
|
+
| `prereconcile()` | `prereconcilepass` | `None` | Authenticate with `reconcile_account`. |
|
|
175
|
+
| `reconcile()` | `reconcilepass` | Set | Authenticate with `reconcile_account`, then set `target_account.new_password` on `target_account.username`. |
|
|
176
|
+
|
|
177
|
+
Use `reconcile_account` for `prereconcile()` and `reconcile()`, because `target_account.password` is not expected to work when reconciling.
|
|
178
|
+
|
|
179
|
+
#### Return codes
|
|
180
|
+
|
|
181
|
+
| Script result | Python exit code | Returned to CPM/SRS |
|
|
182
|
+
| --- | --- | --- |
|
|
183
|
+
| `close_success()` | `10` | `0` (success) |
|
|
184
|
+
| `close_fail()` | `81` | `8100` (recoverable) |
|
|
185
|
+
| `close_fail(unrecoverable=True)` | `89` | `8900` (unrecoverable) |
|
|
186
|
+
| Unhandled exception | `1` | `8900` (unrecoverable) |
|
|
187
|
+
| No close method called | `0` | `8900` (unrecoverable) |
|
|
188
|
+
| Any other exit code | That code | `8900` (unrecoverable) |
|
|
189
|
+
| `PythonExePath` or `PythonScriptPath` not found | Python is not started | `8900` (unrecoverable) |
|
|
190
|
+
|
|
191
|
+
#### Rules for your script
|
|
192
|
+
|
|
193
|
+
- **Every method runs in a new python process.** Nothing in memory is shared between `logon()` and `change()`, or between `prereconcile()` and `reconcile()`.
|
|
194
|
+
- **Implement all five methods.** A missing method raises a `TypeError` when the class is created, which returns `8900`.
|
|
195
|
+
- **If you override `__init__`, call `super().__init__()` first.**
|
|
196
|
+
- **Check for `None`.** Empty values are not sent, so these can all be `None`: `port`, any `password`, and `new_password`. A linked account can have a username and no password. `.get()` on `None` raises an `AttributeError`.
|
|
197
|
+
- **`address` is the raw value from the account.** Build URLs yourself, and pick a default for when `port` is `None`.
|
|
198
|
+
- **Change the password last.** Close with `close_success()` straight after the target accepts it, and do nothing that can fail in between.
|
|
199
|
+
- **Close methods raise `SystemExit`.** They are safe inside `try/except Exception`, and code after them does not run, except `finally` blocks. A bare `except:` or `except BaseException:` catches them, and that action then returns `8900`.
|
|
200
|
+
- **Set a timeout on every network call.** The plugin waits for python with no time limit of its own, so keep each action under the platform's `Timeout`.
|
|
201
|
+
- **Use `self.logger`, not `print`.** Standard output is not captured. Standard error is written to the plugin log when an action fails.
|
|
202
|
+
- **Never log the result of `.get()`.** Logging a `Secret` itself is safe and shows `[ENCRYPTED]`. Keep secrets out of exception messages too, as tracebacks reach the plugin log.
|
|
203
|
+
- **Use absolute paths.** The working directory is inherited from the CPM/SRS process, not taken from your script's folder.
|
|
204
|
+
- **Keep TLS verification on in production.** The `simpleauthcredmanager.py` example turns it off.
|
|
205
|
+
|
|
206
|
+
Common failures can be handled like this:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
def verify(self):
|
|
210
|
+
try:
|
|
211
|
+
self._login(self.target_account)
|
|
212
|
+
except Exception as e:
|
|
213
|
+
self.logger.error(f"{type(e).__name__}: {e}")
|
|
214
|
+
self.close_fail()
|
|
215
|
+
self.close_success()
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Use `close_fail()` for what a later attempt could fix, like a timeout or a rejected password. Use `close_fail(unrecoverable=True)` for what needs manual intervention.
|
|
219
|
+
|
|
220
|
+
#### Logs
|
|
221
|
+
|
|
222
|
+
All logs are in `Logs/ThirdParty`, relative to the working directory.
|
|
223
|
+
|
|
224
|
+
| File | Content |
|
|
225
|
+
| --- | --- |
|
|
226
|
+
| `python4cpm.loggerhandler_<level>_<PolicyID>-<SafeName>-<ObjectName>.log` | `self.logger` output. Rotates at 1 MB and keeps one backup. |
|
|
227
|
+
| `<PolicyID>-<SafeName>-<FolderName>-<ObjectName>.Log` | The last error, with its error code. |
|
|
228
|
+
| `Debug_<PolicyID>-<SafeName>-<FolderName>-<ObjectName>_CANetPluginInvoker_<date>.log` | Plugin trace, written with `Debug=Yes` in a manual run, including paths, values and standard error. |
|
|
229
|
+
|
|
230
|
+
The default level is `error`, so `self.logger.info()` and `self.logger.debug()` write nothing until you change `PythonLoggingLevel`. An unknown level falls back to `error`, and `critical` is the quietest. Logging cannot be turned off.
|
|
231
|
+
|
|
232
|
+
#### Troubleshooting
|
|
233
|
+
|
|
234
|
+
| Log message | Cause |
|
|
235
|
+
| --- | --- |
|
|
236
|
+
| `PythonExePath: '...' not found` or `PythonScriptPath: '...' not found` | Wrong path, or the account is still on the imported platform. |
|
|
237
|
+
| `Python closed with exit code: 0` and `No close signal called` | A method ended without a close method. |
|
|
238
|
+
| `Python closed with exit code: 1` | Unhandled exception. The traceback follows in `StdErr Output`. |
|
|
239
|
+
| `Python closed with failed recoverable` | The script called `close_fail()`. |
|
|
240
|
+
| `Python closed with failed unrecoverable` | The script called `close_fail(unrecoverable=True)`. |
|
|
241
|
+
| `can't open file '...\ C:\...'` | A manual `.ini` file has spaces around `=`. |
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
### Installing dependencies in python venv
|
|
245
|
+
|
|
246
|
+
As with any python venv, you can install dependencies in your venv.
|
|
247
|
+
1. If your CPM can connect to the internet:
|
|
248
|
+
- You can use regular pip install commands (e.g., `c:\venv\Scripts\pip.exe install requests`).
|
|
249
|
+
2. If your CPM cannot connect to the internet:
|
|
250
|
+
- You can download packages for an offline install. More info [here](https://pip.pypa.io/en/stable/cli/pip_download/).
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
## Dev Helper:
|
|
254
|
+
|
|
255
|
+
For dev purposes, `DevHelper` is a companion helper to test your scripts without CPM/SRS. It simplifies the instantiation of the `Python4CPM` or `Python4CPMHandler` objects by simulating how the plugin creates the environment context for the python module.
|
|
256
|
+
|
|
257
|
+
**Note**: As CPM and the SRS management agent run in Windows, the plugin was built to encrypt secrets using DPAPI (a windows only library). For dev purposes in Linux/Mac dev workstations, those secrets put in the environment context by `DevHelper` will be in plaintext. In windows dev workstations, `DevHelper` encrypts the secrets as the .NET plugin does. This is informational only, **the module will use its encryption/decryption capabilities automatically based on the platform** it is running on and you do not have to do anything specific to enable it.
|
|
258
|
+
|
|
259
|
+
### Example:
|
|
260
|
+
|
|
261
|
+
#### Set your arguments and secrets:
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
from python4cpm import DevHelper, Python4CPM, Python4CPMHandler
|
|
265
|
+
|
|
266
|
+
DevHelper.set(
|
|
267
|
+
action=Python4CPM.ACTION_CHANGE, # use actions from Python4CPM.ACTION_*
|
|
268
|
+
logging_level="debug",
|
|
269
|
+
target_policy_id="DevHelper", # name of platform
|
|
270
|
+
target_safe_name="Safename", # name of safe
|
|
271
|
+
target_object_name="Objectname", # name of account
|
|
272
|
+
target_username="jdoe",
|
|
273
|
+
target_address="myapp.corp.local",
|
|
274
|
+
target_port="8443",
|
|
275
|
+
logon_username="ldoe",
|
|
276
|
+
reconcile_username="rdoe",
|
|
277
|
+
target_password="", # str value of target password (required by most scripts)
|
|
278
|
+
logon_password="", # str value of logon password (leave out if not linked)
|
|
279
|
+
reconcile_password="", # str value of reconcile password (leave out if not linked)
|
|
280
|
+
target_new_password="" # str value of new password (only for change and reconcile)
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
class CredManager(Python4CPMHandler):
|
|
284
|
+
def verify(self):
|
|
285
|
+
# TODO: Add your logic here
|
|
286
|
+
self.close_success()
|
|
287
|
+
|
|
288
|
+
def logon(self):
|
|
289
|
+
# TODO: Add your logic here
|
|
290
|
+
self.close_success()
|
|
291
|
+
|
|
292
|
+
def change(self):
|
|
293
|
+
# TODO: Add your logic here
|
|
294
|
+
self.close_success()
|
|
295
|
+
|
|
296
|
+
def prereconcile(self):
|
|
297
|
+
# TODO: Add your logic here
|
|
298
|
+
self.close_success()
|
|
299
|
+
|
|
300
|
+
def reconcile(self):
|
|
301
|
+
# TODO: Add your logic here
|
|
302
|
+
self.close_success()
|
|
303
|
+
|
|
304
|
+
CredManager().run()
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
#### Remember when using `DevHelper`:
|
|
308
|
+
|
|
309
|
+
- `target_policy_id`, `target_safe_name` and `target_object_name` are required. The log file name is built from them.
|
|
310
|
+
- A password left out or set to `""` becomes `None`, as in production. Leave out `target_new_password` when testing `verify()`, `logon()` or `prereconcile()`.
|
|
311
|
+
- Each `DevHelper.set()` call replaces the previous one, so anything left out is cleared.
|
|
312
|
+
- Each run tests one `action`, so set it to test each of the five methods.
|
|
313
|
+
- Exit code `10` means success, and `81` or `89` mean the script called `close_fail()`. Anything else returns `8900` in production.
|
|
314
|
+
- **Your script runs for real.** `change()` and `reconcile()` change the password on the target, so use a test account.
|
|
315
|
+
|
|
316
|
+
#### Remember for your final script:
|
|
317
|
+
|
|
318
|
+
- Remove the import of `DevHelper`.
|
|
319
|
+
- Remove the `DevHelper.set()` call.
|
|
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python4cpm"
|
|
7
|
-
version = "1.1.
|
|
7
|
+
version = "1.1.9"
|
|
8
8
|
description = "Python for CPM"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Gonzalo Atienza Rela", email = "gonatienza@gmail.com" }
|
|
11
11
|
]
|
|
12
12
|
dependencies = []
|
|
13
|
-
requires-python = ">=3.
|
|
13
|
+
requires-python = ">=3.11"
|
|
14
14
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
15
15
|
license = "MIT"
|
|
16
16
|
license-files = ["LICENSE"]
|