python4cpm 1.0.21__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python4cpm
3
- Version: 1.0.21
3
+ Version: 1.0.22
4
4
  Summary: Python for CPM
5
5
  Author-email: Gonzalo Atienza Rela <gonatienza@gmail.com>
6
6
  License: MIT License
@@ -32,7 +32,7 @@ Dynamic: license-file
32
32
 
33
33
  # Python4CPM
34
34
 
35
- A simple way of using python scripts with CyberArk CPM rotations. This module levereages 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.
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
36
 
37
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
38
 
@@ -78,7 +78,127 @@ This platform allows you to duplicate it multiple times, simply changing its set
78
78
 
79
79
  ## Python Script
80
80
 
81
- ### Example:
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):
82
202
 
83
203
  ```python
84
204
  from python4cpm import Python4CPM
@@ -87,7 +207,7 @@ from python4cpm import Python4CPM
87
207
  p4cpm = Python4CPM("MyApp") # this instantiates the object and grabs all arguments and secrets shared by the .NET SDK
88
208
 
89
209
  # These are the usable properties and related methods from the object:
90
- p4cpm.args.action # action requested from CPM
210
+ p4cpm.args.action # action requested from CPM/SRS
91
211
  p4cpm.args.address # address from the account address field
92
212
  p4cpm.args.username # username from the account username field
93
213
  p4cpm.args.reconcile_username # reconcile username from the linked reconcile account
@@ -98,36 +218,35 @@ p4cpm.secrets.new_password.get() # get str from new password in case of a rotati
98
218
  p4cpm.secrets.logon_password.get() # get str from linked logon account password
99
219
  p4cpm.secrets.reconcile_password.get() # get str from linked reconcile account password
100
220
 
101
- # Logging methods -> Will only log if Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonLogging is set to yes (default is yes)
102
- p4cpm.log_error("this is an error message") # logs error into Logs/ThirdParty/Python4CPM/MyApp.log
103
- p4cpm.log_warning("this is a warning message") # logs warning into Logs/ThirdParty/Python4CPM/MyApp.log
104
- p4cpm.log_info("this is an info message") # logs info into Logs/ThirdParty/Python4CPM/MyApp.log
105
- # Logging level -> Will only log debug messages if Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonLoggingLevel is set to debug (default is info)
106
- p4cpm.log_debug("this is an debug message") # logs info into Logs/ThirdParty/Python4CPM/MyApp.log if logging level is set to debug
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
107
227
 
108
228
  # Terminate signals -> MUST use one of the following three signals to terminate the script:
109
229
  ## p4cpm.close_success() # terminate with success state
110
230
  ## p4cpm.close_fail() # terminate with recoverable failed state
111
231
  ## p4cpm.close_fail(unrecoverable=True) # terminate with unrecoverable failed state
112
- # When calling a signal sys.exit is invoked and the script is terminated.
113
- # 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.
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.
114
233
 
115
234
 
116
235
  # Verification example -> verify the username and password are valid
117
236
  def verify(from_reconcile=False):
118
237
  if from_reconcile is False:
119
238
  pass
120
- # Use p4cpm.args.address, p4cpm.args.username, p4cpm.secrets.password.get()
239
+ # TODO: use p4cpm.args.address, p4cpm.args.username, p4cpm.secrets.password.get()
121
240
  # for your logic in a verification
122
241
  else:
123
242
  pass
124
- # Use p4cpm.args.address, p4cpm.args.reconcile_username, p4cpm.secrets.reconcile_password.get()
243
+ # TODO: use p4cpm.args.address, p4cpm.args.reconcile_username, p4cpm.secrets.reconcile_password.get()
125
244
  # for your logic in a verification
126
245
  result = True
127
246
  if result is True:
128
- p4cpm.log_info("verification successful") # logs info message into Logs/ThirdParty/Python4CPM/MyApp.log
247
+ p4cpm.log_info("verification successful")
129
248
  else:
130
- p4cpm.log_error("something went wrong") # logs error message Logs/ThirdParty/Python4CPM/MyApp.log
249
+ p4cpm.log_error("something went wrong")
131
250
  raise Exception("verify failed") # raise to trigger failed termination signal
132
251
 
133
252
 
@@ -135,17 +254,17 @@ def verify(from_reconcile=False):
135
254
  def change(from_reconcile=False):
136
255
  if from_reconcile is False:
137
256
  pass
138
- # Use p4cpm.args.address, p4cpm.args.username, p4cpm.secrets.password.get()
257
+ # TODO: use p4cpm.args.address, p4cpm.args.username, p4cpm.secrets.password.get()
139
258
  # and p4cpm.secrets.new_password.get() for your logic in a rotation
140
259
  else:
141
260
  pass
142
- # Use p4cpm.args.address, p4cpm.args.username, p4cpm.args.reconcile_username,
261
+ # TODO: use p4cpm.args.address, p4cpm.args.username, p4cpm.args.reconcile_username,
143
262
  # p4cpm.secrets.reconcile_password.get() and p4cpm.secrets.new_password.get() for your logic in a reconciliation
144
263
  result = True
145
264
  if result is True:
146
- p4cpm.log_info("rotation successful") # logs info message into Logs/ThirdParty/Python4CPM/MyApp.log
265
+ p4cpm.log_info("rotation successful")
147
266
  else:
148
- p4cpm.log_error("something went wrong") # logs error message Logs/ThirdParty/Python4CPM/MyApp.log
267
+ p4cpm.log_error("something went wrong")
149
268
  raise Exception("change failed") # raise to trigger failed termination signal
150
269
 
151
270
 
@@ -153,48 +272,44 @@ if __name__ == "__main__":
153
272
  try:
154
273
  if p4cpm.args.action == Python4CPM.ACTION_VERIFY: # class attribute ACTION_VERIFY holds the verify action value
155
274
  verify()
156
- p4cpm.close_success() # terminate with success state
275
+ p4cpm.close_success()
157
276
  elif p4cpm.args.action == Python4CPM.ACTION_LOGON: # class attribute ACTION_LOGON holds the logon action value
158
- verify()
159
- p4cpm.close_success() # terminate with success state
277
+ p4cpm.close_success() # terminate with success state if nothing needs to be done with a given action.
160
278
  elif p4cpm.args.action == Python4CPM.ACTION_CHANGE: # class attribute ACTION_CHANGE holds the password change action value
161
279
  change()
162
- p4cpm.close_success() # terminate with success state
280
+ p4cpm.close_success()
163
281
  elif p4cpm.args.action == Python4CPM.ACTION_PRERECONCILE: # class attribute ACTION_PRERECONCILE holds the pre-reconcile action value
164
282
  verify(from_reconcile=True)
165
- p4cpm.close_success() # terminate with success state
283
+ p4cpm.close_success()
166
284
  # Alternatively ->
167
285
  ## p4cpm.log_error("reconciliation is not supported") # let the logs know that reconciliation is not supported
168
- ## p4cpm.close_fail() # let CPM know to check the logs
286
+ ## p4cpm.close_fail() # let CPM/SRS know to check the logs
169
287
  elif p4cpm.args.action == Python4CPM.ACTION_RECONCILE: # class attribute ACTION_RECONCILE holds the reconcile action value
170
288
  change(from_reconcile=True)
171
- p4cpm.close_success() # terminate with success state
289
+ p4cpm.close_success()
172
290
  # Alternatively ->
173
291
  ## p4cpm.log_error("reconciliation is not supported") # let the logs know that reconciliation is not supported
174
- ## p4cpm.close_fail() # let CPM know to check the logs
175
- else:
176
- p4cpm.log_error(f"invalid action: '{p4cpm.args.action}'") # logs into Logs/ThirdParty/Python4CPM/MyApp.log
177
- p4cpm.close_fail(unrecoverable=True) # terminate with unrecoverable failed state
292
+ ## p4cpm.close_fail() # let CPM/SRS know to check the logs
178
293
  except Exception as e:
179
294
  p4cpm.log_error(f"{type(e).__name__}: {e}")
180
- p4cpm.close_fail()
295
+ raise e # CPM/SRS will see any Exception as a p4cpm.close_fail(unrecoverable=True)
181
296
  ```
