python4cpm 1.1.2__tar.gz → 1.1.3__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.2/src/python4cpm.egg-info → python4cpm-1.1.3}/PKG-INFO +16 -48
- {python4cpm-1.1.2 → python4cpm-1.1.3}/README.md +15 -47
- {python4cpm-1.1.2 → python4cpm-1.1.3}/pyproject.toml +1 -1
- {python4cpm-1.1.2 → python4cpm-1.1.3}/src/python4cpm/accounts.py +18 -11
- {python4cpm-1.1.2 → python4cpm-1.1.3}/src/python4cpm/args.py +3 -3
- {python4cpm-1.1.2 → python4cpm-1.1.3}/src/python4cpm/envhandler.py +8 -5
- {python4cpm-1.1.2 → python4cpm-1.1.3}/src/python4cpm/logger.py +3 -2
- {python4cpm-1.1.2 → python4cpm-1.1.3}/src/python4cpm/nethelper.py +13 -12
- {python4cpm-1.1.2 → python4cpm-1.1.3}/src/python4cpm/python4cpm.py +11 -14
- {python4cpm-1.1.2 → python4cpm-1.1.3}/src/python4cpm/python4cpmhandler.py +1 -1
- python4cpm-1.1.3/src/python4cpm/secret.py +24 -0
- {python4cpm-1.1.2 → python4cpm-1.1.3/src/python4cpm.egg-info}/PKG-INFO +16 -48
- python4cpm-1.1.2/src/python4cpm/secret.py +0 -15
- {python4cpm-1.1.2 → python4cpm-1.1.3}/LICENSE +0 -0
- {python4cpm-1.1.2 → python4cpm-1.1.3}/setup.cfg +0 -0
- {python4cpm-1.1.2 → python4cpm-1.1.3}/src/python4cpm/__init__.py +0 -0
- {python4cpm-1.1.2 → python4cpm-1.1.3}/src/python4cpm/crypto.py +0 -0
- {python4cpm-1.1.2 → python4cpm-1.1.3}/src/python4cpm.egg-info/SOURCES.txt +0 -0
- {python4cpm-1.1.2 → python4cpm-1.1.3}/src/python4cpm.egg-info/dependency_links.txt +0 -0
- {python4cpm-1.1.2 → python4cpm-1.1.3}/src/python4cpm.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python4cpm
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.3
|
|
4
4
|
Summary: Python for CPM
|
|
5
5
|
Author-email: Gonzalo Atienza Rela <gonatienza@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -67,7 +67,7 @@ This platform allows you to duplicate it multiple times, simply changing its set
|
|
|
67
67
|
from python4cpm import Python4CPMHandler
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
class MyRotator(Python4CPMHandler):
|
|
70
|
+
class MyRotator(Python4CPMHandler):
|
|
71
71
|
"""
|
|
72
72
|
These are the usable properties and methods from Python4CPMHandler:
|
|
73
73
|
|
|
@@ -123,64 +123,33 @@ class MyRotator(Python4CPMHandler): # create a subclass for the Handler
|
|
|
123
123
|
When calling a signal sys.exit is invoked and the script is terminated.
|
|
124
124
|
If no signal is called, and the script finishes without any exception,
|
|
125
125
|
it will behave like p4cpm.close_fail(unrecoverable=True) and log an error message.
|
|
126
|
+
|
|
126
127
|
=============================
|
|
128
|
+
REQUIRED METHODS
|
|
127
129
|
=============================
|
|
130
|
+
verify(), logon(), change(), prereconcile(), reconcile()
|
|
128
131
|
"""
|
|
129
132
|
|
|
130
|
-
# =============================
|
|
131
|
-
# REQUIRED METHODS (MUST DEFINE)
|
|
132
|
-
# =============================
|
|
133
|
-
# verify(), logon(), change(), prereconcile(), reconcile()
|
|
134
|
-
|
|
135
133
|
def verify(self):
|
|
136
|
-
|
|
137
|
-
self.log_info("verification successful")
|
|
134
|
+
# TODO: use account objects for your logic
|
|
138
135
|
self.close_success()
|
|
139
136
|
|
|
140
137
|
def logon(self):
|
|
138
|
+
# TODO: use account objects for your logic
|
|
141
139
|
self.close_success()
|
|
142
140
|
|
|
143
141
|
def change(self):
|
|
144
|
-
|
|
145
|
-
self.
|
|
146
|
-
self.close_fail()
|
|
142
|
+
# TODO: use account objects for your logic
|
|
143
|
+
self.close_success()
|
|
147
144
|
|
|
148
145
|
def prereconcile(self):
|
|
149
|
-
|
|
146
|
+
# TODO: use account objects for your logic
|
|
150
147
|
self.close_success()
|
|
151
148
|
|
|
152
149
|
def reconcile(self):
|
|
153
|
-
|
|
150
|
+
# TODO: use account objects for your logic
|
|
154
151
|
self.close_success()
|
|
155
152
|
|
|
156
|
-
def _verify(self, from_reconcile=False):
|
|
157
|
-
if from_reconcile is False:
|
|
158
|
-
pass
|
|
159
|
-
# TODO: use account objects for your logic
|
|
160
|
-
else:
|
|
161
|
-
pass
|
|
162
|
-
# TODO: use account objects for your logic
|
|
163
|
-
result = True
|
|
164
|
-
if result is True:
|
|
165
|
-
self.log_info("verification successful")
|
|
166
|
-
else:
|
|
167
|
-
self.log_error("something went wrong")
|
|
168
|
-
self.close_fail()
|
|
169
|
-
|
|
170
|
-
def _change(self, from_reconcile=False):
|
|
171
|
-
if from_reconcile is False:
|
|
172
|
-
pass
|
|
173
|
-
# TODO: use account objects for your logic
|
|
174
|
-
else:
|
|
175
|
-
pass
|
|
176
|
-
# TODO: use account objects for your logic
|
|
177
|
-
result = True
|
|
178
|
-
if result is True:
|
|
179
|
-
self.log_info("rotation successful")
|
|
180
|
-
else:
|
|
181
|
-
self.log_error("something went wrong")
|
|
182
|
-
self.close_fail()
|
|
183
|
-
|
|
184
153
|
|
|
185
154
|
if __name__ == "__main__":
|
|
186
155
|
MyRotator().run() # initializes the class and calls the action that was requested from CPM/SRS.
|
|
@@ -193,9 +162,9 @@ When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
|
|
|
193
162
|
- 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)`.
|
|
194
163
|
3. Reconcile -> the sciprt will be executed twice, running first the `MyRotator.prereconcile()` method and secondly the `MyRotator.reconcile()` method.
|
|
195
164
|
- 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)`.
|
|
196
|
-
4. When calling `MyRotator.verify()`, `MyRotator.logon()` or `MyRotator.prereconcile()`: `self.target_account.new_password
|
|
197
|
-
5. If a logon account is not linked, `self.logon_account
|
|
198
|
-
6. If a reconcile account is not linked, `self.reconcile_account
|
|
165
|
+
4. When calling `MyRotator.verify()`, `MyRotator.logon()` or `MyRotator.prereconcile()`: `self.target_account.new_password` will always return `None`.
|
|
166
|
+
5. If a logon account is not linked, `self.logon_account` will return `None`.
|
|
167
|
+
6. If a reconcile account is not linked, `self.reconcile_account` will return `None`.
|
|
199
168
|
7. The python `Logger` places its logs in the `Logs/ThirdParty` directory. The filename will be based on the name of the subclass created (e.g., `MyRotator`).
|
|
200
169
|
|
|
201
170
|
|
|
@@ -210,7 +179,7 @@ As with any python venv, you can install dependencies in your venv.
|
|
|
210
179
|
|
|
211
180
|
## Dev Helper:
|
|
212
181
|
|
|
213
|
-
For dev purposes, `NETHelper` is a companion helper to test your scripts
|
|
182
|
+
For dev purposes, `NETHelper` is a companion helper to test your scripts without CPM/SRS. It simplifies the instantiation of the `Python4CPM` or `Python4CPMHandler` objects by simulating how the plugin creates the environment context for the python module.
|
|
214
183
|
|
|
215
184
|
**Note**: As CPM and the SRS management agent run in Windows, the plugin was built to encrypt secrets using DPAPI (a windows only library). For dev purposes in Linux/Mac dev workstations, those secrets put in the environment context by `NETHelper` will be in plaintext. In windows dev workstations, `NETHelper` encrypts the secrets as the .NET plugin does. This is informational only, **the module will use its encryption/decryption capabilities automatically based on the platform** it is running on and you do not have to do anything specific to enable it.
|
|
216
185
|
|
|
@@ -223,7 +192,7 @@ from python4cpm import NETHelper, Python4CPM, Python4CPMHandler
|
|
|
223
192
|
from getpass import getpass
|
|
224
193
|
|
|
225
194
|
# Get secrets for your password, logon account password, reconcile account password and new password
|
|
226
|
-
# You
|
|
195
|
+
# You may set to None any argument that does not apply or simply leaving it to its default None value.
|
|
227
196
|
target_password = getpass("password: ") # password from account
|
|
228
197
|
logon_password = getpass("logon_password: ") # password from linked logon account
|
|
229
198
|
reconcile_password = getpass("reconcile_password: ") # password from linked reconcile account
|
|
@@ -271,5 +240,4 @@ MyRotator().run()
|
|
|
271
240
|
|
|
272
241
|
- Remove the import of `NETHelper`.
|
|
273
242
|
- Remove the `NETHelper.set()` call.
|
|
274
|
-
- If applicable, change the definition of `p4cpm` from `p4cpm = NETHelper.get()` to `p4cpm = Python4CPM("MyApp")`.
|
|
275
243
|
- Remove any secrets prompting or interactive interruptions.
|
|
@@ -56,7 +56,7 @@ This platform allows you to duplicate it multiple times, simply changing its set
|
|
|
56
56
|
from python4cpm import Python4CPMHandler
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
class MyRotator(Python4CPMHandler):
|
|
59
|
+
class MyRotator(Python4CPMHandler):
|
|
60
60
|
"""
|
|
61
61
|
These are the usable properties and methods from Python4CPMHandler:
|
|
62
62
|
|
|
@@ -112,64 +112,33 @@ class MyRotator(Python4CPMHandler): # create a subclass for the Handler
|
|
|
112
112
|
When calling a signal sys.exit is invoked and the script is terminated.
|
|
113
113
|
If no signal is called, and the script finishes without any exception,
|
|
114
114
|
it will behave like p4cpm.close_fail(unrecoverable=True) and log an error message.
|
|
115
|
+
|
|
115
116
|
=============================
|
|
117
|
+
REQUIRED METHODS
|
|
116
118
|
=============================
|
|
119
|
+
verify(), logon(), change(), prereconcile(), reconcile()
|
|
117
120
|
"""
|
|
118
121
|
|
|
119
|
-
# =============================
|
|
120
|
-
# REQUIRED METHODS (MUST DEFINE)
|
|
121
|
-
# =============================
|
|
122
|
-
# verify(), logon(), change(), prereconcile(), reconcile()
|
|
123
|
-
|
|
124
122
|
def verify(self):
|
|
125
|
-
|
|
126
|
-
self.log_info("verification successful")
|
|
123
|
+
# TODO: use account objects for your logic
|
|
127
124
|
self.close_success()
|
|
128
125
|
|
|
129
126
|
def logon(self):
|
|
127
|
+
# TODO: use account objects for your logic
|
|
130
128
|
self.close_success()
|
|
131
129
|
|
|
132
130
|
def change(self):
|
|
133
|
-
|
|
134
|
-
self.
|
|
135
|
-
self.close_fail()
|
|
131
|
+
# TODO: use account objects for your logic
|
|
132
|
+
self.close_success()
|
|
136
133
|
|
|
137
134
|
def prereconcile(self):
|
|
138
|
-
|
|
135
|
+
# TODO: use account objects for your logic
|
|
139
136
|
self.close_success()
|
|
140
137
|
|
|
141
138
|
def reconcile(self):
|
|
142
|
-
|
|
139
|
+
# TODO: use account objects for your logic
|
|
143
140
|
self.close_success()
|
|
144
141
|
|
|
145
|
-
def _verify(self, from_reconcile=False):
|
|
146
|
-
if from_reconcile is False:
|
|
147
|
-
pass
|
|
148
|
-
# TODO: use account objects for your logic
|
|
149
|
-
else:
|
|
150
|
-
pass
|
|
151
|
-
# TODO: use account objects for your logic
|
|
152
|
-
result = True
|
|
153
|
-
if result is True:
|
|
154
|
-
self.log_info("verification successful")
|
|
155
|
-
else:
|
|
156
|
-
self.log_error("something went wrong")
|
|
157
|
-
self.close_fail()
|
|
158
|
-
|
|
159
|
-
def _change(self, from_reconcile=False):
|
|
160
|
-
if from_reconcile is False:
|
|
161
|
-
pass
|
|
162
|
-
# TODO: use account objects for your logic
|
|
163
|
-
else:
|
|
164
|
-
pass
|
|
165
|
-
# TODO: use account objects for your logic
|
|
166
|
-
result = True
|
|
167
|
-
if result is True:
|
|
168
|
-
self.log_info("rotation successful")
|
|
169
|
-
else:
|
|
170
|
-
self.log_error("something went wrong")
|
|
171
|
-
self.close_fail()
|
|
172
|
-
|
|
173
142
|
|
|
174
143
|
if __name__ == "__main__":
|
|
175
144
|
MyRotator().run() # initializes the class and calls the action that was requested from CPM/SRS.
|
|
@@ -182,9 +151,9 @@ When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
|
|
|
182
151
|
- 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)`.
|
|
183
152
|
3. Reconcile -> the sciprt will be executed twice, running first the `MyRotator.prereconcile()` method and secondly the `MyRotator.reconcile()` method.
|
|
184
153
|
- 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)`.
|
|
185
|
-
4. When calling `MyRotator.verify()`, `MyRotator.logon()` or `MyRotator.prereconcile()`: `self.target_account.new_password
|
|
186
|
-
5. If a logon account is not linked, `self.logon_account
|
|
187
|
-
6. If a reconcile account is not linked, `self.reconcile_account
|
|
154
|
+
4. When calling `MyRotator.verify()`, `MyRotator.logon()` or `MyRotator.prereconcile()`: `self.target_account.new_password` will always return `None`.
|
|
155
|
+
5. If a logon account is not linked, `self.logon_account` will return `None`.
|
|
156
|
+
6. If a reconcile account is not linked, `self.reconcile_account` will return `None`.
|
|
188
157
|
7. The python `Logger` places its logs in the `Logs/ThirdParty` directory. The filename will be based on the name of the subclass created (e.g., `MyRotator`).
|
|
189
158
|
|
|
190
159
|
|
|
@@ -199,7 +168,7 @@ As with any python venv, you can install dependencies in your venv.
|
|
|
199
168
|
|
|
200
169
|
## Dev Helper:
|
|
201
170
|
|
|
202
|
-
For dev purposes, `NETHelper` is a companion helper to test your scripts
|
|
171
|
+
For dev purposes, `NETHelper` is a companion helper to test your scripts without CPM/SRS. It simplifies the instantiation of the `Python4CPM` or `Python4CPMHandler` objects by simulating how the plugin creates the environment context for the python module.
|
|
203
172
|
|
|
204
173
|
**Note**: As CPM and the SRS management agent run in Windows, the plugin was built to encrypt secrets using DPAPI (a windows only library). For dev purposes in Linux/Mac dev workstations, those secrets put in the environment context by `NETHelper` will be in plaintext. In windows dev workstations, `NETHelper` encrypts the secrets as the .NET plugin does. This is informational only, **the module will use its encryption/decryption capabilities automatically based on the platform** it is running on and you do not have to do anything specific to enable it.
|
|
205
174
|
|
|
@@ -212,7 +181,7 @@ from python4cpm import NETHelper, Python4CPM, Python4CPMHandler
|
|
|
212
181
|
from getpass import getpass
|
|
213
182
|
|
|
214
183
|
# Get secrets for your password, logon account password, reconcile account password and new password
|
|
215
|
-
# You
|
|
184
|
+
# You may set to None any argument that does not apply or simply leaving it to its default None value.
|
|
216
185
|
target_password = getpass("password: ") # password from account
|
|
217
186
|
logon_password = getpass("logon_password: ") # password from linked logon account
|
|
218
187
|
reconcile_password = getpass("reconcile_password: ") # password from linked reconcile account
|
|
@@ -260,5 +229,4 @@ MyRotator().run()
|
|
|
260
229
|
|
|
261
230
|
- Remove the import of `NETHelper`.
|
|
262
231
|
- Remove the `NETHelper.set()` call.
|
|
263
|
-
- If applicable, change the definition of `p4cpm` from `p4cpm = NETHelper.get()` to `p4cpm = Python4CPM("MyApp")`.
|
|
264
232
|
- Remove any secrets prompting or interactive interruptions.
|
|
@@ -7,11 +7,18 @@ class BaseAccount(EnvHandler):
|
|
|
7
7
|
|
|
8
8
|
def __init__(
|
|
9
9
|
self,
|
|
10
|
-
username: str,
|
|
11
|
-
password: str
|
|
10
|
+
username: str | None,
|
|
11
|
+
password: str | None
|
|
12
12
|
) -> None:
|
|
13
13
|
self._username = username
|
|
14
|
-
self._password = Secret(password)
|
|
14
|
+
self._password = Secret.from_env_var(password)
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def get(cls) -> object | None:
|
|
18
|
+
kwargs = cls.get_kwargs()
|
|
19
|
+
if all(value is None for value in kwargs.values()):
|
|
20
|
+
return None
|
|
21
|
+
return cls(**kwargs)
|
|
15
22
|
|
|
16
23
|
@property
|
|
17
24
|
def username(self) -> str:
|
|
@@ -28,11 +35,11 @@ class TargetAccount(BaseAccount):
|
|
|
28
35
|
|
|
29
36
|
def __init__(
|
|
30
37
|
self,
|
|
31
|
-
username: str,
|
|
32
|
-
password: str,
|
|
33
|
-
address: str,
|
|
34
|
-
port: str,
|
|
35
|
-
new_password: str
|
|
38
|
+
username: str | None,
|
|
39
|
+
password: str | None,
|
|
40
|
+
address: str | None,
|
|
41
|
+
port: str | None,
|
|
42
|
+
new_password: str | None
|
|
36
43
|
) -> None:
|
|
37
44
|
super().__init__(
|
|
38
45
|
username,
|
|
@@ -40,14 +47,14 @@ class TargetAccount(BaseAccount):
|
|
|
40
47
|
)
|
|
41
48
|
self._address = address
|
|
42
49
|
self._port = port
|
|
43
|
-
self._new_password = Secret(new_password)
|
|
50
|
+
self._new_password = Secret.from_env_var(new_password)
|
|
44
51
|
|
|
45
52
|
@property
|
|
46
|
-
def address(self) -> str:
|
|
53
|
+
def address(self) -> str | None:
|
|
47
54
|
return self._address
|
|
48
55
|
|
|
49
56
|
@property
|
|
50
|
-
def port(self) -> str:
|
|
57
|
+
def port(self) -> str | None:
|
|
51
58
|
return self._port
|
|
52
59
|
|
|
53
60
|
@property
|
|
@@ -7,8 +7,8 @@ class Args(EnvHandler):
|
|
|
7
7
|
|
|
8
8
|
def __init__(
|
|
9
9
|
self,
|
|
10
|
-
action: str,
|
|
11
|
-
logging_level: str
|
|
10
|
+
action: str | None,
|
|
11
|
+
logging_level: str | None
|
|
12
12
|
) -> None:
|
|
13
13
|
self._action = action
|
|
14
14
|
self._logging_level = logging_level
|
|
@@ -18,5 +18,5 @@ class Args(EnvHandler):
|
|
|
18
18
|
return self._action
|
|
19
19
|
|
|
20
20
|
@property
|
|
21
|
-
def logging_level(self) -> str:
|
|
21
|
+
def logging_level(self) -> str | None:
|
|
22
22
|
return self._logging_level
|
|
@@ -20,10 +20,13 @@ class EnvHandler:
|
|
|
20
20
|
env_key = f"{cls.PREFIX}{cls.OBJ_PREFIX}{key}"
|
|
21
21
|
return env_key.upper()
|
|
22
22
|
|
|
23
|
+
@classmethod
|
|
24
|
+
def get_kwargs(cls) -> dict:
|
|
25
|
+
return {
|
|
26
|
+
prop: os.environ.get(cls.get_key(prop))
|
|
27
|
+
for prop in cls.PROPS
|
|
28
|
+
}
|
|
29
|
+
|
|
23
30
|
@classmethod
|
|
24
31
|
def get(cls) -> object:
|
|
25
|
-
|
|
26
|
-
for prop in cls.PROPS:
|
|
27
|
-
value = os.environ.get(cls.get_key(prop))
|
|
28
|
-
kwargs[prop] = value if value is not None else ""
|
|
29
|
-
return cls(**kwargs)
|
|
32
|
+
return cls(**cls.get_kwargs())
|
|
@@ -18,13 +18,14 @@ class Logger:
|
|
|
18
18
|
def get_logger(
|
|
19
19
|
cls,
|
|
20
20
|
name: str,
|
|
21
|
-
logging_level: str
|
|
21
|
+
logging_level: str | None
|
|
22
22
|
) -> logging.Logger:
|
|
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()
|
|
26
26
|
logger = logging.getLogger(_id)
|
|
27
|
-
|
|
27
|
+
is_logging_level_str = isinstance(logging_level, str)
|
|
28
|
+
if is_logging_level_str and logging_level.lower() in cls._LOGGING_LEVELS:
|
|
28
29
|
logger.setLevel(cls._LOGGING_LEVELS[logging_level.lower()])
|
|
29
30
|
else:
|
|
30
31
|
logger.setLevel(cls._DEFAULT_LEVEL)
|
|
@@ -9,17 +9,17 @@ class NETHelper:
|
|
|
9
9
|
@classmethod
|
|
10
10
|
def set(
|
|
11
11
|
cls,
|
|
12
|
-
action: str =
|
|
13
|
-
logging_level: str =
|
|
14
|
-
target_username: str =
|
|
15
|
-
target_address: str =
|
|
16
|
-
target_port: str =
|
|
17
|
-
logon_username: str =
|
|
18
|
-
reconcile_username: str =
|
|
19
|
-
target_password: str =
|
|
20
|
-
logon_password: str =
|
|
21
|
-
reconcile_password: str =
|
|
22
|
-
target_new_password: str =
|
|
12
|
+
action: str | None = None,
|
|
13
|
+
logging_level: str | None = None,
|
|
14
|
+
target_username: str | None = None,
|
|
15
|
+
target_address: str | None = None,
|
|
16
|
+
target_port: str | None = None,
|
|
17
|
+
logon_username: str | None = None,
|
|
18
|
+
reconcile_username: str | None = None,
|
|
19
|
+
target_password: str | None = None,
|
|
20
|
+
logon_password: str | None = None,
|
|
21
|
+
reconcile_password: str | None = None,
|
|
22
|
+
target_new_password: str | None = None
|
|
23
23
|
) -> None:
|
|
24
24
|
if Crypto.ENABLED:
|
|
25
25
|
target_password = Crypto.encrypt(target_password)
|
|
@@ -53,7 +53,8 @@ class NETHelper:
|
|
|
53
53
|
target_new_password
|
|
54
54
|
)
|
|
55
55
|
for i, key in enumerate(keys):
|
|
56
|
-
|
|
56
|
+
if values[i] is not None:
|
|
57
|
+
os.environ.update({key: values[i]})
|
|
57
58
|
|
|
58
59
|
@classmethod
|
|
59
60
|
def get(cls) -> Python4CPM:
|
|
@@ -3,7 +3,6 @@ import atexit
|
|
|
3
3
|
import logging
|
|
4
4
|
from python4cpm.secret import Secret
|
|
5
5
|
from python4cpm.args import Args
|
|
6
|
-
from python4cpm.crypto import Crypto
|
|
7
6
|
from python4cpm.logger import Logger
|
|
8
7
|
from python4cpm.accounts import TargetAccount, LogonAccount, ReconcileAccount
|
|
9
8
|
|
|
@@ -65,20 +64,18 @@ class Python4CPM:
|
|
|
65
64
|
if self._args.action not in self._VALID_ACTIONS:
|
|
66
65
|
self._logger.warning(f"Unkonwn action -> '{self._args.action}'")
|
|
67
66
|
|
|
68
|
-
def _log_obj(self, obj: object) -> None:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if not
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if Crypto.ENABLED is True:
|
|
76
|
-
logging_value = "[ENCRYPTED]"
|
|
67
|
+
def _log_obj(self, obj: object | None) -> None:
|
|
68
|
+
if obj is not None:
|
|
69
|
+
for key, value in vars(obj).items():
|
|
70
|
+
_key = f"{obj.__class__.__name__}.{key.removeprefix('_')}"
|
|
71
|
+
if value is not None:
|
|
72
|
+
if not isinstance(value, Secret):
|
|
73
|
+
logging_value = f"'{value}'"
|
|
77
74
|
else:
|
|
78
|
-
logging_value =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
logging_value = str(value)
|
|
76
|
+
else:
|
|
77
|
+
logging_value = "[NOT SET]"
|
|
78
|
+
self._logger.debug(f"{_key} -> {logging_value}")
|
|
82
79
|
|
|
83
80
|
def close_fail(self, unrecoverable: bool = False) -> None:
|
|
84
81
|
if unrecoverable is False:
|
|
@@ -18,7 +18,7 @@ class Python4CPMHandler(ABC, Python4CPM):
|
|
|
18
18
|
if action is not None:
|
|
19
19
|
action()
|
|
20
20
|
else:
|
|
21
|
-
raise ValueError(f"Unknown action: {self._args.action}")
|
|
21
|
+
raise ValueError(f"Unknown action: '{self._args.action}'")
|
|
22
22
|
|
|
23
23
|
@abstractmethod
|
|
24
24
|
def verify(self) -> None:
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from python4cpm.crypto import Crypto
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Secret:
|
|
5
|
+
def __init__(self, secret: str) -> None:
|
|
6
|
+
self._secret = secret
|
|
7
|
+
|
|
8
|
+
@classmethod
|
|
9
|
+
def from_env_var(cls, secret: str | None) -> object | None:
|
|
10
|
+
if secret is not None:
|
|
11
|
+
return cls(secret)
|
|
12
|
+
return None
|
|
13
|
+
|
|
14
|
+
def __str__(self) -> str:
|
|
15
|
+
if Crypto.ENABLED:
|
|
16
|
+
return "[ENCRYPTED]"
|
|
17
|
+
else:
|
|
18
|
+
return "[SET]"
|
|
19
|
+
|
|
20
|
+
def get(self) -> str:
|
|
21
|
+
if Crypto.ENABLED:
|
|
22
|
+
return Crypto.decrypt(self._secret)
|
|
23
|
+
else:
|
|
24
|
+
return self._secret
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python4cpm
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.3
|
|
4
4
|
Summary: Python for CPM
|
|
5
5
|
Author-email: Gonzalo Atienza Rela <gonatienza@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -67,7 +67,7 @@ This platform allows you to duplicate it multiple times, simply changing its set
|
|
|
67
67
|
from python4cpm import Python4CPMHandler
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
class MyRotator(Python4CPMHandler):
|
|
70
|
+
class MyRotator(Python4CPMHandler):
|
|
71
71
|
"""
|
|
72
72
|
These are the usable properties and methods from Python4CPMHandler:
|
|
73
73
|
|
|
@@ -123,64 +123,33 @@ class MyRotator(Python4CPMHandler): # create a subclass for the Handler
|
|
|
123
123
|
When calling a signal sys.exit is invoked and the script is terminated.
|
|
124
124
|
If no signal is called, and the script finishes without any exception,
|
|
125
125
|
it will behave like p4cpm.close_fail(unrecoverable=True) and log an error message.
|
|
126
|
+
|
|
126
127
|
=============================
|
|
128
|
+
REQUIRED METHODS
|
|
127
129
|
=============================
|
|
130
|
+
verify(), logon(), change(), prereconcile(), reconcile()
|
|
128
131
|
"""
|
|
129
132
|
|
|
130
|
-
# =============================
|
|
131
|
-
# REQUIRED METHODS (MUST DEFINE)
|
|
132
|
-
# =============================
|
|
133
|
-
# verify(), logon(), change(), prereconcile(), reconcile()
|
|
134
|
-
|
|
135
133
|
def verify(self):
|
|
136
|
-
|
|
137
|
-
self.log_info("verification successful")
|
|
134
|
+
# TODO: use account objects for your logic
|
|
138
135
|
self.close_success()
|
|
139
136
|
|
|
140
137
|
def logon(self):
|
|
138
|
+
# TODO: use account objects for your logic
|
|
141
139
|
self.close_success()
|
|
142
140
|
|
|
143
141
|
def change(self):
|
|
144
|
-
|
|
145
|
-
self.
|
|
146
|
-
self.close_fail()
|
|
142
|
+
# TODO: use account objects for your logic
|
|
143
|
+
self.close_success()
|
|
147
144
|
|
|
148
145
|
def prereconcile(self):
|
|
149
|
-
|
|
146
|
+
# TODO: use account objects for your logic
|
|
150
147
|
self.close_success()
|
|
151
148
|
|
|
152
149
|
def reconcile(self):
|
|
153
|
-
|
|
150
|
+
# TODO: use account objects for your logic
|
|
154
151
|
self.close_success()
|
|
155
152
|
|
|
156
|
-
def _verify(self, from_reconcile=False):
|
|
157
|
-
if from_reconcile is False:
|
|
158
|
-
pass
|
|
159
|
-
# TODO: use account objects for your logic
|
|
160
|
-
else:
|
|
161
|
-
pass
|
|
162
|
-
# TODO: use account objects for your logic
|
|
163
|
-
result = True
|
|
164
|
-
if result is True:
|
|
165
|
-
self.log_info("verification successful")
|
|
166
|
-
else:
|
|
167
|
-
self.log_error("something went wrong")
|
|
168
|
-
self.close_fail()
|
|
169
|
-
|
|
170
|
-
def _change(self, from_reconcile=False):
|
|
171
|
-
if from_reconcile is False:
|
|
172
|
-
pass
|
|
173
|
-
# TODO: use account objects for your logic
|
|
174
|
-
else:
|
|
175
|
-
pass
|
|
176
|
-
# TODO: use account objects for your logic
|
|
177
|
-
result = True
|
|
178
|
-
if result is True:
|
|
179
|
-
self.log_info("rotation successful")
|
|
180
|
-
else:
|
|
181
|
-
self.log_error("something went wrong")
|
|
182
|
-
self.close_fail()
|
|
183
|
-
|
|
184
153
|
|
|
185
154
|
if __name__ == "__main__":
|
|
186
155
|
MyRotator().run() # initializes the class and calls the action that was requested from CPM/SRS.
|
|
@@ -193,9 +162,9 @@ When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
|
|
|
193
162
|
- 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)`.
|
|
194
163
|
3. Reconcile -> the sciprt will be executed twice, running first the `MyRotator.prereconcile()` method and secondly the `MyRotator.reconcile()` method.
|
|
195
164
|
- 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)`.
|
|
196
|
-
4. When calling `MyRotator.verify()`, `MyRotator.logon()` or `MyRotator.prereconcile()`: `self.target_account.new_password
|
|
197
|
-
5. If a logon account is not linked, `self.logon_account
|
|
198
|
-
6. If a reconcile account is not linked, `self.reconcile_account
|
|
165
|
+
4. When calling `MyRotator.verify()`, `MyRotator.logon()` or `MyRotator.prereconcile()`: `self.target_account.new_password` will always return `None`.
|
|
166
|
+
5. If a logon account is not linked, `self.logon_account` will return `None`.
|
|
167
|
+
6. If a reconcile account is not linked, `self.reconcile_account` will return `None`.
|
|
199
168
|
7. The python `Logger` places its logs in the `Logs/ThirdParty` directory. The filename will be based on the name of the subclass created (e.g., `MyRotator`).
|
|
200
169
|
|
|
201
170
|
|
|
@@ -210,7 +179,7 @@ As with any python venv, you can install dependencies in your venv.
|
|
|
210
179
|
|
|
211
180
|
## Dev Helper:
|
|
212
181
|
|
|
213
|
-
For dev purposes, `NETHelper` is a companion helper to test your scripts
|
|
182
|
+
For dev purposes, `NETHelper` is a companion helper to test your scripts without CPM/SRS. It simplifies the instantiation of the `Python4CPM` or `Python4CPMHandler` objects by simulating how the plugin creates the environment context for the python module.
|
|
214
183
|
|
|
215
184
|
**Note**: As CPM and the SRS management agent run in Windows, the plugin was built to encrypt secrets using DPAPI (a windows only library). For dev purposes in Linux/Mac dev workstations, those secrets put in the environment context by `NETHelper` will be in plaintext. In windows dev workstations, `NETHelper` encrypts the secrets as the .NET plugin does. This is informational only, **the module will use its encryption/decryption capabilities automatically based on the platform** it is running on and you do not have to do anything specific to enable it.
|
|
216
185
|
|
|
@@ -223,7 +192,7 @@ from python4cpm import NETHelper, Python4CPM, Python4CPMHandler
|
|
|
223
192
|
from getpass import getpass
|
|
224
193
|
|
|
225
194
|
# Get secrets for your password, logon account password, reconcile account password and new password
|
|
226
|
-
# You
|
|
195
|
+
# You may set to None any argument that does not apply or simply leaving it to its default None value.
|
|
227
196
|
target_password = getpass("password: ") # password from account
|
|
228
197
|
logon_password = getpass("logon_password: ") # password from linked logon account
|
|
229
198
|
reconcile_password = getpass("reconcile_password: ") # password from linked reconcile account
|
|
@@ -271,5 +240,4 @@ MyRotator().run()
|
|
|
271
240
|
|
|
272
241
|
- Remove the import of `NETHelper`.
|
|
273
242
|
- Remove the `NETHelper.set()` call.
|
|
274
|
-
- If applicable, change the definition of `p4cpm` from `p4cpm = NETHelper.get()` to `p4cpm = Python4CPM("MyApp")`.
|
|
275
243
|
- Remove any secrets prompting or interactive interruptions.
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
from python4cpm.crypto import Crypto
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class Secret:
|
|
5
|
-
def __init__(self, secret: str) -> None:
|
|
6
|
-
self._secret = secret
|
|
7
|
-
|
|
8
|
-
def __bool__(self) -> bool:
|
|
9
|
-
return bool(self._secret)
|
|
10
|
-
|
|
11
|
-
def get(self) -> str:
|
|
12
|
-
if Crypto.ENABLED and self._secret:
|
|
13
|
-
return Crypto.decrypt(self._secret)
|
|
14
|
-
else:
|
|
15
|
-
return self._secret
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|