python4cpm 1.0.22__tar.gz → 1.0.23__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.0.22/src/python4cpm.egg-info → python4cpm-1.0.23}/PKG-INFO +35 -57
- {python4cpm-1.0.22 → python4cpm-1.0.23}/README.md +33 -34
- {python4cpm-1.0.22 → python4cpm-1.0.23}/pyproject.toml +3 -2
- python4cpm-1.0.23/src/python4cpm/logger.py +44 -0
- {python4cpm-1.0.22 → python4cpm-1.0.23}/src/python4cpm/python4cpm.py +22 -26
- {python4cpm-1.0.22 → python4cpm-1.0.23/src/python4cpm.egg-info}/PKG-INFO +35 -57
- python4cpm-1.0.22/src/python4cpm/logger.py +0 -40
- {python4cpm-1.0.22 → python4cpm-1.0.23}/LICENSE +0 -0
- {python4cpm-1.0.22 → python4cpm-1.0.23}/setup.cfg +0 -0
- {python4cpm-1.0.22 → python4cpm-1.0.23}/src/python4cpm/__init__.py +0 -0
- {python4cpm-1.0.22 → python4cpm-1.0.23}/src/python4cpm/args.py +0 -0
- {python4cpm-1.0.22 → python4cpm-1.0.23}/src/python4cpm/crypto.py +0 -0
- {python4cpm-1.0.22 → python4cpm-1.0.23}/src/python4cpm/nethelper.py +0 -0
- {python4cpm-1.0.22 → python4cpm-1.0.23}/src/python4cpm/python4cpmhandler.py +0 -0
- {python4cpm-1.0.22 → python4cpm-1.0.23}/src/python4cpm/secrets.py +0 -0
- {python4cpm-1.0.22 → python4cpm-1.0.23}/src/python4cpm.egg-info/SOURCES.txt +0 -0
- {python4cpm-1.0.22 → python4cpm-1.0.23}/src/python4cpm.egg-info/dependency_links.txt +0 -0
- {python4cpm-1.0.22 → python4cpm-1.0.23}/src/python4cpm.egg-info/top_level.txt +0 -0
|
@@ -1,30 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python4cpm
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.23
|
|
4
4
|
Summary: Python for CPM
|
|
5
5
|
Author-email: Gonzalo Atienza Rela <gonatienza@gmail.com>
|
|
6
|
-
License: MIT
|
|
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
|
-
|
|
6
|
+
License-Expression: MIT
|
|
28
7
|
Requires-Python: >=3.10
|
|
29
8
|
Description-Content-Type: text/markdown
|
|
30
9
|
License-File: LICENSE
|
|
@@ -32,7 +11,7 @@ Dynamic: license-file
|
|
|
32
11
|
|
|
33
12
|
# Python4CPM
|
|
34
13
|
|
|
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.
|
|
14
|
+
A simple way of using python scripts with CyberArk CPM/SRS 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
15
|
|
|
37
16
|
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
17
|
|
|
@@ -40,14 +19,14 @@ This platform allows you to duplicate it multiple times, simply changing its set
|
|
|
40
19
|
|
|
41
20
|
### Preparing Python
|
|
42
21
|
|
|
43
|
-
1. Install Python
|
|
22
|
+
1. Install Python along CPM or the SRS Connector Management Agent.
|
|
44
23
|
- **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
|
|
24
|
+
3. Create a venv in the server, by running `py -m venv c:\venv`. If desired, use a custom location and adjust any future references.
|
|
46
25
|
4. Install `python4cpm` in your venv:
|
|
47
26
|
- If your CPM can connect to the internet, install with `c:\venv\Scripts\pip install python4cpm`.
|
|
48
27
|
- If your CPM cannot connect to the internet:
|
|
49
28
|
- Download the latest `python4cpm-*.whl` file from the [pypi project files](https://pypi.org/project/python4cpm/#files).
|
|
50
|
-
- Copy the file to
|
|
29
|
+
- Copy the file to the server into a temporary directory called `python4cpm-wheel`.
|
|
51
30
|
- From the parent directory of `python4cpm-wheel` run `c:\venv\Scripts\pip install --no-index --find-links=.\python4cpm-wheel python4cpm`.
|
|
52
31
|
|
|
53
32
|
|
|
@@ -84,7 +63,7 @@ This platform allows you to duplicate it multiple times, simply changing its set
|
|
|
84
63
|
from python4cpm import Python4CPMHandler
|
|
85
64
|
|
|
86
65
|
|
|
87
|
-
class
|
|
66
|
+
class MyRotator(Python4CPMHandler): # create a subclass for the Handler
|
|
88
67
|
"""
|
|
89
68
|
These are the usable properties and methods from Python4CPMHandler:
|
|
90
69
|
|
|
@@ -101,13 +80,13 @@ class MyApp(Python4CPMHandler): # create a subclass for the Handler
|
|
|
101
80
|
|
|
102
81
|
Logging methods -> Will only log if PythonLogging (platform parameters) is set to yes (default is yes)
|
|
103
82
|
|
|
104
|
-
self.log_error("this is an error message") # logs error into Logs/ThirdParty/
|
|
105
|
-
self.log_warning("this is a warning message") # logs warning into Logs/ThirdParty/
|
|
106
|
-
self.log_info("this is an info message") # logs info into Logs/ThirdParty/
|
|
83
|
+
self.log_error("this is an error message") # logs error into Logs/ThirdParty/MyRotator.log
|
|
84
|
+
self.log_warning("this is a warning message") # logs warning into Logs/ThirdParty/MyRotator.log
|
|
85
|
+
self.log_info("this is an info message") # logs info into Logs/ThirdParty/MyRotator.log
|
|
107
86
|
|
|
108
87
|
Logging level -> Will only log debug messages if PythonLoggingLevel (platform parameters) is set to debug (default is info)
|
|
109
88
|
|
|
110
|
-
self.log_debug("this is an debug message") # logs info into Logs/ThirdParty/
|
|
89
|
+
self.log_debug("this is an debug message") # logs info into Logs/ThirdParty/MyRotator.log if logging level is set to debug
|
|
111
90
|
|
|
112
91
|
=============================
|
|
113
92
|
REQUIRED TERMINATION SIGNALS
|
|
@@ -160,9 +139,9 @@ class MyApp(Python4CPMHandler): # create a subclass for the Handler
|
|
|
160
139
|
# for your logic in a verification
|
|
161
140
|
result = True
|
|
162
141
|
if result is True:
|
|
163
|
-
self.log_info("verification successful") # logs info message into Logs/ThirdParty/
|
|
142
|
+
self.log_info("verification successful") # logs info message into Logs/ThirdParty/MyRotator.log
|
|
164
143
|
else:
|
|
165
|
-
self.log_error("something went wrong") # logs error message Logs/ThirdParty/
|
|
144
|
+
self.log_error("something went wrong") # logs error message Logs/ThirdParty/MyRotator.log
|
|
166
145
|
self.close_fail()
|
|
167
146
|
|
|
168
147
|
def _change(self, from_reconcile=False):
|
|
@@ -176,26 +155,26 @@ class MyApp(Python4CPMHandler): # create a subclass for the Handler
|
|
|
176
155
|
# self.secrets.reconcile_password.get() and self.secrets.new_password.get() for your logic in a reconciliation
|
|
177
156
|
result = True
|
|
178
157
|
if result is True:
|
|
179
|
-
self.log_info("rotation successful") # logs info message into Logs/ThirdParty/
|
|
158
|
+
self.log_info("rotation successful") # logs info message into Logs/ThirdParty/MyRotator.log
|
|
180
159
|
else:
|
|
181
|
-
self.log_error("something went wrong") # logs error message Logs/ThirdParty/
|
|
160
|
+
self.log_error("something went wrong") # logs error message Logs/ThirdParty/MyRotator.log
|
|
182
161
|
self.close_fail()
|
|
183
162
|
|
|
184
163
|
|
|
185
164
|
if __name__ == "__main__":
|
|
186
|
-
|
|
165
|
+
MyRotator().run() # initializes the class and calls the action that was requested from CPM/SRS.
|
|
187
166
|
```
|
|
188
167
|
(*) More realistic examples can be found [here](https://github.com/gonatienza/python4cpm/blob/main/examples/python4cpmhandler).
|
|
189
168
|
|
|
190
169
|
When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
|
|
191
|
-
1. Verify -> the sciprt will be executed once
|
|
192
|
-
2. Change -> the sciprt will be executed twice,
|
|
193
|
-
- If
|
|
194
|
-
3. Reconcile -> the sciprt will be executed twice,
|
|
195
|
-
- If
|
|
196
|
-
4. When calling `
|
|
197
|
-
5. If a logon account is not linked, `self.args.logon_username` and `self.secrets.logon_password.get()` will return
|
|
198
|
-
6. If a reconcile account is not linked, `self.args.reconcile_username` and `self.secrets.reconcile_password.get()` will return
|
|
170
|
+
1. Verify -> the sciprt will be executed once running the `MyRotator.verify()` method.
|
|
171
|
+
2. Change -> the sciprt will be executed twice, running first the `MyRotator.logon()` method and secondly the `MyRotator.change()` method.
|
|
172
|
+
- If both 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)`.
|
|
173
|
+
3. Reconcile -> the sciprt will be executed twice, running first the `MyRotator.prereconcile()` method and secondly the `MyRotator.reconcile()` method.
|
|
174
|
+
- If both 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)`.
|
|
175
|
+
4. When calling `MyRotator.verify()`, `MyRotator.logon()` or `MyRotator.prereconcile()`: `self.secrets.new_password.get()` will always return an empty string.
|
|
176
|
+
5. If a logon account is not linked, `self.args.logon_username` and `self.secrets.logon_password.get()` will return empty strings.
|
|
177
|
+
6. If a reconcile account is not linked, `self.args.reconcile_username` and `self.secrets.reconcile_password.get()` will return empty strings.
|
|
199
178
|
|
|
200
179
|
|
|
201
180
|
### Using Python4CPM properties and methods directly (for low level controls):
|
|
@@ -299,12 +278,12 @@ if __name__ == "__main__":
|
|
|
299
278
|
When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
|
|
300
279
|
1. Verify -> the sciprt will be executed once with the `p4cpm.args.action` as `Python4CPM.ACTION_VERIFY`.
|
|
301
280
|
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
|
|
281
|
+
- If both 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
282
|
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
|
|
283
|
+
- If both 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
284
|
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
|
|
307
|
-
6. If a reconcile account is not linked, `p4cpm.args.reconcile_username` and `p4cpm.secrets.reconcile_password.get()` will return
|
|
285
|
+
5. If a logon account is not linked, `p4cpm.args.logon_username` and `p4cpm.secrets.logon_password.get()` will return empty strings.
|
|
286
|
+
6. If a reconcile account is not linked, `p4cpm.args.reconcile_username` and `p4cpm.secrets.reconcile_password.get()` will return empty strings.
|
|
308
287
|
|
|
309
288
|
|
|
310
289
|
### Installing dependencies in python venv
|
|
@@ -329,6 +308,8 @@ pip install python4cpm
|
|
|
329
308
|
|
|
330
309
|
### Example:
|
|
331
310
|
|
|
311
|
+
#### Set your arguments and secrets:
|
|
312
|
+
|
|
332
313
|
```python
|
|
333
314
|
from python4cpm import NETHelper, Python4CPM, Python4CPMHandler
|
|
334
315
|
from getpass import getpass
|
|
@@ -339,11 +320,7 @@ password = getpass("password: ") # password from account
|
|
|
339
320
|
logon_password = getpass("logon_password: ") # password from linked logon account
|
|
340
321
|
reconcile_password = getpass("reconcile_password: ") # password from linked reconcile account
|
|
341
322
|
new_password = getpass("new_password: ") # new password for the rotation
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
#### Set your arguments and secrets:
|
|
345
323
|
|
|
346
|
-
```python
|
|
347
324
|
NETHelper.set(
|
|
348
325
|
action=Python4CPM.ACTION_LOGON, # use actions from Python4CPM.ACTION_*
|
|
349
326
|
address="myapp.corp.local", # populate with the address from your account properties
|
|
@@ -362,7 +339,7 @@ NETHelper.set(
|
|
|
362
339
|
#### Using the handler (recommended):
|
|
363
340
|
|
|
364
341
|
```python
|
|
365
|
-
class
|
|
342
|
+
class MyRotator(Python4CPMHandler):
|
|
366
343
|
def verify(self):
|
|
367
344
|
# TODO: Add your logic here
|
|
368
345
|
self.close_success()
|
|
@@ -383,7 +360,7 @@ class MyApp(Python4CPMHandler):
|
|
|
383
360
|
# TODO: Add your logic here
|
|
384
361
|
self.close_success()
|
|
385
362
|
|
|
386
|
-
|
|
363
|
+
MyRotator().run()
|
|
387
364
|
```
|
|
388
365
|
|
|
389
366
|
#### Using Python4CPM properties and methods directly:
|
|
@@ -399,6 +376,7 @@ p4cpm.close_success()
|
|
|
399
376
|
|
|
400
377
|
#### Remember for your final script:
|
|
401
378
|
|
|
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
379
|
- Remove the import of `NETHelper`.
|
|
380
|
+
- Remove the `NETHelper.set()` call.
|
|
381
|
+
- If applicable, change the definition of `p4cpm` from `p4cpm = NETHelper.get()` to `p4cpm = Python4CPM("MyApp")`.
|
|
382
|
+
- Remove any secrets prompting or interactive interruptions.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Python4CPM
|
|
2
2
|
|
|
3
|
-
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.
|
|
3
|
+
A simple way of using python scripts with CyberArk CPM/SRS 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.
|
|
4
4
|
|
|
5
5
|
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`.
|
|
6
6
|
|
|
@@ -8,14 +8,14 @@ This platform allows you to duplicate it multiple times, simply changing its set
|
|
|
8
8
|
|
|
9
9
|
### Preparing Python
|
|
10
10
|
|
|
11
|
-
1. Install Python
|
|
11
|
+
1. Install Python along CPM or the SRS Connector Management Agent.
|
|
12
12
|
- **Python must be installed for all users**. Follow the custom install steps from the installation wizard to check the checkbox.
|
|
13
|
-
3. Create a venv in
|
|
13
|
+
3. Create a venv in the server, by running `py -m venv c:\venv`. If desired, use a custom location and adjust any future references.
|
|
14
14
|
4. Install `python4cpm` in your venv:
|
|
15
15
|
- If your CPM can connect to the internet, install with `c:\venv\Scripts\pip install python4cpm`.
|
|
16
16
|
- If your CPM cannot connect to the internet:
|
|
17
17
|
- Download the latest `python4cpm-*.whl` file from the [pypi project files](https://pypi.org/project/python4cpm/#files).
|
|
18
|
-
- Copy the file to
|
|
18
|
+
- Copy the file to the server into a temporary directory called `python4cpm-wheel`.
|
|
19
19
|
- From the parent directory of `python4cpm-wheel` run `c:\venv\Scripts\pip install --no-index --find-links=.\python4cpm-wheel python4cpm`.
|
|
20
20
|
|
|
21
21
|
|
|
@@ -52,7 +52,7 @@ This platform allows you to duplicate it multiple times, simply changing its set
|
|
|
52
52
|
from python4cpm import Python4CPMHandler
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
class
|
|
55
|
+
class MyRotator(Python4CPMHandler): # create a subclass for the Handler
|
|
56
56
|
"""
|
|
57
57
|
These are the usable properties and methods from Python4CPMHandler:
|
|
58
58
|
|
|
@@ -69,13 +69,13 @@ class MyApp(Python4CPMHandler): # create a subclass for the Handler
|
|
|
69
69
|
|
|
70
70
|
Logging methods -> Will only log if PythonLogging (platform parameters) is set to yes (default is yes)
|
|
71
71
|
|
|
72
|
-
self.log_error("this is an error message") # logs error into Logs/ThirdParty/
|
|
73
|
-
self.log_warning("this is a warning message") # logs warning into Logs/ThirdParty/
|
|
74
|
-
self.log_info("this is an info message") # logs info into Logs/ThirdParty/
|
|
72
|
+
self.log_error("this is an error message") # logs error into Logs/ThirdParty/MyRotator.log
|
|
73
|
+
self.log_warning("this is a warning message") # logs warning into Logs/ThirdParty/MyRotator.log
|
|
74
|
+
self.log_info("this is an info message") # logs info into Logs/ThirdParty/MyRotator.log
|
|
75
75
|
|
|
76
76
|
Logging level -> Will only log debug messages if PythonLoggingLevel (platform parameters) is set to debug (default is info)
|
|
77
77
|
|
|
78
|
-
self.log_debug("this is an debug message") # logs info into Logs/ThirdParty/
|
|
78
|
+
self.log_debug("this is an debug message") # logs info into Logs/ThirdParty/MyRotator.log if logging level is set to debug
|
|
79
79
|
|
|
80
80
|
=============================
|
|
81
81
|
REQUIRED TERMINATION SIGNALS
|
|
@@ -128,9 +128,9 @@ class MyApp(Python4CPMHandler): # create a subclass for the Handler
|
|
|
128
128
|
# for your logic in a verification
|
|
129
129
|
result = True
|
|
130
130
|
if result is True:
|
|
131
|
-
self.log_info("verification successful") # logs info message into Logs/ThirdParty/
|
|
131
|
+
self.log_info("verification successful") # logs info message into Logs/ThirdParty/MyRotator.log
|
|
132
132
|
else:
|
|
133
|
-
self.log_error("something went wrong") # logs error message Logs/ThirdParty/
|
|
133
|
+
self.log_error("something went wrong") # logs error message Logs/ThirdParty/MyRotator.log
|
|
134
134
|
self.close_fail()
|
|
135
135
|
|
|
136
136
|
def _change(self, from_reconcile=False):
|
|
@@ -144,26 +144,26 @@ class MyApp(Python4CPMHandler): # create a subclass for the Handler
|
|
|
144
144
|
# self.secrets.reconcile_password.get() and self.secrets.new_password.get() for your logic in a reconciliation
|
|
145
145
|
result = True
|
|
146
146
|
if result is True:
|
|
147
|
-
self.log_info("rotation successful") # logs info message into Logs/ThirdParty/
|
|
147
|
+
self.log_info("rotation successful") # logs info message into Logs/ThirdParty/MyRotator.log
|
|
148
148
|
else:
|
|
149
|
-
self.log_error("something went wrong") # logs error message Logs/ThirdParty/
|
|
149
|
+
self.log_error("something went wrong") # logs error message Logs/ThirdParty/MyRotator.log
|
|
150
150
|
self.close_fail()
|
|
151
151
|
|
|
152
152
|
|
|
153
153
|
if __name__ == "__main__":
|
|
154
|
-
|
|
154
|
+
MyRotator().run() # initializes the class and calls the action that was requested from CPM/SRS.
|
|
155
155
|
```
|
|
156
156
|
(*) More realistic examples can be found [here](https://github.com/gonatienza/python4cpm/blob/main/examples/python4cpmhandler).
|
|
157
157
|
|
|
158
158
|
When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
|
|
159
|
-
1. Verify -> the sciprt will be executed once
|
|
160
|
-
2. Change -> the sciprt will be executed twice,
|
|
161
|
-
- If
|
|
162
|
-
3. Reconcile -> the sciprt will be executed twice,
|
|
163
|
-
- If
|
|
164
|
-
4. When calling `
|
|
165
|
-
5. If a logon account is not linked, `self.args.logon_username` and `self.secrets.logon_password.get()` will return
|
|
166
|
-
6. If a reconcile account is not linked, `self.args.reconcile_username` and `self.secrets.reconcile_password.get()` will return
|
|
159
|
+
1. Verify -> the sciprt will be executed once running the `MyRotator.verify()` method.
|
|
160
|
+
2. Change -> the sciprt will be executed twice, running first the `MyRotator.logon()` method and secondly the `MyRotator.change()` method.
|
|
161
|
+
- If both 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)`.
|
|
162
|
+
3. Reconcile -> the sciprt will be executed twice, running first the `MyRotator.prereconcile()` method and secondly the `MyRotator.reconcile()` method.
|
|
163
|
+
- If both 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)`.
|
|
164
|
+
4. When calling `MyRotator.verify()`, `MyRotator.logon()` or `MyRotator.prereconcile()`: `self.secrets.new_password.get()` will always return an empty string.
|
|
165
|
+
5. If a logon account is not linked, `self.args.logon_username` and `self.secrets.logon_password.get()` will return empty strings.
|
|
166
|
+
6. If a reconcile account is not linked, `self.args.reconcile_username` and `self.secrets.reconcile_password.get()` will return empty strings.
|
|
167
167
|
|
|
168
168
|
|
|
169
169
|
### Using Python4CPM properties and methods directly (for low level controls):
|
|
@@ -267,12 +267,12 @@ if __name__ == "__main__":
|
|
|
267
267
|
When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
|
|
268
268
|
1. Verify -> the sciprt will be executed once with the `p4cpm.args.action` as `Python4CPM.ACTION_VERIFY`.
|
|
269
269
|
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`.
|
|
270
|
-
- If
|
|
270
|
+
- If both 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)`.
|
|
271
271
|
3. Reconcile -> the sciprt will be executed twice, once with the `p4cpm.args.action` as `Python4CPM.ACTION_PRERECONCILE` and once as `Python4CPM.ACTION_RECONCILE`.
|
|
272
|
-
- If
|
|
272
|
+
- If both 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)`.
|
|
273
273
|
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.
|
|
274
|
-
5. If a logon account is not linked, `p4cpm.args.logon_username` and `p4cpm.secrets.logon_password.get()` will return
|
|
275
|
-
6. If a reconcile account is not linked, `p4cpm.args.reconcile_username` and `p4cpm.secrets.reconcile_password.get()` will return
|
|
274
|
+
5. If a logon account is not linked, `p4cpm.args.logon_username` and `p4cpm.secrets.logon_password.get()` will return empty strings.
|
|
275
|
+
6. If a reconcile account is not linked, `p4cpm.args.reconcile_username` and `p4cpm.secrets.reconcile_password.get()` will return empty strings.
|
|
276
276
|
|
|
277
277
|
|
|
278
278
|
### Installing dependencies in python venv
|
|
@@ -297,6 +297,8 @@ pip install python4cpm
|
|
|
297
297
|
|
|
298
298
|
### Example:
|
|
299
299
|
|
|
300
|
+
#### Set your arguments and secrets:
|
|
301
|
+
|
|
300
302
|
```python
|
|
301
303
|
from python4cpm import NETHelper, Python4CPM, Python4CPMHandler
|
|
302
304
|
from getpass import getpass
|
|
@@ -307,11 +309,7 @@ password = getpass("password: ") # password from account
|
|
|
307
309
|
logon_password = getpass("logon_password: ") # password from linked logon account
|
|
308
310
|
reconcile_password = getpass("reconcile_password: ") # password from linked reconcile account
|
|
309
311
|
new_password = getpass("new_password: ") # new password for the rotation
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
#### Set your arguments and secrets:
|
|
313
312
|
|
|
314
|
-
```python
|
|
315
313
|
NETHelper.set(
|
|
316
314
|
action=Python4CPM.ACTION_LOGON, # use actions from Python4CPM.ACTION_*
|
|
317
315
|
address="myapp.corp.local", # populate with the address from your account properties
|
|
@@ -330,7 +328,7 @@ NETHelper.set(
|
|
|
330
328
|
#### Using the handler (recommended):
|
|
331
329
|
|
|
332
330
|
```python
|
|
333
|
-
class
|
|
331
|
+
class MyRotator(Python4CPMHandler):
|
|
334
332
|
def verify(self):
|
|
335
333
|
# TODO: Add your logic here
|
|
336
334
|
self.close_success()
|
|
@@ -351,7 +349,7 @@ class MyApp(Python4CPMHandler):
|
|
|
351
349
|
# TODO: Add your logic here
|
|
352
350
|
self.close_success()
|
|
353
351
|
|
|
354
|
-
|
|
352
|
+
MyRotator().run()
|
|
355
353
|
```
|
|
356
354
|
|
|
357
355
|
#### Using Python4CPM properties and methods directly:
|
|
@@ -367,6 +365,7 @@ p4cpm.close_success()
|
|
|
367
365
|
|
|
368
366
|
#### Remember for your final script:
|
|
369
367
|
|
|
370
|
-
- Change the definition of `p4cpm` from `p4cpm = NETHelper.get()` to `p4cpm = Python4CPM("MyApp")` if you are using the properties and methods directly.
|
|
371
|
-
- Remove any secrets prompting or interactive interruptions.
|
|
372
368
|
- Remove the import of `NETHelper`.
|
|
369
|
+
- Remove the `NETHelper.set()` call.
|
|
370
|
+
- If applicable, change the definition of `p4cpm` from `p4cpm = NETHelper.get()` to `p4cpm = Python4CPM("MyApp")`.
|
|
371
|
+
- Remove any secrets prompting or interactive interruptions.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python4cpm"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.23"
|
|
8
8
|
description = "Python for CPM"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Gonzalo Atienza Rela", email = "gonatienza@gmail.com" }
|
|
@@ -12,7 +12,8 @@ authors = [
|
|
|
12
12
|
dependencies = []
|
|
13
13
|
requires-python = ">=3.10"
|
|
14
14
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
15
|
-
license =
|
|
15
|
+
license = "MIT"
|
|
16
|
+
license-files = ["LICENSE"]
|
|
16
17
|
|
|
17
18
|
[tool.setuptools]
|
|
18
19
|
package-dir = { "" = "src" }
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
from logging.handlers import RotatingFileHandler
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Logger:
|
|
7
|
+
_LOGS_DIR = os.path.join("Logs", "ThirdParty")
|
|
8
|
+
_LOGGING_ENABLED_VALUE = "yes"
|
|
9
|
+
_LOGGING_LEVELS = {
|
|
10
|
+
"info": logging.INFO,
|
|
11
|
+
"debug": logging.DEBUG
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def get_logger(
|
|
16
|
+
cls,
|
|
17
|
+
name: str,
|
|
18
|
+
args_logging: str,
|
|
19
|
+
args_logging_level: str
|
|
20
|
+
) -> logging.Logger:
|
|
21
|
+
if args_logging.lower() != cls._LOGGING_ENABLED_VALUE:
|
|
22
|
+
return None
|
|
23
|
+
os.makedirs(cls._LOGS_DIR, exist_ok=True)
|
|
24
|
+
logs_file = os.path.join(cls._LOGS_DIR, f"{__name__}-{name}.log")
|
|
25
|
+
_id = os.urandom(4).hex()
|
|
26
|
+
logger = logging.getLogger(_id)
|
|
27
|
+
if args_logging_level.lower() in cls._LOGGING_LEVELS:
|
|
28
|
+
logger.setLevel(cls._LOGGING_LEVELS[args_logging_level.lower()])
|
|
29
|
+
else:
|
|
30
|
+
logger.setLevel(cls._LOGGING_LEVELS["info"])
|
|
31
|
+
handler = RotatingFileHandler(
|
|
32
|
+
filename=logs_file,
|
|
33
|
+
maxBytes=1024 ** 2,
|
|
34
|
+
backupCount=1
|
|
35
|
+
)
|
|
36
|
+
fmt = (
|
|
37
|
+
"%(asctime)s.%(msecs)03d | %(levelname)s | %(name)s | "
|
|
38
|
+
"%(module)s | %(funcName)s | %(message)s"
|
|
39
|
+
)
|
|
40
|
+
datefmt = "%Y-%m-%d %H:%M:%S"
|
|
41
|
+
formatter = logging.Formatter(fmt=fmt, datefmt=datefmt)
|
|
42
|
+
handler.setFormatter(formatter)
|
|
43
|
+
logger.addHandler(handler)
|
|
44
|
+
return logger
|
|
@@ -3,7 +3,7 @@ import sys
|
|
|
3
3
|
import atexit
|
|
4
4
|
from python4cpm.secrets import Secrets
|
|
5
5
|
from python4cpm.args import Args
|
|
6
|
-
from python4cpm.logger import
|
|
6
|
+
from python4cpm.logger import Logger
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class Python4CPM:
|
|
@@ -27,15 +27,16 @@ class Python4CPM:
|
|
|
27
27
|
def __init__(self, name: str) -> None:
|
|
28
28
|
self._name = name
|
|
29
29
|
self._args = self._get_args()
|
|
30
|
-
self._logger = get_logger(
|
|
30
|
+
self._logger = Logger.get_logger(
|
|
31
31
|
self._name,
|
|
32
32
|
self._args.logging,
|
|
33
33
|
self._args.logging_level
|
|
34
34
|
)
|
|
35
|
-
self.
|
|
36
|
-
self.
|
|
35
|
+
self.log_debug("Initiating...")
|
|
36
|
+
self._log_env(self._args, False)
|
|
37
37
|
self._verify_action()
|
|
38
38
|
self._secrets = self._get_secrets()
|
|
39
|
+
self._log_env(self._secrets)
|
|
39
40
|
self._closed = False
|
|
40
41
|
atexit.register(self._on_exit)
|
|
41
42
|
|
|
@@ -49,19 +50,19 @@ class Python4CPM:
|
|
|
49
50
|
|
|
50
51
|
def log_debug(self, message: str) -> None:
|
|
51
52
|
if self._logger is not None:
|
|
52
|
-
self._logger.debug(message)
|
|
53
|
+
self._logger.debug(message, stacklevel=2)
|
|
53
54
|
|
|
54
55
|
def log_info(self, message: str) -> None:
|
|
55
56
|
if self._logger is not None:
|
|
56
|
-
self._logger.info(message)
|
|
57
|
+
self._logger.info(message, stacklevel=2)
|
|
57
58
|
|
|
58
59
|
def log_warning(self, message: str) -> None:
|
|
59
60
|
if self._logger is not None:
|
|
60
|
-
self._logger.warning(message)
|
|
61
|
+
self._logger.warning(message, stacklevel=2)
|
|
61
62
|
|
|
62
63
|
def log_error(self, message: str) -> None:
|
|
63
64
|
if self._logger is not None:
|
|
64
|
-
self._logger.error(message)
|
|
65
|
+
self._logger.error(message, stacklevel=2)
|
|
65
66
|
|
|
66
67
|
@classmethod
|
|
67
68
|
def _get_env_key(cls, key: str) -> str:
|
|
@@ -80,46 +81,41 @@ class Python4CPM:
|
|
|
80
81
|
for secret in Secrets.SECRETS:
|
|
81
82
|
_secret = os.environ.get(self._get_env_key(secret))
|
|
82
83
|
secrets[secret] = _secret if _secret is not None else ""
|
|
83
|
-
common_message = f"Python4CPM._get_secrets: {secret} ->"
|
|
84
|
-
if secrets[secret]:
|
|
85
|
-
self.log_info(f"{common_message} [SET]")
|
|
86
|
-
else:
|
|
87
|
-
self.log_info(f"{common_message} [NOT SET]")
|
|
88
84
|
return Secrets(**secrets)
|
|
89
85
|
|
|
90
86
|
def _verify_action(self) -> None:
|
|
91
87
|
if self._args.action not in self._VALID_ACTIONS:
|
|
92
|
-
self.log_warning(
|
|
93
|
-
f"Python4CPM._verify_action: unkonwn action -> {self._args.action}"
|
|
94
|
-
)
|
|
88
|
+
self.log_warning(f"Unkonwn action -> '{self._args.action}'")
|
|
95
89
|
|
|
96
|
-
def
|
|
97
|
-
for key, value in vars(
|
|
98
|
-
|
|
90
|
+
def _log_env(self, obj: object, masked: bool = True) -> None:
|
|
91
|
+
for key, value in vars(obj).items():
|
|
92
|
+
_key = key.strip('_')
|
|
99
93
|
if value:
|
|
100
|
-
|
|
94
|
+
if masked:
|
|
95
|
+
logging_value = "[SET]"
|
|
96
|
+
else:
|
|
97
|
+
logging_value = value
|
|
101
98
|
else:
|
|
102
|
-
|
|
99
|
+
logging_value = "[NOT SET]"
|
|
100
|
+
self.log_debug(f"{_key} -> '{logging_value}'")
|
|
103
101
|
|
|
104
102
|
def close_fail(self, unrecoverable: bool = False) -> None:
|
|
105
103
|
if unrecoverable is False:
|
|
106
104
|
code = self._FAILED_RECOVERABLE_CODE
|
|
107
105
|
else:
|
|
108
106
|
code = self._FAILED_UNRECOVERABLE_CODE
|
|
109
|
-
self.log_error(f"
|
|
107
|
+
self.log_error(f"Closing with code {code}")
|
|
110
108
|
self._closed = True
|
|
111
109
|
sys.exit(code)
|
|
112
110
|
|
|
113
111
|
def close_success(self) -> None:
|
|
114
|
-
self.
|
|
115
|
-
f"Python4CPM.close_success: closing with code {self._SUCCESS_CODE}"
|
|
116
|
-
)
|
|
112
|
+
self.log_debug(f"Closing with code {self._SUCCESS_CODE}")
|
|
117
113
|
self._closed = True
|
|
118
114
|
sys.exit(self._SUCCESS_CODE)
|
|
119
115
|
|
|
120
116
|
def _on_exit(self):
|
|
121
117
|
if self._closed is False:
|
|
122
|
-
message = "
|
|
118
|
+
message = "No close signal called"
|
|
123
119
|
self.log_error(message)
|
|
124
120
|
sys.stderr.write(message)
|
|
125
121
|
sys.stderr.flush()
|
|
@@ -1,30 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python4cpm
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.23
|
|
4
4
|
Summary: Python for CPM
|
|
5
5
|
Author-email: Gonzalo Atienza Rela <gonatienza@gmail.com>
|
|
6
|
-
License: MIT
|
|
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
|
-
|
|
6
|
+
License-Expression: MIT
|
|
28
7
|
Requires-Python: >=3.10
|
|
29
8
|
Description-Content-Type: text/markdown
|
|
30
9
|
License-File: LICENSE
|
|
@@ -32,7 +11,7 @@ Dynamic: license-file
|
|
|
32
11
|
|
|
33
12
|
# Python4CPM
|
|
34
13
|
|
|
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.
|
|
14
|
+
A simple way of using python scripts with CyberArk CPM/SRS 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
15
|
|
|
37
16
|
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
17
|
|
|
@@ -40,14 +19,14 @@ This platform allows you to duplicate it multiple times, simply changing its set
|
|
|
40
19
|
|
|
41
20
|
### Preparing Python
|
|
42
21
|
|
|
43
|
-
1. Install Python
|
|
22
|
+
1. Install Python along CPM or the SRS Connector Management Agent.
|
|
44
23
|
- **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
|
|
24
|
+
3. Create a venv in the server, by running `py -m venv c:\venv`. If desired, use a custom location and adjust any future references.
|
|
46
25
|
4. Install `python4cpm` in your venv:
|
|
47
26
|
- If your CPM can connect to the internet, install with `c:\venv\Scripts\pip install python4cpm`.
|
|
48
27
|
- If your CPM cannot connect to the internet:
|
|
49
28
|
- Download the latest `python4cpm-*.whl` file from the [pypi project files](https://pypi.org/project/python4cpm/#files).
|
|
50
|
-
- Copy the file to
|
|
29
|
+
- Copy the file to the server into a temporary directory called `python4cpm-wheel`.
|
|
51
30
|
- From the parent directory of `python4cpm-wheel` run `c:\venv\Scripts\pip install --no-index --find-links=.\python4cpm-wheel python4cpm`.
|
|
52
31
|
|
|
53
32
|
|
|
@@ -84,7 +63,7 @@ This platform allows you to duplicate it multiple times, simply changing its set
|
|
|
84
63
|
from python4cpm import Python4CPMHandler
|
|
85
64
|
|
|
86
65
|
|
|
87
|
-
class
|
|
66
|
+
class MyRotator(Python4CPMHandler): # create a subclass for the Handler
|
|
88
67
|
"""
|
|
89
68
|
These are the usable properties and methods from Python4CPMHandler:
|
|
90
69
|
|
|
@@ -101,13 +80,13 @@ class MyApp(Python4CPMHandler): # create a subclass for the Handler
|
|
|
101
80
|
|
|
102
81
|
Logging methods -> Will only log if PythonLogging (platform parameters) is set to yes (default is yes)
|
|
103
82
|
|
|
104
|
-
self.log_error("this is an error message") # logs error into Logs/ThirdParty/
|
|
105
|
-
self.log_warning("this is a warning message") # logs warning into Logs/ThirdParty/
|
|
106
|
-
self.log_info("this is an info message") # logs info into Logs/ThirdParty/
|
|
83
|
+
self.log_error("this is an error message") # logs error into Logs/ThirdParty/MyRotator.log
|
|
84
|
+
self.log_warning("this is a warning message") # logs warning into Logs/ThirdParty/MyRotator.log
|
|
85
|
+
self.log_info("this is an info message") # logs info into Logs/ThirdParty/MyRotator.log
|
|
107
86
|
|
|
108
87
|
Logging level -> Will only log debug messages if PythonLoggingLevel (platform parameters) is set to debug (default is info)
|
|
109
88
|
|
|
110
|
-
self.log_debug("this is an debug message") # logs info into Logs/ThirdParty/
|
|
89
|
+
self.log_debug("this is an debug message") # logs info into Logs/ThirdParty/MyRotator.log if logging level is set to debug
|
|
111
90
|
|
|
112
91
|
=============================
|
|
113
92
|
REQUIRED TERMINATION SIGNALS
|
|
@@ -160,9 +139,9 @@ class MyApp(Python4CPMHandler): # create a subclass for the Handler
|
|
|
160
139
|
# for your logic in a verification
|
|
161
140
|
result = True
|
|
162
141
|
if result is True:
|
|
163
|
-
self.log_info("verification successful") # logs info message into Logs/ThirdParty/
|
|
142
|
+
self.log_info("verification successful") # logs info message into Logs/ThirdParty/MyRotator.log
|
|
164
143
|
else:
|
|
165
|
-
self.log_error("something went wrong") # logs error message Logs/ThirdParty/
|
|
144
|
+
self.log_error("something went wrong") # logs error message Logs/ThirdParty/MyRotator.log
|
|
166
145
|
self.close_fail()
|
|
167
146
|
|
|
168
147
|
def _change(self, from_reconcile=False):
|
|
@@ -176,26 +155,26 @@ class MyApp(Python4CPMHandler): # create a subclass for the Handler
|
|
|
176
155
|
# self.secrets.reconcile_password.get() and self.secrets.new_password.get() for your logic in a reconciliation
|
|
177
156
|
result = True
|
|
178
157
|
if result is True:
|
|
179
|
-
self.log_info("rotation successful") # logs info message into Logs/ThirdParty/
|
|
158
|
+
self.log_info("rotation successful") # logs info message into Logs/ThirdParty/MyRotator.log
|
|
180
159
|
else:
|
|
181
|
-
self.log_error("something went wrong") # logs error message Logs/ThirdParty/
|
|
160
|
+
self.log_error("something went wrong") # logs error message Logs/ThirdParty/MyRotator.log
|
|
182
161
|
self.close_fail()
|
|
183
162
|
|
|
184
163
|
|
|
185
164
|
if __name__ == "__main__":
|
|
186
|
-
|
|
165
|
+
MyRotator().run() # initializes the class and calls the action that was requested from CPM/SRS.
|
|
187
166
|
```
|
|
188
167
|
(*) More realistic examples can be found [here](https://github.com/gonatienza/python4cpm/blob/main/examples/python4cpmhandler).
|
|
189
168
|
|
|
190
169
|
When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
|
|
191
|
-
1. Verify -> the sciprt will be executed once
|
|
192
|
-
2. Change -> the sciprt will be executed twice,
|
|
193
|
-
- If
|
|
194
|
-
3. Reconcile -> the sciprt will be executed twice,
|
|
195
|
-
- If
|
|
196
|
-
4. When calling `
|
|
197
|
-
5. If a logon account is not linked, `self.args.logon_username` and `self.secrets.logon_password.get()` will return
|
|
198
|
-
6. If a reconcile account is not linked, `self.args.reconcile_username` and `self.secrets.reconcile_password.get()` will return
|
|
170
|
+
1. Verify -> the sciprt will be executed once running the `MyRotator.verify()` method.
|
|
171
|
+
2. Change -> the sciprt will be executed twice, running first the `MyRotator.logon()` method and secondly the `MyRotator.change()` method.
|
|
172
|
+
- If both 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)`.
|
|
173
|
+
3. Reconcile -> the sciprt will be executed twice, running first the `MyRotator.prereconcile()` method and secondly the `MyRotator.reconcile()` method.
|
|
174
|
+
- If both 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)`.
|
|
175
|
+
4. When calling `MyRotator.verify()`, `MyRotator.logon()` or `MyRotator.prereconcile()`: `self.secrets.new_password.get()` will always return an empty string.
|
|
176
|
+
5. If a logon account is not linked, `self.args.logon_username` and `self.secrets.logon_password.get()` will return empty strings.
|
|
177
|
+
6. If a reconcile account is not linked, `self.args.reconcile_username` and `self.secrets.reconcile_password.get()` will return empty strings.
|
|
199
178
|
|
|
200
179
|
|
|
201
180
|
### Using Python4CPM properties and methods directly (for low level controls):
|
|
@@ -299,12 +278,12 @@ if __name__ == "__main__":
|
|
|
299
278
|
When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
|
|
300
279
|
1. Verify -> the sciprt will be executed once with the `p4cpm.args.action` as `Python4CPM.ACTION_VERIFY`.
|
|
301
280
|
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
|
|
281
|
+
- If both 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
282
|
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
|
|
283
|
+
- If both 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
284
|
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
|
|
307
|
-
6. If a reconcile account is not linked, `p4cpm.args.reconcile_username` and `p4cpm.secrets.reconcile_password.get()` will return
|
|
285
|
+
5. If a logon account is not linked, `p4cpm.args.logon_username` and `p4cpm.secrets.logon_password.get()` will return empty strings.
|
|
286
|
+
6. If a reconcile account is not linked, `p4cpm.args.reconcile_username` and `p4cpm.secrets.reconcile_password.get()` will return empty strings.
|
|
308
287
|
|
|
309
288
|
|
|
310
289
|
### Installing dependencies in python venv
|
|
@@ -329,6 +308,8 @@ pip install python4cpm
|
|
|
329
308
|
|
|
330
309
|
### Example:
|
|
331
310
|
|
|
311
|
+
#### Set your arguments and secrets:
|
|
312
|
+
|
|
332
313
|
```python
|
|
333
314
|
from python4cpm import NETHelper, Python4CPM, Python4CPMHandler
|
|
334
315
|
from getpass import getpass
|
|
@@ -339,11 +320,7 @@ password = getpass("password: ") # password from account
|
|
|
339
320
|
logon_password = getpass("logon_password: ") # password from linked logon account
|
|
340
321
|
reconcile_password = getpass("reconcile_password: ") # password from linked reconcile account
|
|
341
322
|
new_password = getpass("new_password: ") # new password for the rotation
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
#### Set your arguments and secrets:
|
|
345
323
|
|
|
346
|
-
```python
|
|
347
324
|
NETHelper.set(
|
|
348
325
|
action=Python4CPM.ACTION_LOGON, # use actions from Python4CPM.ACTION_*
|
|
349
326
|
address="myapp.corp.local", # populate with the address from your account properties
|
|
@@ -362,7 +339,7 @@ NETHelper.set(
|
|
|
362
339
|
#### Using the handler (recommended):
|
|
363
340
|
|
|
364
341
|
```python
|
|
365
|
-
class
|
|
342
|
+
class MyRotator(Python4CPMHandler):
|
|
366
343
|
def verify(self):
|
|
367
344
|
# TODO: Add your logic here
|
|
368
345
|
self.close_success()
|
|
@@ -383,7 +360,7 @@ class MyApp(Python4CPMHandler):
|
|
|
383
360
|
# TODO: Add your logic here
|
|
384
361
|
self.close_success()
|
|
385
362
|
|
|
386
|
-
|
|
363
|
+
MyRotator().run()
|
|
387
364
|
```
|
|
388
365
|
|
|
389
366
|
#### Using Python4CPM properties and methods directly:
|
|
@@ -399,6 +376,7 @@ p4cpm.close_success()
|
|
|
399
376
|
|
|
400
377
|
#### Remember for your final script:
|
|
401
378
|
|
|
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
379
|
- Remove the import of `NETHelper`.
|
|
380
|
+
- Remove the `NETHelper.set()` call.
|
|
381
|
+
- If applicable, change the definition of `p4cpm` from `p4cpm = NETHelper.get()` to `p4cpm = Python4CPM("MyApp")`.
|
|
382
|
+
- Remove any secrets prompting or interactive interruptions.
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import logging
|
|
3
|
-
from logging.handlers import RotatingFileHandler
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
_LOGS_DIR = os.path.join("Logs", "ThirdParty")
|
|
7
|
-
_LOGGING_ENABLED_VALUE = "yes"
|
|
8
|
-
_LOGGING_LEVELS = {
|
|
9
|
-
"info": logging.INFO,
|
|
10
|
-
"debug": logging.DEBUG
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def get_logger(
|
|
15
|
-
name: str,
|
|
16
|
-
args_logging: str,
|
|
17
|
-
args_logging_level: str
|
|
18
|
-
) -> logging.Logger:
|
|
19
|
-
if args_logging.lower() != _LOGGING_ENABLED_VALUE:
|
|
20
|
-
return None
|
|
21
|
-
os.makedirs(_LOGS_DIR, exist_ok=True)
|
|
22
|
-
logs_file = os.path.join(_LOGS_DIR, f"{__name__}-{name}.log")
|
|
23
|
-
_id = os.urandom(4).hex()
|
|
24
|
-
logger = logging.getLogger(_id)
|
|
25
|
-
if args_logging_level.lower() in _LOGGING_LEVELS:
|
|
26
|
-
logger.setLevel(_LOGGING_LEVELS[args_logging_level.lower()])
|
|
27
|
-
else:
|
|
28
|
-
logger.setLevel(_LOGGING_LEVELS["info"])
|
|
29
|
-
handler = RotatingFileHandler(
|
|
30
|
-
filename=logs_file,
|
|
31
|
-
maxBytes=1024 ** 2,
|
|
32
|
-
backupCount=1
|
|
33
|
-
)
|
|
34
|
-
formatter = logging.Formatter(
|
|
35
|
-
fmt="%(asctime)s | %(name)s | %(levelname)s | %(message)s",
|
|
36
|
-
datefmt="%Y-%m-%d %H:%M:%S"
|
|
37
|
-
)
|
|
38
|
-
handler.setFormatter(formatter)
|
|
39
|
-
logger.addHandler(handler)
|
|
40
|
-
return logger
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|