182
- (*) More realistic examples can be found [here](https://github.com/gonatienza/python4cpm/blob/main/examples).
297
+ (*) More realistic examples can be found [here](https://github.com/gonatienza/python4cpm/blob/main/examples/python4cpm).
183
298
 
184
299
  When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
185
300
  1. Verify -> the sciprt will be executed once with the `p4cpm.args.action` as `Python4CPM.ACTION_VERIFY`.
186
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`.
187
- - If all actions are not terminated with `p4cpm.close_success()` and the scripts terminates without any exception, it defaults to a successful return.
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)`.
188
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`.
189
- - If all actions are not terminated with `p4cpm.close_success()` the overall reconcile will fail.
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)`.
190
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.
191
306
  5. If a logon account is not linked, `p4cpm.args.logon_username` and `p4cpm.secrets.logon_password.get()` will return an empty string.
192
307
  6. If a reconcile account is not linked, `p4cpm.args.reconcile_username` and `p4cpm.secrets.reconcile_password.get()` will return an empty string.
193
308
 
194
309
 
195
- ### Installing dependancies in python venv
310
+ ### Installing dependencies in python venv
196
311
 
197
- As with any python venv, you can install dependancies in your venv.
312
+ As with any python venv, you can install dependencies in your venv.
198
313
  1. If your CPM can connect to the internet:
199
314
  - You can use regular pip install commands (e.g., `c:\venv\Scripts\pip.exe install requests`).
200
315
  2. If your CPM cannot connect to the internet:
@@ -203,7 +318,7 @@ As with any python venv, you can install dependancies in your venv.
203
318
 
204
319
  ## Dev Helper:
205
320
 
206
- For dev purposes, `NETHelper` is a companion helper that simplifies the instantiation of the `Python4CPM` object by simulating how the plugin passes arguments and secrets to `Python4CPM`.
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.
207
322
  Install this module (in a dev workstation) with:
208
323
 
209
324
  ```bash
@@ -215,7 +330,7 @@ pip install python4cpm
215
330
  ### Example:
216
331
 
217
332
  ```python
218
- from python4cpm import NETHelper, Python4CPM
333
+ from python4cpm import NETHelper, Python4CPM, Python4CPMHandler
219
334
  from getpass import getpass
220
335
 
221
336
  # Get secrets for your password, logon account password, reconcile account password and new password
@@ -224,8 +339,12 @@ password = getpass("password: ") # password from account
224
339
  logon_password = getpass("logon_password: ") # password from linked logon account
225
340
  reconcile_password = getpass("reconcile_password: ") # password from linked reconcile account
226
341
  new_password = getpass("new_password: ") # new password for the rotation
342
+ ```
227
343
 
228
- p4cpm = NETHelper.run(
344
+ #### Set your arguments and secrets:
345
+
346
+ ```python
347
+ NETHelper.set(
229
348
  action=Python4CPM.ACTION_LOGON, # use actions from Python4CPM.ACTION_*
230
349
  address="myapp.corp.local", # populate with the address from your account properties
231
350
  username="jdoe", # populate with the username from your account properties
@@ -238,19 +357,48 @@ p4cpm = NETHelper.run(
238
357
  reconcile_password=reconcile_password,
239
358
  new_password=new_password
240
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()
241
381
 
242
- # Use the p4cpm object during dev to build your script logic
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
243
395
  assert password == p4cpm.secrets.password.get()
244
396
  p4cpm.log_info("success!")
245
397
  p4cpm.close_success()
246
-
247
- # Remember for your final script:
248
- ## changing the definition of p4cpm from NETHelper.run() to Python4CPM("MyApp")
249
- ## remove any secrets prompting
250
- ## remove the NETHelper import
251
398
  ```
252
399
 
253
- Remember for your final script:
254
- - Change the definition of `p4cpm` from `p4cpm = NETHelper.run(**kwargs)` to `p4cpm = Python4CPM("MyApp")`.
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.
255
403
  - Remove any secrets prompting or interactive interruptions.
256
404
  - Remove the import of `NETHelper`.