python4cpm 1.0.20__tar.gz → 1.0.22__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.
@@ -0,0 +1,404 @@
1
+ Metadata-Version: 2.4
2
+ Name: python4cpm
3
+ Version: 1.0.22
4
+ Summary: Python for CPM
5
+ Author-email: Gonzalo Atienza Rela <gonatienza@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Gonzalo Atienza Rela
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in
18
+ all copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ THE SOFTWARE.
27
+
28
+ Requires-Python: >=3.10
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENSE
31
+ Dynamic: license-file
32
+
33
+ # Python4CPM
34
+
35
+ A simple way of using python scripts with CyberArk CPM rotations. 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 a python script.
36
+
37
+ This platform allows you to duplicate it multiple times, simply changing its settings from Privilege Cloud/PVWA to point to different python scripts leveraging the module `python4cpm`.
38
+
39
+ ## Installation
40
+
41
+ ### Preparing Python
42
+
43
+ 1. Install Python in CPM.
44
+ - **Python must be installed for all users**. Follow the custom install steps from the installation wizard to check the checkbox.
45
+ 3. Create a venv in CPM, by running `py -m venv c:\venv`. If desired, use a custom location and adjust any future references.
46
+ 4. Install `python4cpm` in your venv:
47
+ - If your CPM can connect to the internet, install with `c:\venv\Scripts\pip install python4cpm`.
48
+ - If your CPM cannot connect to the internet:
49
+ - Download the latest `python4cpm-*.whl` file from the [pypi project files](https://pypi.org/project/python4cpm/#files).
50
+ - Copy the file to CPM and extract to a temporary directory called `python4cpm-wheel`.
51
+ - From the parent directory of `python4cpm-wheel` run `c:\venv\Scripts\pip install --no-index --find-links=.\python4cpm-wheel python4cpm`.
52
+
53
+
54
+ ### Importing the platform
55
+
56
+ #### If you are using CPM (SaaS or Self-Hosted):
57
+ 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.
58
+ 2. Download the `python4cpm-platform-*.zip` asset from the [release](https://github.com/gonatienza/python4cpm/releases).
59
+ 3. Import the platform zip file into Privilege Cloud/PVWA `(Administration -> Platform Management -> Import platform)`.
60
+ 4. Craft your python script and place it within a folder in CPM (e.g., `C:\python4cpm-scripts`).
61
+ 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).
62
+ 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`).
63
+ 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`).
64
+ 8. If you want to disable logging, update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonLogging -> Value` to `no`.
65
+ 9. If you want to change the logging level to `debug`, update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonLoggingLevel -> Value` to `debug`.
66
+ 10. For new applications repeat steps from 4 to 9.
67
+
68
+ #### If you are using SRS (SaaS only):
69
+ 1. Download the `python4cpm-platform-*.zip` asset from the [release](https://github.com/gonatienza/python4cpm/releases).
70
+ 2. Import the platform zip file into Privilege Cloud `(Administration -> Platform Management -> Import platform)`.
71
+ 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`).
72
+ 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).
73
+ 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`).
74
+ 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`).
75
+ 7. If you want to disable logging, update `Plugin Settings -> Additional Parameters -> PythonLogging` to `no`.
76
+ 8. If you want to change the logging level to `debug`, update `Plugin Settings -> Additional Parameters -> PythonLoggingLevel -> Value` to `debug`.
77
+ 9. For new applications repeat steps from 3 to 8.
78
+
79
+ ## Python Script
80
+
81
+ ### Using the handler (recommended):
82
+
83
+ ```python
84
+ from python4cpm import Python4CPMHandler
85
+
86
+
87
+ class MyApp(Python4CPMHandler): # create a subclass for the Handler
88
+ """
89
+ These are the usable properties and methods from Python4CPMHandler:
90
+
91
+ self.args.action # action requested from CPM/SRS
92
+ self.args.address # address from the account address field
93
+ self.args.username # username from the account username field
94
+ self.args.reconcile_username # reconcile username from the linked reconcile account
95
+ self.args.logon_username # logon username from the linked logon account
96
+ self.args.logging # used to carry the platform logging settings for python
97
+ self.secrets.password.get() # get str from password received from the vault
98
+ self.secrets.new_password.get() # get str from new password in case of a rotation
99
+ self.secrets.logon_password.get() # get str from linked logon account password
100
+ self.secrets.reconcile_password.get() # get str from linked reconcile account password
101
+
102
+ Logging methods -> Will only log if PythonLogging (platform parameters) is set to yes (default is yes)
103
+
104
+ self.log_error("this is an error message") # logs error into Logs/ThirdParty/MyApp.log
105
+ self.log_warning("this is a warning message") # logs warning into Logs/ThirdParty/MyApp.log
106
+ self.log_info("this is an info message") # logs info into Logs/ThirdParty/MyApp.log
107
+
108
+ Logging level -> Will only log debug messages if PythonLoggingLevel (platform parameters) is set to debug (default is info)
109
+
110
+ self.log_debug("this is an debug message") # logs info into Logs/ThirdParty/MyApp.log if logging level is set to debug
111
+
112
+ =============================
113
+ REQUIRED TERMINATION SIGNALS
114
+ =============================
115
+ Terminate signals -> MUST use one of the following three signals to terminate the script:
116
+
117
+ self.close_success() # terminate with success state
118
+ self.close_fail() # terminate with recoverable failed state
119
+ self.close_fail(unrecoverable=True) # terminate with unrecoverable failed state
120
+
121
+ When calling a signal sys.exit is invoked and the script is terminated. If no signal is called, and the script finishes without any exception, it will behave like p4cpm.close_fail(unrecoverable=True) and log an error message.
122
+ =============================
123
+ =============================
124
+ """
125
+
126
+ # =============================
127
+ # REQUIRED METHODS (MUST DEFINE)
128
+ # =============================
129
+ # verify(), logon(), change(), prereconcile(), reconcile()
130
+
131
+ def verify(self):
132
+ self._verify()
133
+ self.log_info("verification successful")
134
+ self.close_success()
135
+
136
+ def logon(self):
137
+ self.close_success() # terminate with success state if nothing needs to be done with a given action.
138
+
139
+ def change(self):
140
+ self._change()
141
+ self.log_error("something went wrong")
142
+ self.close_fail()
143
+
144
+ def prereconcile(self):
145
+ self._verify(from_reconcile=True)
146
+ self.close_success()
147
+
148
+ def reconcile(self):
149
+ self._change(from_reconcile=True)
150
+ self.close_success()
151
+
152
+ def _verify(self, from_reconcile=False):
153
+ if from_reconcile is False:
154
+ pass
155
+ # TODO: use self.args.address, self.args.username, self.secrets.password.get()
156
+ # for your logic in a verification
157
+ else:
158
+ pass
159
+ # TODO: use self.args.address, self.args.reconcile_username, self.secrets.reconcile_password.get()
160
+ # for your logic in a verification
161
+ result = True
162
+ if result is True:
163
+ self.log_info("verification successful") # logs info message into Logs/ThirdParty/MyApp.log
164
+ else:
165
+ self.log_error("something went wrong") # logs error message Logs/ThirdParty/MyApp.log
166
+ self.close_fail()
167
+
168
+ def _change(self, from_reconcile=False):
169
+ if from_reconcile is False:
170
+ pass
171
+ # TODO: use self.args.address, self.args.username, self.secrets.password.get()
172
+ # and self.secrets.new_password.get() for your logic in a rotation
173
+ else:
174
+ pass
175
+ # TODO: use self.args.address, self.args.username, self.args.reconcile_username,
176
+ # self.secrets.reconcile_password.get() and self.secrets.new_password.get() for your logic in a reconciliation
177
+ result = True
178
+ if result is True:
179
+ self.log_info("rotation successful") # logs info message into Logs/ThirdParty/MyApp.log
180
+ else:
181
+ self.log_error("something went wrong") # logs error message Logs/ThirdParty/MyApp.log
182
+ self.close_fail()
183
+
184
+
185
+ if __name__ == "__main__":
186
+ MyApp().run() # initializes the class and calls the action that was requested from CPM/SRS.
187
+ ```
188
+ (*) More realistic examples can be found [here](https://github.com/gonatienza/python4cpm/blob/main/examples/python4cpmhandler).
189
+
190
+ When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
191
+ 1. Verify -> the sciprt will be executed once with the `MyApp.verify()` method.
192
+ 2. Change -> the sciprt will be executed twice, once with the action `MyApp.logon()` method and once as `MyApp.change()` method.
193
+ - If all actions are not terminated with `self.close_success()` and the scripts terminates without any exception, CPM/SRS will see this as a `self.close_fail(unrecoverable=True)`.
194
+ 3. Reconcile -> the sciprt will be executed twice, once with the action `MyApp.prereconcile()` method and once as `MyApp.reconcile()` method.
195
+ - If all actions are not terminated with `self.close_success()` and the scripts terminates without any exception, CPM/SRS will see this as a `self.close_fail(unrecoverable=True)`.
196
+ 4. When calling `MyApp.logon()` or `MyApp.prereconcile()`: `self.secrets.new_password.get()` will always return an empty string.
197
+ 5. If a logon account is not linked, `self.args.logon_username` and `self.secrets.logon_password.get()` will return an empty string.
198
+ 6. If a reconcile account is not linked, `self.args.reconcile_username` and `self.secrets.reconcile_password.get()` will return an empty string.
199
+
200
+
201
+ ### Using Python4CPM properties and methods directly (for low level controls):
202
+
203
+ ```python
204
+ from python4cpm import Python4CPM
205
+
206
+
207
+ p4cpm = Python4CPM("MyApp") # this instantiates the object and grabs all arguments and secrets shared by the .NET SDK
208
+
209
+ # These are the usable properties and related methods from the object:
210
+ p4cpm.args.action # action requested from CPM/SRS
211
+ p4cpm.args.address # address from the account address field
212
+ p4cpm.args.username # username from the account username field
213
+ p4cpm.args.reconcile_username # reconcile username from the linked reconcile account
214
+ p4cpm.args.logon_username # logon username from the linked logon account
215
+ p4cpm.args.logging # used to carry the platform logging settings for python
216
+ p4cpm.secrets.password.get() # get str from password received from the vault
217
+ p4cpm.secrets.new_password.get() # get str from new password in case of a rotation
218
+ p4cpm.secrets.logon_password.get() # get str from linked logon account password
219
+ p4cpm.secrets.reconcile_password.get() # get str from linked reconcile account password
220
+
221
+ # Logging methods -> Will only log if PythonLogging (platform parameters) is set to yes (default is yes)
222
+ p4cpm.log_error("this is an error message") # logs error into Logs/ThirdParty/MyApp.log
223
+ p4cpm.log_warning("this is a warning message") # logs warning into Logs/ThirdParty/MyApp.log
224
+ p4cpm.log_info("this is an info message") # logs info into Logs/ThirdParty/MyApp.log
225
+ # Logging level -> Will only log debug messages if PythonLoggingLevel (platform parameters) is set to debug (default is info)
226
+ p4cpm.log_debug("this is an debug message") # logs info into Logs/ThirdParty/MyApp.log if logging level is set to debug
227
+
228
+ # Terminate signals -> MUST use one of the following three signals to terminate the script:
229
+ ## p4cpm.close_success() # terminate with success state
230
+ ## p4cpm.close_fail() # terminate with recoverable failed state
231
+ ## p4cpm.close_fail(unrecoverable=True) # terminate with unrecoverable failed state
232
+ # When calling a signal sys.exit is invoked and the script is terminated. If no signal is called, and the script finishes without any exception, it will behave like p4cpm.close_fail(unrecoverable=True) and log an error message.
233
+
234
+
235
+ # Verification example -> verify the username and password are valid
236
+ def verify(from_reconcile=False):
237
+ if from_reconcile is False:
238
+ pass
239
+ # TODO: use p4cpm.args.address, p4cpm.args.username, p4cpm.secrets.password.get()
240
+ # for your logic in a verification
241
+ else:
242
+ pass
243
+ # TODO: use p4cpm.args.address, p4cpm.args.reconcile_username, p4cpm.secrets.reconcile_password.get()
244
+ # for your logic in a verification
245
+ result = True
246
+ if result is True:
247
+ p4cpm.log_info("verification successful")
248
+ else:
249
+ p4cpm.log_error("something went wrong")
250
+ raise Exception("verify failed") # raise to trigger failed termination signal
251
+
252
+
253
+ # Rotation example -> rotate the password of the account
254
+ def change(from_reconcile=False):
255
+ if from_reconcile is False:
256
+ pass
257
+ # TODO: use p4cpm.args.address, p4cpm.args.username, p4cpm.secrets.password.get()
258
+ # and p4cpm.secrets.new_password.get() for your logic in a rotation
259
+ else:
260
+ pass
261
+ # TODO: use p4cpm.args.address, p4cpm.args.username, p4cpm.args.reconcile_username,
262
+ # p4cpm.secrets.reconcile_password.get() and p4cpm.secrets.new_password.get() for your logic in a reconciliation
263
+ result = True
264
+ if result is True:
265
+ p4cpm.log_info("rotation successful")
266
+ else:
267
+ p4cpm.log_error("something went wrong")
268
+ raise Exception("change failed") # raise to trigger failed termination signal
269
+
270
+
271
+ if __name__ == "__main__":
272
+ try:
273
+ if p4cpm.args.action == Python4CPM.ACTION_VERIFY: # class attribute ACTION_VERIFY holds the verify action value
274
+ verify()
275
+ p4cpm.close_success()
276
+ elif p4cpm.args.action == Python4CPM.ACTION_LOGON: # class attribute ACTION_LOGON holds the logon action value
277
+ p4cpm.close_success() # terminate with success state if nothing needs to be done with a given action.
278
+ elif p4cpm.args.action == Python4CPM.ACTION_CHANGE: # class attribute ACTION_CHANGE holds the password change action value
279
+ change()
280
+ p4cpm.close_success()
281
+ elif p4cpm.args.action == Python4CPM.ACTION_PRERECONCILE: # class attribute ACTION_PRERECONCILE holds the pre-reconcile action value
282
+ verify(from_reconcile=True)
283
+ p4cpm.close_success()
284
+ # Alternatively ->
285
+ ## p4cpm.log_error("reconciliation is not supported") # let the logs know that reconciliation is not supported
286
+ ## p4cpm.close_fail() # let CPM/SRS know to check the logs
287
+ elif p4cpm.args.action == Python4CPM.ACTION_RECONCILE: # class attribute ACTION_RECONCILE holds the reconcile action value
288
+ change(from_reconcile=True)
289
+ p4cpm.close_success()
290
+ # Alternatively ->
291
+ ## p4cpm.log_error("reconciliation is not supported") # let the logs know that reconciliation is not supported
292
+ ## p4cpm.close_fail() # let CPM/SRS know to check the logs
293
+ except Exception as e:
294
+ p4cpm.log_error(f"{type(e).__name__}: {e}")
295
+ raise e # CPM/SRS will see any Exception as a p4cpm.close_fail(unrecoverable=True)
296
+ ```
297
+ (*) More realistic examples can be found [here](https://github.com/gonatienza/python4cpm/blob/main/examples/python4cpm).
298
+
299
+ When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
300
+ 1. Verify -> the sciprt will be executed once with the `p4cpm.args.action` as `Python4CPM.ACTION_VERIFY`.
301
+ 2. Change -> the sciprt will be executed twice, once with the action `p4cpm.args.action` as `Python4CPM.ACTION_LOGON` and once as `Python4CPM.ACTION_CHANGE`.
302
+ - If all actions are not terminated with `p4cpm.close_success()` and the scripts terminates without any exception, CPM/SRS will see this as a `p4cpm.close_fail(unrecoverable=True)`.
303
+ 3. Reconcile -> the sciprt will be executed twice, once with the `p4cpm.args.action` as `Python4CPM.ACTION_PRERECONCILE` and once as `Python4CPM.ACTION_RECONCILE`.
304
+ - If all actions are not terminated with `p4cpm.close_success()` and the scripts terminates without any exception, CPM/SRS will see this as a `p4cpm.close_fail(unrecoverable=True)`.
305
+ 4. When `p4cpm.args.action` comes as `Python4CPM.ACTION_VERIFY`, `Python4CPM.ACTION_LOGON` or `Python4CPM.ACTION_PRERECONCILE`: `p4cpm.secrets.new_password.get()` will always return an empty string.
306
+ 5. If a logon account is not linked, `p4cpm.args.logon_username` and `p4cpm.secrets.logon_password.get()` will return an empty string.
307
+ 6. If a reconcile account is not linked, `p4cpm.args.reconcile_username` and `p4cpm.secrets.reconcile_password.get()` will return an empty string.
308
+
309
+
310
+ ### Installing dependencies in python venv
311
+
312
+ As with any python venv, you can install dependencies in your venv.
313
+ 1. If your CPM can connect to the internet:
314
+ - You can use regular pip install commands (e.g., `c:\venv\Scripts\pip.exe install requests`).
315
+ 2. If your CPM cannot connect to the internet:
316
+ - You can download packages for an offline install. More info [here](https://pip.pypa.io/en/stable/cli/pip_download/).
317
+
318
+
319
+ ## Dev Helper:
320
+
321
+ For dev purposes, `NETHelper` is a companion helper that simplifies the instantiation of the `Python4CPM` or `Python4CPMHandler` objects by simulating how the plugin passes arguments and secrets to the modules.
322
+ Install this module (in a dev workstation) with:
323
+
324
+ ```bash
325
+ pip install python4cpm
326
+ ```
327
+
328
+ **Note**: As CPM runs in Windows, the plugin was built to pass secrets securely to the `Python4CPM.crypto` module using the Data Protection API (DPAPI). For dev purposes in Linux/Mac dev workstations, those secrets will appear as plaintext in the environment of the process. This is informational only, the module will use its encryption/decryption capabilities automatically in Windows and you do not have to do anything specific to enable it.
329
+
330
+ ### Example:
331
+
332
+ ```python
333
+ from python4cpm import NETHelper, Python4CPM, Python4CPMHandler
334
+ from getpass import getpass
335
+
336
+ # Get secrets for your password, logon account password, reconcile account password and new password
337
+ # You can use an empty string if it does not apply
338
+ password = getpass("password: ") # password from account
339
+ logon_password = getpass("logon_password: ") # password from linked logon account
340
+ reconcile_password = getpass("reconcile_password: ") # password from linked reconcile account
341
+ new_password = getpass("new_password: ") # new password for the rotation
342
+ ```
343
+
344
+ #### Set your arguments and secrets:
345
+
346
+ ```python
347
+ NETHelper.set(
348
+ action=Python4CPM.ACTION_LOGON, # use actions from Python4CPM.ACTION_*
349
+ address="myapp.corp.local", # populate with the address from your account properties
350
+ username="jdoe", # populate with the username from your account properties
351
+ logon_username="ldoe", # populate with the logon account username from your linked logon account
352
+ reconcile_username="rdoe", # ppopulate with the reconcile account username from your linked logon account
353
+ logging="yes", # populate with the PythonLogging parameter from the platform: "yes" or "no"
354
+ logging_level="info", # populate with the PythonLoggingLevel parameter from the platform: "info" or "debug"
355
+ password=password,
356
+ logon_password=logon_password,
357
+ reconcile_password=reconcile_password,
358
+ new_password=new_password
359
+ )
360
+ ```
361
+
362
+ #### Using the handler (recommended):
363
+
364
+ ```python
365
+ class MyApp(Python4CPMHandler):
366
+ def verify(self):
367
+ # TODO: Add your logic here
368
+ self.close_success()
369
+
370
+ def logon(self):
371
+ # TODO: Add your logic here
372
+ self.close_success()
373
+
374
+ def change(self):
375
+ # TODO: Add your logic here
376
+ self.close_success()
377
+
378
+ def prereconcile(self):
379
+ # TODO: Add your logic here
380
+ self.close_success()
381
+
382
+ def reconcile(self):
383
+ # TODO: Add your logic here
384
+ self.close_success()
385
+
386
+ MyApp().run()
387
+ ```
388
+
389
+ #### Using Python4CPM properties and methods directly:
390
+
391
+ ```python
392
+ p4cpm = NETHelper.get()
393
+
394
+ # TODO: use the p4cpm object during dev to build your script logic
395
+ assert password == p4cpm.secrets.password.get()
396
+ p4cpm.log_info("success!")
397
+ p4cpm.close_success()
398
+ ```
399
+
400
+ #### Remember for your final script:
401
+
402
+ - Change the definition of `p4cpm` from `p4cpm = NETHelper.get()` to `p4cpm = Python4CPM("MyApp")` if you are using the properties and methods directly.
403
+ - Remove any secrets prompting or interactive interruptions.
404
+ - Remove the import of `NETHelper`.