python4cpm 1.0.27__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python4cpm-1.1.0/PKG-INFO +275 -0
- python4cpm-1.1.0/README.md +264 -0
- {python4cpm-1.0.27 → python4cpm-1.1.0}/pyproject.toml +1 -1
- python4cpm-1.1.0/src/python4cpm/accounts.py +70 -0
- python4cpm-1.1.0/src/python4cpm/args.py +21 -0
- {python4cpm-1.0.27 → python4cpm-1.1.0}/src/python4cpm/logger.py +5 -5
- python4cpm-1.1.0/src/python4cpm/nethelper.py +46 -0
- {python4cpm-1.0.27 → python4cpm-1.1.0}/src/python4cpm/python4cpm.py +48 -46
- python4cpm-1.1.0/src/python4cpm/secret.py +15 -0
- python4cpm-1.1.0/src/python4cpm.egg-info/PKG-INFO +275 -0
- {python4cpm-1.0.27 → python4cpm-1.1.0}/src/python4cpm.egg-info/SOURCES.txt +2 -1
- python4cpm-1.0.27/PKG-INFO +0 -385
- python4cpm-1.0.27/README.md +0 -374
- python4cpm-1.0.27/src/python4cpm/args.py +0 -63
- python4cpm-1.0.27/src/python4cpm/nethelper.py +0 -54
- python4cpm-1.0.27/src/python4cpm/secrets.py +0 -52
- python4cpm-1.0.27/src/python4cpm.egg-info/PKG-INFO +0 -385
- {python4cpm-1.0.27 → python4cpm-1.1.0}/LICENSE +0 -0
- {python4cpm-1.0.27 → python4cpm-1.1.0}/setup.cfg +0 -0
- {python4cpm-1.0.27 → python4cpm-1.1.0}/src/python4cpm/__init__.py +0 -0
- {python4cpm-1.0.27 → python4cpm-1.1.0}/src/python4cpm/crypto.py +0 -0
- {python4cpm-1.0.27 → python4cpm-1.1.0}/src/python4cpm/python4cpmhandler.py +0 -0
- {python4cpm-1.0.27 → python4cpm-1.1.0}/src/python4cpm.egg-info/dependency_links.txt +0 -0
- {python4cpm-1.0.27 → python4cpm-1.1.0}/src/python4cpm.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python4cpm
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Python for CPM
|
|
5
|
+
Author-email: Gonzalo Atienza Rela <gonatienza@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Dynamic: license-file
|
|
11
|
+
|
|
12
|
+
# Python4CPM
|
|
13
|
+
|
|
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.
|
|
15
|
+
|
|
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`.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
### Preparing Python
|
|
21
|
+
|
|
22
|
+
1. Install Python along CPM or the SRS Connector Management Agent.
|
|
23
|
+
- **Python must be installed for all users**. Follow the custom install steps from the installation wizard to check the checkbox.
|
|
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.
|
|
25
|
+
4. Install `python4cpm` in your venv:
|
|
26
|
+
- If your CPM can connect to the internet, install with `c:\venv\Scripts\pip.exe install python4cpm`.
|
|
27
|
+
- If your CPM cannot connect to the internet:
|
|
28
|
+
- Download the latest `python4cpm-*.whl` file from the [pypi project files](https://pypi.org/project/python4cpm/#files).
|
|
29
|
+
- Copy the file to the server into a temporary directory called `python4cpm-wheel`.
|
|
30
|
+
- From the parent directory of `python4cpm-wheel` run `c:\venv\Scripts\pip.exe install --no-index --find-links=.\python4cpm-wheel python4cpm`.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Importing the platform
|
|
34
|
+
|
|
35
|
+
#### If you are using CPM (SaaS or Self-Hosted):
|
|
36
|
+
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.
|
|
37
|
+
2. Download the `python4cpm-platform-*.zip` asset from the latest [release](https://github.com/gonatienza/python4cpm/releases).
|
|
38
|
+
3. Import the platform zip file into Privilege Cloud/PVWA `(Administration -> Platform Management -> Import platform)`.
|
|
39
|
+
4. Craft your python script and place it within a folder in CPM (e.g., `C:\python4cpm-scripts`).
|
|
40
|
+
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).
|
|
41
|
+
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`).
|
|
42
|
+
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`).
|
|
43
|
+
8. If you want to disable logging, update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonLogging -> Value` to `no`.
|
|
44
|
+
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`.
|
|
45
|
+
10. For new applications repeat steps from 4 to 9.
|
|
46
|
+
|
|
47
|
+
#### If you are using SRS (SaaS only):
|
|
48
|
+
1. Download the `python4cpm-platform-*.zip` asset from the latest [release](https://github.com/gonatienza/python4cpm/releases).
|
|
49
|
+
2. Import the platform zip file into Privilege Cloud `(Administration -> Platform Management -> Import platform)`.
|
|
50
|
+
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`).
|
|
51
|
+
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).
|
|
52
|
+
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`).
|
|
53
|
+
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`).
|
|
54
|
+
7. If you want to disable logging, update `Plugin Settings -> Additional Parameters -> PythonLogging` to `no`.
|
|
55
|
+
8. If you want to change the logging level to `debug`, update `Plugin Settings -> Additional Parameters -> PythonLoggingLevel -> Value` to `debug`.
|
|
56
|
+
9. For new applications repeat steps from 3 to 8.
|
|
57
|
+
|
|
58
|
+
## Python Script
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from python4cpm import Python4CPMHandler
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class MyRotator(Python4CPMHandler): # create a subclass for the Handler
|
|
65
|
+
"""
|
|
66
|
+
These are the usable properties and methods from Python4CPMHandler:
|
|
67
|
+
|
|
68
|
+
self.args.action
|
|
69
|
+
# action requested from CPM/SRS
|
|
70
|
+
|
|
71
|
+
## Target Account
|
|
72
|
+
|
|
73
|
+
self.target_account.username
|
|
74
|
+
# address from account
|
|
75
|
+
|
|
76
|
+
self.target_account.address
|
|
77
|
+
# address from account
|
|
78
|
+
|
|
79
|
+
self.target_account.port
|
|
80
|
+
# port from account
|
|
81
|
+
|
|
82
|
+
self.target_account.password.get()
|
|
83
|
+
# get plaintext str from password object
|
|
84
|
+
|
|
85
|
+
## Logon Account
|
|
86
|
+
self.logon_account.username
|
|
87
|
+
self.logon_account.password.get()
|
|
88
|
+
|
|
89
|
+
## Reconcile Account
|
|
90
|
+
self.reconcile_account.username
|
|
91
|
+
self.reconcile_account.password.get()
|
|
92
|
+
|
|
93
|
+
## Logging
|
|
94
|
+
|
|
95
|
+
self.logger.critical("this is critical message")
|
|
96
|
+
self.logger.error("this is an error message")
|
|
97
|
+
self.logger.warning("this is a warning message")
|
|
98
|
+
self.logger.info("this is an info message")
|
|
99
|
+
self.logger.debug("this is a debug message")
|
|
100
|
+
|
|
101
|
+
# logs are placed in Logs/ThirdParty/MyRotator.log
|
|
102
|
+
|
|
103
|
+
## The logging level comes from PythonLoggingLevel (platform parameters) (default is error)
|
|
104
|
+
|
|
105
|
+
=============================
|
|
106
|
+
REQUIRED TERMINATION SIGNALS
|
|
107
|
+
=============================
|
|
108
|
+
Terminate signals -> MUST use one of the following three signals to terminate the script:
|
|
109
|
+
|
|
110
|
+
self.close_success()
|
|
111
|
+
# terminate with success state
|
|
112
|
+
|
|
113
|
+
self.close_fail()
|
|
114
|
+
# terminate with recoverable failed state
|
|
115
|
+
|
|
116
|
+
self.close_fail(unrecoverable=True)
|
|
117
|
+
# terminate with unrecoverable failed state
|
|
118
|
+
|
|
119
|
+
When calling a signal sys.exit is invoked and the script is terminated.
|
|
120
|
+
If no signal is called, and the script finishes without any exception,
|
|
121
|
+
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()
|
|
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 account objects for your logic
|
|
156
|
+
else:
|
|
157
|
+
pass
|
|
158
|
+
# TODO: use account objects for your logic
|
|
159
|
+
result = True
|
|
160
|
+
if result is True:
|
|
161
|
+
self.log_info("verification successful")
|
|
162
|
+
else:
|
|
163
|
+
self.log_error("something went wrong")
|
|
164
|
+
self.close_fail()
|
|
165
|
+
|
|
166
|
+
def _change(self, from_reconcile=False):
|
|
167
|
+
if from_reconcile is False:
|
|
168
|
+
pass
|
|
169
|
+
# TODO: use account objects for your logic
|
|
170
|
+
else:
|
|
171
|
+
pass
|
|
172
|
+
# TODO: use account objects for your logic
|
|
173
|
+
result = True
|
|
174
|
+
if result is True:
|
|
175
|
+
self.log_info("rotation successful")
|
|
176
|
+
else:
|
|
177
|
+
self.log_error("something went wrong")
|
|
178
|
+
self.close_fail()
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
if __name__ == "__main__":
|
|
182
|
+
MyRotator().run() # initializes the class and calls the action that was requested from CPM/SRS.
|
|
183
|
+
```
|
|
184
|
+
(*) More realistic examples can be found [here](https://github.com/gonatienza/python4cpm/blob/main/examples).
|
|
185
|
+
|
|
186
|
+
When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
|
|
187
|
+
1. Verify -> the sciprt will be executed once running the `MyRotator.verify()` method.
|
|
188
|
+
2. Change -> the sciprt will be executed twice, running first the `MyRotator.logon()` method and secondly the `MyRotator.change()` method.
|
|
189
|
+
- 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)`.
|
|
190
|
+
3. Reconcile -> the sciprt will be executed twice, running first the `MyRotator.prereconcile()` method and secondly the `MyRotator.reconcile()` method.
|
|
191
|
+
- 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)`.
|
|
192
|
+
4. When calling `MyRotator.verify()`, `MyRotator.logon()` or `MyRotator.prereconcile()`: `self.secrets.new_password.get()` will always return an empty string.
|
|
193
|
+
5. If a logon account is not linked, `self.args.logon_username` and `self.secrets.logon_password.get()` will return empty strings.
|
|
194
|
+
6. If a reconcile account is not linked, `self.args.reconcile_username` and `self.secrets.reconcile_password.get()` will return empty strings.
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
### Installing dependencies in python venv
|
|
198
|
+
|
|
199
|
+
As with any python venv, you can install dependencies in your venv.
|
|
200
|
+
1. If your CPM can connect to the internet:
|
|
201
|
+
- You can use regular pip install commands (e.g., `c:\venv\Scripts\pip.exe install requests`).
|
|
202
|
+
2. If your CPM cannot connect to the internet:
|
|
203
|
+
- You can download packages for an offline install. More info [here](https://pip.pypa.io/en/stable/cli/pip_download/).
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
## Dev Helper:
|
|
207
|
+
|
|
208
|
+
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.
|
|
209
|
+
Install this module (in a dev workstation) with:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
pip install python4cpm
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**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.
|
|
216
|
+
|
|
217
|
+
### Example:
|
|
218
|
+
|
|
219
|
+
#### Set your arguments and secrets:
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from python4cpm import NETHelper, Python4CPM, Python4CPMHandler
|
|
223
|
+
from getpass import getpass
|
|
224
|
+
|
|
225
|
+
# Get secrets for your password, logon account password, reconcile account password and new password
|
|
226
|
+
# You can use an empty string if it does not apply
|
|
227
|
+
target_password = getpass("password: ") # password from account
|
|
228
|
+
logon_password = getpass("logon_password: ") # password from linked logon account
|
|
229
|
+
reconcile_password = getpass("reconcile_password: ") # password from linked reconcile account
|
|
230
|
+
target_new_password = getpass("new_password: ") # new password for the rotation
|
|
231
|
+
|
|
232
|
+
NETHelper.set(
|
|
233
|
+
action=Python4CPM.ACTION_CHANGE, # use actions from Python4CPM.ACTION_*
|
|
234
|
+
target_username="jdoe",
|
|
235
|
+
target_address="myapp.corp.local",
|
|
236
|
+
target_port="8443",
|
|
237
|
+
logon_username="ldoe",
|
|
238
|
+
reconcile_username="rdoe",
|
|
239
|
+
logging_level="debug", # "critical", "error", "warning", "info" or "debug"
|
|
240
|
+
target_password=target_password,
|
|
241
|
+
logon_password=logon_password,
|
|
242
|
+
reconcile_password=reconcile_password,
|
|
243
|
+
target_new_password=target_new_password
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
class MyRotator(Python4CPMHandler):
|
|
247
|
+
def verify(self):
|
|
248
|
+
# TODO: Add your logic here
|
|
249
|
+
self.close_success()
|
|
250
|
+
|
|
251
|
+
def logon(self):
|
|
252
|
+
# TODO: Add your logic here
|
|
253
|
+
self.close_success()
|
|
254
|
+
|
|
255
|
+
def change(self):
|
|
256
|
+
# TODO: Add your logic here
|
|
257
|
+
self.close_success()
|
|
258
|
+
|
|
259
|
+
def prereconcile(self):
|
|
260
|
+
# TODO: Add your logic here
|
|
261
|
+
self.close_success()
|
|
262
|
+
|
|
263
|
+
def reconcile(self):
|
|
264
|
+
# TODO: Add your logic here
|
|
265
|
+
self.close_success()
|
|
266
|
+
|
|
267
|
+
MyRotator().run()
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
#### Remember for your final script:
|
|
271
|
+
|
|
272
|
+
- Remove the import of `NETHelper`.
|
|
273
|
+
- Remove the `NETHelper.set()` call.
|
|
274
|
+
- If applicable, change the definition of `p4cpm` from `p4cpm = NETHelper.get()` to `p4cpm = Python4CPM("MyApp")`.
|
|
275
|
+
- Remove any secrets prompting or interactive interruptions.
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# Python4CPM
|
|
2
|
+
|
|
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
|
+
|
|
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
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
### Preparing Python
|
|
10
|
+
|
|
11
|
+
1. Install Python along CPM or the SRS Connector Management Agent.
|
|
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 the server, by running `py -m venv c:\venv`. If desired, use a custom location and adjust any future references.
|
|
14
|
+
4. Install `python4cpm` in your venv:
|
|
15
|
+
- If your CPM can connect to the internet, install with `c:\venv\Scripts\pip.exe install python4cpm`.
|
|
16
|
+
- If your CPM cannot connect to the internet:
|
|
17
|
+
- Download the latest `python4cpm-*.whl` file from the [pypi project files](https://pypi.org/project/python4cpm/#files).
|
|
18
|
+
- Copy the file to the server into a temporary directory called `python4cpm-wheel`.
|
|
19
|
+
- From the parent directory of `python4cpm-wheel` run `c:\venv\Scripts\pip.exe install --no-index --find-links=.\python4cpm-wheel python4cpm`.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Importing the platform
|
|
23
|
+
|
|
24
|
+
#### If you are using CPM (SaaS or Self-Hosted):
|
|
25
|
+
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.
|
|
26
|
+
2. Download the `python4cpm-platform-*.zip` asset from the latest [release](https://github.com/gonatienza/python4cpm/releases).
|
|
27
|
+
3. Import the platform zip file into Privilege Cloud/PVWA `(Administration -> Platform Management -> Import platform)`.
|
|
28
|
+
4. Craft your python script and place it within a folder in CPM (e.g., `C:\python4cpm-scripts`).
|
|
29
|
+
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).
|
|
30
|
+
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`).
|
|
31
|
+
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`).
|
|
32
|
+
8. If you want to disable logging, update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonLogging -> Value` to `no`.
|
|
33
|
+
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`.
|
|
34
|
+
10. For new applications repeat steps from 4 to 9.
|
|
35
|
+
|
|
36
|
+
#### If you are using SRS (SaaS only):
|
|
37
|
+
1. Download the `python4cpm-platform-*.zip` asset from the latest [release](https://github.com/gonatienza/python4cpm/releases).
|
|
38
|
+
2. Import the platform zip file into Privilege Cloud `(Administration -> Platform Management -> Import platform)`.
|
|
39
|
+
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`).
|
|
40
|
+
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).
|
|
41
|
+
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`).
|
|
42
|
+
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`).
|
|
43
|
+
7. If you want to disable logging, update `Plugin Settings -> Additional Parameters -> PythonLogging` to `no`.
|
|
44
|
+
8. If you want to change the logging level to `debug`, update `Plugin Settings -> Additional Parameters -> PythonLoggingLevel -> Value` to `debug`.
|
|
45
|
+
9. For new applications repeat steps from 3 to 8.
|
|
46
|
+
|
|
47
|
+
## Python Script
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from python4cpm import Python4CPMHandler
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class MyRotator(Python4CPMHandler): # create a subclass for the Handler
|
|
54
|
+
"""
|
|
55
|
+
These are the usable properties and methods from Python4CPMHandler:
|
|
56
|
+
|
|
57
|
+
self.args.action
|
|
58
|
+
# action requested from CPM/SRS
|
|
59
|
+
|
|
60
|
+
## Target Account
|
|
61
|
+
|
|
62
|
+
self.target_account.username
|
|
63
|
+
# address from account
|
|
64
|
+
|
|
65
|
+
self.target_account.address
|
|
66
|
+
# address from account
|
|
67
|
+
|
|
68
|
+
self.target_account.port
|
|
69
|
+
# port from account
|
|
70
|
+
|
|
71
|
+
self.target_account.password.get()
|
|
72
|
+
# get plaintext str from password object
|
|
73
|
+
|
|
74
|
+
## Logon Account
|
|
75
|
+
self.logon_account.username
|
|
76
|
+
self.logon_account.password.get()
|
|
77
|
+
|
|
78
|
+
## Reconcile Account
|
|
79
|
+
self.reconcile_account.username
|
|
80
|
+
self.reconcile_account.password.get()
|
|
81
|
+
|
|
82
|
+
## Logging
|
|
83
|
+
|
|
84
|
+
self.logger.critical("this is critical message")
|
|
85
|
+
self.logger.error("this is an error message")
|
|
86
|
+
self.logger.warning("this is a warning message")
|
|
87
|
+
self.logger.info("this is an info message")
|
|
88
|
+
self.logger.debug("this is a debug message")
|
|
89
|
+
|
|
90
|
+
# logs are placed in Logs/ThirdParty/MyRotator.log
|
|
91
|
+
|
|
92
|
+
## The logging level comes from PythonLoggingLevel (platform parameters) (default is error)
|
|
93
|
+
|
|
94
|
+
=============================
|
|
95
|
+
REQUIRED TERMINATION SIGNALS
|
|
96
|
+
=============================
|
|
97
|
+
Terminate signals -> MUST use one of the following three signals to terminate the script:
|
|
98
|
+
|
|
99
|
+
self.close_success()
|
|
100
|
+
# terminate with success state
|
|
101
|
+
|
|
102
|
+
self.close_fail()
|
|
103
|
+
# terminate with recoverable failed state
|
|
104
|
+
|
|
105
|
+
self.close_fail(unrecoverable=True)
|
|
106
|
+
# terminate with unrecoverable failed state
|
|
107
|
+
|
|
108
|
+
When calling a signal sys.exit is invoked and the script is terminated.
|
|
109
|
+
If no signal is called, and the script finishes without any exception,
|
|
110
|
+
it will behave like p4cpm.close_fail(unrecoverable=True) and log an error message.
|
|
111
|
+
=============================
|
|
112
|
+
=============================
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
# =============================
|
|
116
|
+
# REQUIRED METHODS (MUST DEFINE)
|
|
117
|
+
# =============================
|
|
118
|
+
# verify(), logon(), change(), prereconcile(), reconcile()
|
|
119
|
+
|
|
120
|
+
def verify(self):
|
|
121
|
+
self._verify()
|
|
122
|
+
self.log_info("verification successful")
|
|
123
|
+
self.close_success()
|
|
124
|
+
|
|
125
|
+
def logon(self):
|
|
126
|
+
self.close_success()
|
|
127
|
+
|
|
128
|
+
def change(self):
|
|
129
|
+
self._change()
|
|
130
|
+
self.log_error("something went wrong")
|
|
131
|
+
self.close_fail()
|
|
132
|
+
|
|
133
|
+
def prereconcile(self):
|
|
134
|
+
self._verify(from_reconcile=True)
|
|
135
|
+
self.close_success()
|
|
136
|
+
|
|
137
|
+
def reconcile(self):
|
|
138
|
+
self._change(from_reconcile=True)
|
|
139
|
+
self.close_success()
|
|
140
|
+
|
|
141
|
+
def _verify(self, from_reconcile=False):
|
|
142
|
+
if from_reconcile is False:
|
|
143
|
+
pass
|
|
144
|
+
# TODO: use account objects for your logic
|
|
145
|
+
else:
|
|
146
|
+
pass
|
|
147
|
+
# TODO: use account objects for your logic
|
|
148
|
+
result = True
|
|
149
|
+
if result is True:
|
|
150
|
+
self.log_info("verification successful")
|
|
151
|
+
else:
|
|
152
|
+
self.log_error("something went wrong")
|
|
153
|
+
self.close_fail()
|
|
154
|
+
|
|
155
|
+
def _change(self, from_reconcile=False):
|
|
156
|
+
if from_reconcile is False:
|
|
157
|
+
pass
|
|
158
|
+
# TODO: use account objects for your logic
|
|
159
|
+
else:
|
|
160
|
+
pass
|
|
161
|
+
# TODO: use account objects for your logic
|
|
162
|
+
result = True
|
|
163
|
+
if result is True:
|
|
164
|
+
self.log_info("rotation successful")
|
|
165
|
+
else:
|
|
166
|
+
self.log_error("something went wrong")
|
|
167
|
+
self.close_fail()
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
if __name__ == "__main__":
|
|
171
|
+
MyRotator().run() # initializes the class and calls the action that was requested from CPM/SRS.
|
|
172
|
+
```
|
|
173
|
+
(*) More realistic examples can be found [here](https://github.com/gonatienza/python4cpm/blob/main/examples).
|
|
174
|
+
|
|
175
|
+
When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
|
|
176
|
+
1. Verify -> the sciprt will be executed once running the `MyRotator.verify()` method.
|
|
177
|
+
2. Change -> the sciprt will be executed twice, running first the `MyRotator.logon()` method and secondly the `MyRotator.change()` method.
|
|
178
|
+
- 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)`.
|
|
179
|
+
3. Reconcile -> the sciprt will be executed twice, running first the `MyRotator.prereconcile()` method and secondly the `MyRotator.reconcile()` method.
|
|
180
|
+
- 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)`.
|
|
181
|
+
4. When calling `MyRotator.verify()`, `MyRotator.logon()` or `MyRotator.prereconcile()`: `self.secrets.new_password.get()` will always return an empty string.
|
|
182
|
+
5. If a logon account is not linked, `self.args.logon_username` and `self.secrets.logon_password.get()` will return empty strings.
|
|
183
|
+
6. If a reconcile account is not linked, `self.args.reconcile_username` and `self.secrets.reconcile_password.get()` will return empty strings.
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
### Installing dependencies in python venv
|
|
187
|
+
|
|
188
|
+
As with any python venv, you can install dependencies in your venv.
|
|
189
|
+
1. If your CPM can connect to the internet:
|
|
190
|
+
- You can use regular pip install commands (e.g., `c:\venv\Scripts\pip.exe install requests`).
|
|
191
|
+
2. If your CPM cannot connect to the internet:
|
|
192
|
+
- You can download packages for an offline install. More info [here](https://pip.pypa.io/en/stable/cli/pip_download/).
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
## Dev Helper:
|
|
196
|
+
|
|
197
|
+
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.
|
|
198
|
+
Install this module (in a dev workstation) with:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
pip install python4cpm
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**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.
|
|
205
|
+
|
|
206
|
+
### Example:
|
|
207
|
+
|
|
208
|
+
#### Set your arguments and secrets:
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from python4cpm import NETHelper, Python4CPM, Python4CPMHandler
|
|
212
|
+
from getpass import getpass
|
|
213
|
+
|
|
214
|
+
# Get secrets for your password, logon account password, reconcile account password and new password
|
|
215
|
+
# You can use an empty string if it does not apply
|
|
216
|
+
target_password = getpass("password: ") # password from account
|
|
217
|
+
logon_password = getpass("logon_password: ") # password from linked logon account
|
|
218
|
+
reconcile_password = getpass("reconcile_password: ") # password from linked reconcile account
|
|
219
|
+
target_new_password = getpass("new_password: ") # new password for the rotation
|
|
220
|
+
|
|
221
|
+
NETHelper.set(
|
|
222
|
+
action=Python4CPM.ACTION_CHANGE, # use actions from Python4CPM.ACTION_*
|
|
223
|
+
target_username="jdoe",
|
|
224
|
+
target_address="myapp.corp.local",
|
|
225
|
+
target_port="8443",
|
|
226
|
+
logon_username="ldoe",
|
|
227
|
+
reconcile_username="rdoe",
|
|
228
|
+
logging_level="debug", # "critical", "error", "warning", "info" or "debug"
|
|
229
|
+
target_password=target_password,
|
|
230
|
+
logon_password=logon_password,
|
|
231
|
+
reconcile_password=reconcile_password,
|
|
232
|
+
target_new_password=target_new_password
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
class MyRotator(Python4CPMHandler):
|
|
236
|
+
def verify(self):
|
|
237
|
+
# TODO: Add your logic here
|
|
238
|
+
self.close_success()
|
|
239
|
+
|
|
240
|
+
def logon(self):
|
|
241
|
+
# TODO: Add your logic here
|
|
242
|
+
self.close_success()
|
|
243
|
+
|
|
244
|
+
def change(self):
|
|
245
|
+
# TODO: Add your logic here
|
|
246
|
+
self.close_success()
|
|
247
|
+
|
|
248
|
+
def prereconcile(self):
|
|
249
|
+
# TODO: Add your logic here
|
|
250
|
+
self.close_success()
|
|
251
|
+
|
|
252
|
+
def reconcile(self):
|
|
253
|
+
# TODO: Add your logic here
|
|
254
|
+
self.close_success()
|
|
255
|
+
|
|
256
|
+
MyRotator().run()
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### Remember for your final script:
|
|
260
|
+
|
|
261
|
+
- Remove the import of `NETHelper`.
|
|
262
|
+
- Remove the `NETHelper.set()` call.
|
|
263
|
+
- If applicable, change the definition of `p4cpm` from `p4cpm = NETHelper.get()` to `p4cpm = Python4CPM("MyApp")`.
|
|
264
|
+
- Remove any secrets prompting or interactive interruptions.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from python4cpm.secret import Secret
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BaseAccount:
|
|
5
|
+
def __init__(
|
|
6
|
+
self: str,
|
|
7
|
+
username: str,
|
|
8
|
+
password: str
|
|
9
|
+
) -> None:
|
|
10
|
+
self._username = username
|
|
11
|
+
self._password = Secret(password)
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def username(self) -> str:
|
|
15
|
+
return self._username
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def password(self) -> Secret:
|
|
19
|
+
return self._password
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TargetAccount(BaseAccount):
|
|
23
|
+
ENV_VARS = (
|
|
24
|
+
"target_username",
|
|
25
|
+
"target_password",
|
|
26
|
+
"target_address",
|
|
27
|
+
"target_port",
|
|
28
|
+
"target_new_password"
|
|
29
|
+
)
|
|
30
|
+
def __init__(
|
|
31
|
+
self: str,
|
|
32
|
+
username: str,
|
|
33
|
+
password: str,
|
|
34
|
+
address: str,
|
|
35
|
+
port: str,
|
|
36
|
+
new_password: str
|
|
37
|
+
) -> None:
|
|
38
|
+
super().__init__(
|
|
39
|
+
username,
|
|
40
|
+
password
|
|
41
|
+
)
|
|
42
|
+
self._address = address
|
|
43
|
+
self._port = port
|
|
44
|
+
self._new_password = Secret(new_password)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def address(self) -> str:
|
|
48
|
+
return self._address
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def port(self) -> str:
|
|
52
|
+
return self._port
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def new_password(self) -> Secret:
|
|
56
|
+
return self._new_password
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class LogonAccount(BaseAccount):
|
|
60
|
+
ENV_VARS = (
|
|
61
|
+
"logon_username",
|
|
62
|
+
"logon_password"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ReconcileAccount(BaseAccount):
|
|
67
|
+
ENV_VARS = (
|
|
68
|
+
"reconcile_username",
|
|
69
|
+
"reconcile_password"
|
|
70
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class Args:
|
|
2
|
+
ARGS = (
|
|
3
|
+
"action",
|
|
4
|
+
"logging_level"
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
def __init__(
|
|
8
|
+
self: str,
|
|
9
|
+
action: str,
|
|
10
|
+
logging_level: str
|
|
11
|
+
) -> None:
|
|
12
|
+
self._action = action
|
|
13
|
+
self._logging_level = logging_level
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def action(self) -> str:
|
|
17
|
+
return self._action
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def logging_level(self) -> str:
|
|
21
|
+
return self._logging_level
|
|
@@ -5,21 +5,21 @@ from logging.handlers import RotatingFileHandler
|
|
|
5
5
|
|
|
6
6
|
class Logger:
|
|
7
7
|
_LOGS_DIR = os.path.join("Logs", "ThirdParty")
|
|
8
|
-
_LOGGING_ENABLED_VALUE = "yes"
|
|
9
8
|
_LOGGING_LEVELS = {
|
|
9
|
+
"critical": logging.CRITICAL,
|
|
10
|
+
"error": logging.ERROR,
|
|
11
|
+
"warning": logging.WARNING,
|
|
10
12
|
"info": logging.INFO,
|
|
11
13
|
"debug": logging.DEBUG
|
|
12
14
|
}
|
|
15
|
+
_DEFAULT_LEVEL = _LOGGING_LEVELS["error"]
|
|
13
16
|
|
|
14
17
|
@classmethod
|
|
15
18
|
def get_logger(
|
|
16
19
|
cls,
|
|
17
20
|
name: str,
|
|
18
|
-
args_logging: str,
|
|
19
21
|
args_logging_level: str
|
|
20
22
|
) -> logging.Logger:
|
|
21
|
-
if args_logging.lower() != cls._LOGGING_ENABLED_VALUE:
|
|
22
|
-
return None
|
|
23
23
|
os.makedirs(cls._LOGS_DIR, exist_ok=True)
|
|
24
24
|
logs_file = os.path.join(cls._LOGS_DIR, f"{__name__}-{name}.log")
|
|
25
25
|
_id = os.urandom(4).hex()
|
|
@@ -27,7 +27,7 @@ class Logger:
|
|
|
27
27
|
if args_logging_level.lower() in cls._LOGGING_LEVELS:
|
|
28
28
|
logger.setLevel(cls._LOGGING_LEVELS[args_logging_level.lower()])
|
|
29
29
|
else:
|
|
30
|
-
logger.setLevel(cls.
|
|
30
|
+
logger.setLevel(cls._DEFAULT_LEVEL)
|
|
31
31
|
handler = RotatingFileHandler(
|
|
32
32
|
filename=logs_file,
|
|
33
33
|
maxBytes=1024 ** 2,
|