python4cpm 1.1.1__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.
Files changed (23) hide show
  1. {python4cpm-1.1.1/src/python4cpm.egg-info → python4cpm-1.1.3}/PKG-INFO +39 -71
  2. {python4cpm-1.1.1 → python4cpm-1.1.3}/README.md +38 -70
  3. {python4cpm-1.1.1 → python4cpm-1.1.3}/pyproject.toml +1 -1
  4. python4cpm-1.1.3/src/python4cpm/accounts.py +70 -0
  5. python4cpm-1.1.3/src/python4cpm/args.py +22 -0
  6. python4cpm-1.1.3/src/python4cpm/envhandler.py +32 -0
  7. {python4cpm-1.1.1 → python4cpm-1.1.3}/src/python4cpm/logger.py +4 -3
  8. python4cpm-1.1.3/src/python4cpm/nethelper.py +61 -0
  9. {python4cpm-1.1.1 → python4cpm-1.1.3}/src/python4cpm/python4cpm.py +17 -45
  10. {python4cpm-1.1.1 → python4cpm-1.1.3}/src/python4cpm/python4cpmhandler.py +1 -1
  11. python4cpm-1.1.3/src/python4cpm/secret.py +24 -0
  12. {python4cpm-1.1.1 → python4cpm-1.1.3/src/python4cpm.egg-info}/PKG-INFO +39 -71
  13. {python4cpm-1.1.1 → python4cpm-1.1.3}/src/python4cpm.egg-info/SOURCES.txt +1 -0
  14. python4cpm-1.1.1/src/python4cpm/accounts.py +0 -70
  15. python4cpm-1.1.1/src/python4cpm/args.py +0 -21
  16. python4cpm-1.1.1/src/python4cpm/nethelper.py +0 -46
  17. python4cpm-1.1.1/src/python4cpm/secret.py +0 -15
  18. {python4cpm-1.1.1 → python4cpm-1.1.3}/LICENSE +0 -0
  19. {python4cpm-1.1.1 → python4cpm-1.1.3}/setup.cfg +0 -0
  20. {python4cpm-1.1.1 → python4cpm-1.1.3}/src/python4cpm/__init__.py +0 -0
  21. {python4cpm-1.1.1 → python4cpm-1.1.3}/src/python4cpm/crypto.py +0 -0
  22. {python4cpm-1.1.1 → python4cpm-1.1.3}/src/python4cpm.egg-info/dependency_links.txt +0 -0
  23. {python4cpm-1.1.1 → 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.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
@@ -11,9 +11,15 @@ Dynamic: license-file
11
11
 
12
12
  # Python4CPM
13
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.
14
+ A simple and secure way of using python scripts with CyberArk CPM/SRS password rotations.
15
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`.
16
+ ## How it works
17
+
18
+ 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 Python.
19
+
20
+ All objects are collected from the SDK and sent as environment context to be picked up by the `python4cpm` module during the subprocess execution of python. All secrets of such environment are protected and encrypted by [Data Protection API (DPAPI)](https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection), until they are explicitely retrieved in your python script runtime, invoking the `Secret.get()` method. Finally, python controls the termination signal sent back to the SDK, which is consequently used as the return code to CPM/SRS. Such as a successful or failed (recoverable or not) result of the requested action.
21
+
22
+ This platform allows you to duplicate it multiple times, simply changing its settings (with regular day two operations from Privilege Cloud/PVWA) to point to different venvs and/or python scripts.
17
23
 
18
24
  ## Installation
19
25
 
@@ -61,7 +67,7 @@ This platform allows you to duplicate it multiple times, simply changing its set
61
67
  from python4cpm import Python4CPMHandler
62
68
 
63
69
 
64
- class MyRotator(Python4CPMHandler): # create a subclass for the Handler
70
+ class MyRotator(Python4CPMHandler):
65
71
  """
66
72
  These are the usable properties and methods from Python4CPMHandler:
67
73
 
@@ -98,9 +104,7 @@ class MyRotator(Python4CPMHandler): # create a subclass for the Handler
98
104
  self.logger.info("this is an info message")
99
105
  self.logger.debug("this is a debug message")
100
106
 
101
- # logs are placed in Logs/ThirdParty/MyRotator.log
102
-
103
- ## The logging level comes from PythonLoggingLevel (platform parameters) (default is error)
107
+ # The logging level comes from PythonLoggingLevel (platform parameters) (default is error)
104
108
 
105
109
  =============================
106
110
  REQUIRED TERMINATION SIGNALS
@@ -108,75 +112,44 @@ class MyRotator(Python4CPMHandler): # create a subclass for the Handler
108
112
  Terminate signals -> MUST use one of the following three signals to terminate the script:
109
113
 
110
114
  self.close_success()
111
- # terminate with success state
115
+ # terminate and provide CPM/SRS with a success state
112
116
 
113
117
  self.close_fail()
114
- # terminate with recoverable failed state
118
+ # terminate and provide CPM/SRS with a failed recoverable state
115
119
 
116
120
  self.close_fail(unrecoverable=True)
117
- # terminate with unrecoverable failed state
121
+ # terminate and provide CPM/SRS with a failed unrecoverable state
118
122
 
119
123
  When calling a signal sys.exit is invoked and the script is terminated.
120
124
  If no signal is called, and the script finishes without any exception,
121
125
  it will behave like p4cpm.close_fail(unrecoverable=True) and log an error message.
126
+
122
127
  =============================
128
+ REQUIRED METHODS
123
129
  =============================
130
+ verify(), logon(), change(), prereconcile(), reconcile()
124
131
  """
125
132
 
126
- # =============================
127
- # REQUIRED METHODS (MUST DEFINE)
128
- # =============================
129
- # verify(), logon(), change(), prereconcile(), reconcile()
130
-
131
133
  def verify(self):
132
- self._verify()
133
- self.log_info("verification successful")
134
+ # TODO: use account objects for your logic
134
135
  self.close_success()
135
136
 
136
137
  def logon(self):
138
+ # TODO: use account objects for your logic
137
139
  self.close_success()
138
140
 
139
141
  def change(self):
140
- self._change()
141
- self.log_error("something went wrong")
142
- self.close_fail()
142
+ # TODO: use account objects for your logic
143
+ self.close_success()
143
144
 
144
145
  def prereconcile(self):
145
- self._verify(from_reconcile=True)
146
+ # TODO: use account objects for your logic
146
147
  self.close_success()
147
148
 
148
149
  def reconcile(self):
149
- self._change(from_reconcile=True)
150
+ # TODO: use account objects for your logic
150
151
  self.close_success()
151
152
 
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
153
 
181
154
  if __name__ == "__main__":
182
155
  MyRotator().run() # initializes the class and calls the action that was requested from CPM/SRS.
@@ -189,9 +162,10 @@ When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
189
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)`.
190
163
  3. Reconcile -> the sciprt will be executed twice, running first the `MyRotator.prereconcile()` method and secondly the `MyRotator.reconcile()` method.
191
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)`.
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.
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`.
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`).
195
169
 
196
170
 
197
171
  ### Installing dependencies in python venv
@@ -205,14 +179,9 @@ As with any python venv, you can install dependencies in your venv.
205
179
 
206
180
  ## Dev Helper:
207
181
 
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
- ```
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
- **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.
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
 
217
186
  ### Example:
218
187
 
@@ -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 can use an empty string if it does not apply
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
@@ -231,16 +200,16 @@ target_new_password = getpass("new_password: ") # new password for the rotation
231
200
 
232
201
  NETHelper.set(
233
202
  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",
203
+ target_username="jdoe", # -> will fall under MyRotator.target_account.username
204
+ target_address="myapp.corp.local", # -> will fall under MyRotator.target_account.address
205
+ target_port="8443", # -> will fall under MyRotator.target_account.port
206
+ logon_username="ldoe", # -> will fall under MyRotator.logon_account.username
207
+ reconcile_username="rdoe", # -> will fall under MyRotator.reconcile_account.username
239
208
  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
209
+ target_password=target_password, # -> will fall under MyRotator.target_account.password.get()
210
+ logon_password=logon_password, # -> will fall under MyRotator.logon_account.password.get()
211
+ reconcile_password=reconcile_password, # -> will fall under MyRotator.reconcile_account.password.get()
212
+ target_new_password=target_new_password # -> will fall under MyRotator.target_account.new_password.get()
244
213
  )
245
214
 
246
215
  class MyRotator(Python4CPMHandler):
@@ -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,8 +1,14 @@
1
1
  # Python4CPM
2
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.
3
+ A simple and secure way of using python scripts with CyberArk CPM/SRS password rotations.
4
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`.
5
+ ## How it works
6
+
7
+ 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 Python.
8
+
9
+ All objects are collected from the SDK and sent as environment context to be picked up by the `python4cpm` module during the subprocess execution of python. All secrets of such environment are protected and encrypted by [Data Protection API (DPAPI)](https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection), until they are explicitely retrieved in your python script runtime, invoking the `Secret.get()` method. Finally, python controls the termination signal sent back to the SDK, which is consequently used as the return code to CPM/SRS. Such as a successful or failed (recoverable or not) result of the requested action.
10
+
11
+ This platform allows you to duplicate it multiple times, simply changing its settings (with regular day two operations from Privilege Cloud/PVWA) to point to different venvs and/or python scripts.
6
12
 
7
13
  ## Installation
8
14
 
@@ -50,7 +56,7 @@ This platform allows you to duplicate it multiple times, simply changing its set
50
56
  from python4cpm import Python4CPMHandler
51
57
 
52
58
 
53
- class MyRotator(Python4CPMHandler): # create a subclass for the Handler
59
+ class MyRotator(Python4CPMHandler):
54
60
  """
55
61
  These are the usable properties and methods from Python4CPMHandler:
56
62
 
@@ -87,9 +93,7 @@ class MyRotator(Python4CPMHandler): # create a subclass for the Handler
87
93
  self.logger.info("this is an info message")
88
94
  self.logger.debug("this is a debug message")
89
95
 
90
- # logs are placed in Logs/ThirdParty/MyRotator.log
91
-
92
- ## The logging level comes from PythonLoggingLevel (platform parameters) (default is error)
96
+ # The logging level comes from PythonLoggingLevel (platform parameters) (default is error)
93
97
 
94
98
  =============================
95
99
  REQUIRED TERMINATION SIGNALS
@@ -97,75 +101,44 @@ class MyRotator(Python4CPMHandler): # create a subclass for the Handler
97
101
  Terminate signals -> MUST use one of the following three signals to terminate the script:
98
102
 
99
103
  self.close_success()
100
- # terminate with success state
104
+ # terminate and provide CPM/SRS with a success state
101
105
 
102
106
  self.close_fail()
103
- # terminate with recoverable failed state
107
+ # terminate and provide CPM/SRS with a failed recoverable state
104
108
 
105
109
  self.close_fail(unrecoverable=True)
106
- # terminate with unrecoverable failed state
110
+ # terminate and provide CPM/SRS with a failed unrecoverable state
107
111
 
108
112
  When calling a signal sys.exit is invoked and the script is terminated.
109
113
  If no signal is called, and the script finishes without any exception,
110
114
  it will behave like p4cpm.close_fail(unrecoverable=True) and log an error message.
115
+
111
116
  =============================
117
+ REQUIRED METHODS
112
118
  =============================
119
+ verify(), logon(), change(), prereconcile(), reconcile()
113
120
  """
114
121
 
115
- # =============================
116
- # REQUIRED METHODS (MUST DEFINE)
117
- # =============================
118
- # verify(), logon(), change(), prereconcile(), reconcile()
119
-
120
122
  def verify(self):
121
- self._verify()
122
- self.log_info("verification successful")
123
+ # TODO: use account objects for your logic
123
124
  self.close_success()
124
125
 
125
126
  def logon(self):
127
+ # TODO: use account objects for your logic
126
128
  self.close_success()
127
129
 
128
130
  def change(self):
129
- self._change()
130
- self.log_error("something went wrong")
131
- self.close_fail()
131
+ # TODO: use account objects for your logic
132
+ self.close_success()
132
133
 
133
134
  def prereconcile(self):
134
- self._verify(from_reconcile=True)
135
+ # TODO: use account objects for your logic
135
136
  self.close_success()
136
137
 
137
138
  def reconcile(self):
138
- self._change(from_reconcile=True)
139
+ # TODO: use account objects for your logic
139
140
  self.close_success()
140
141
 
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
142
 
170
143
  if __name__ == "__main__":
171
144
  MyRotator().run() # initializes the class and calls the action that was requested from CPM/SRS.
@@ -178,9 +151,10 @@ When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
178
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)`.
179
152
  3. Reconcile -> the sciprt will be executed twice, running first the `MyRotator.prereconcile()` method and secondly the `MyRotator.reconcile()` method.
180
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)`.
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.
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`.
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`).
184
158
 
185
159
 
186
160
  ### Installing dependencies in python venv
@@ -194,14 +168,9 @@ As with any python venv, you can install dependencies in your venv.
194
168
 
195
169
  ## Dev Helper:
196
170
 
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
- ```
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
- **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.
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
 
206
175
  ### Example:
207
176
 
@@ -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 can use an empty string if it does not apply
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
@@ -220,16 +189,16 @@ target_new_password = getpass("new_password: ") # new password for the rotation
220
189
 
221
190
  NETHelper.set(
222
191
  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",
192
+ target_username="jdoe", # -> will fall under MyRotator.target_account.username
193
+ target_address="myapp.corp.local", # -> will fall under MyRotator.target_account.address
194
+ target_port="8443", # -> will fall under MyRotator.target_account.port
195
+ logon_username="ldoe", # -> will fall under MyRotator.logon_account.username
196
+ reconcile_username="rdoe", # -> will fall under MyRotator.reconcile_account.username
228
197
  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
198
+ target_password=target_password, # -> will fall under MyRotator.target_account.password.get()
199
+ logon_password=logon_password, # -> will fall under MyRotator.logon_account.password.get()
200
+ reconcile_password=reconcile_password, # -> will fall under MyRotator.reconcile_account.password.get()
201
+ target_new_password=target_new_password # -> will fall under MyRotator.target_account.new_password.get()
233
202
  )
234
203
 
235
204
  class MyRotator(Python4CPMHandler):
@@ -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.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python4cpm"
7
- version = "1.1.1"
7
+ version = "1.1.3"
8
8
  description = "Python for CPM"
9
9
  authors = [
10
10
  { name = "Gonzalo Atienza Rela", email = "gonatienza@gmail.com" }
@@ -0,0 +1,70 @@
1
+ from python4cpm.envhandler import EnvHandler, Props
2
+ from python4cpm.secret import Secret
3
+
4
+
5
+ class BaseAccount(EnvHandler):
6
+ PROPS = Props("username", "password")
7
+
8
+ def __init__(
9
+ self,
10
+ username: str | None,
11
+ password: str | None
12
+ ) -> None:
13
+ self._username = username
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)
22
+
23
+ @property
24
+ def username(self) -> str:
25
+ return self._username
26
+
27
+ @property
28
+ def password(self) -> Secret:
29
+ return self._password
30
+
31
+
32
+ class TargetAccount(BaseAccount):
33
+ OBJ_PREFIX = "target_"
34
+ PROPS = Props("username", "password", "address", "port", "new_password")
35
+
36
+ def __init__(
37
+ self,
38
+ username: str | None,
39
+ password: str | None,
40
+ address: str | None,
41
+ port: str | None,
42
+ new_password: str | None
43
+ ) -> None:
44
+ super().__init__(
45
+ username,
46
+ password
47
+ )
48
+ self._address = address
49
+ self._port = port
50
+ self._new_password = Secret.from_env_var(new_password)
51
+
52
+ @property
53
+ def address(self) -> str | None:
54
+ return self._address
55
+
56
+ @property
57
+ def port(self) -> str | None:
58
+ return self._port
59
+
60
+ @property
61
+ def new_password(self) -> Secret:
62
+ return self._new_password
63
+
64
+
65
+ class LogonAccount(BaseAccount):
66
+ OBJ_PREFIX = "logon_"
67
+
68
+
69
+ class ReconcileAccount(BaseAccount):
70
+ OBJ_PREFIX = "reconcile_"
@@ -0,0 +1,22 @@
1
+ from python4cpm.envhandler import EnvHandler, Props
2
+
3
+
4
+ class Args(EnvHandler):
5
+ OBJ_PREFIX = "args_"
6
+ PROPS = Props("action", "logging_level")
7
+
8
+ def __init__(
9
+ self,
10
+ action: str | None,
11
+ logging_level: str | None
12
+ ) -> None:
13
+ self._action = action
14
+ self._logging_level = logging_level
15
+
16
+ @property
17
+ def action(self) -> str:
18
+ return self._action
19
+
20
+ @property
21
+ def logging_level(self) -> str | None:
22
+ return self._logging_level
@@ -0,0 +1,32 @@
1
+ import os
2
+
3
+
4
+ class Props:
5
+ def __init__(self, *props):
6
+ for prop in props:
7
+ setattr(self, prop, prop)
8
+
9
+ def __iter__(self) -> iter:
10
+ return iter(self.__dict__.values())
11
+
12
+
13
+ class EnvHandler:
14
+ PREFIX = "python4cpm_"
15
+ OBJ_PREFIX = ""
16
+ PROPS = Props()
17
+
18
+ @classmethod
19
+ def get_key(cls, key: str) -> str:
20
+ env_key = f"{cls.PREFIX}{cls.OBJ_PREFIX}{key}"
21
+ return env_key.upper()
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
+
30
+ @classmethod
31
+ def get(cls) -> object:
32
+ return cls(**cls.get_kwargs())
@@ -18,14 +18,15 @@ class Logger:
18
18
  def get_logger(
19
19
  cls,
20
20
  name: str,
21
- args_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
- if args_logging_level.lower() in cls._LOGGING_LEVELS:
28
- logger.setLevel(cls._LOGGING_LEVELS[args_logging_level.lower()])
27
+ is_logging_level_str = isinstance(logging_level, str)
28
+ if is_logging_level_str and logging_level.lower() in cls._LOGGING_LEVELS:
29
+ logger.setLevel(cls._LOGGING_LEVELS[logging_level.lower()])
29
30
  else:
30
31
  logger.setLevel(cls._DEFAULT_LEVEL)
31
32
  handler = RotatingFileHandler(
@@ -0,0 +1,61 @@
1
+ from python4cpm.python4cpm import Python4CPM
2
+ from python4cpm.args import Args
3
+ from python4cpm.accounts import TargetAccount, LogonAccount, ReconcileAccount
4
+ from python4cpm.crypto import Crypto
5
+ import os
6
+
7
+
8
+ class NETHelper:
9
+ @classmethod
10
+ def set(
11
+ cls,
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
+ ) -> None:
24
+ if Crypto.ENABLED:
25
+ target_password = Crypto.encrypt(target_password)
26
+ logon_password = Crypto.encrypt(logon_password)
27
+ reconcile_password = Crypto.encrypt(reconcile_password)
28
+ target_new_password = Crypto.encrypt(target_new_password)
29
+ keys = (
30
+ Args.get_key(Args.PROPS.action),
31
+ Args.get_key(Args.PROPS.logging_level),
32
+ TargetAccount.get_key(TargetAccount.PROPS.username),
33
+ TargetAccount.get_key(TargetAccount.PROPS.address),
34
+ TargetAccount.get_key(TargetAccount.PROPS.port),
35
+ LogonAccount.get_key(LogonAccount.PROPS.username),
36
+ ReconcileAccount.get_key(ReconcileAccount.PROPS.username),
37
+ TargetAccount.get_key(TargetAccount.PROPS.password),
38
+ LogonAccount.get_key(LogonAccount.PROPS.password),
39
+ ReconcileAccount.get_key(ReconcileAccount.PROPS.password),
40
+ TargetAccount.get_key(TargetAccount.PROPS.new_password)
41
+ )
42
+ values = (
43
+ action,
44
+ logging_level,
45
+ target_username,
46
+ target_address,
47
+ target_port,
48
+ logon_username,
49
+ reconcile_username,
50
+ target_password,
51
+ logon_password,
52
+ reconcile_password,
53
+ target_new_password
54
+ )
55
+ for i, key in enumerate(keys):
56
+ if values[i] is not None:
57
+ os.environ.update({key: values[i]})
58
+
59
+ @classmethod
60
+ def get(cls) -> Python4CPM:
61
+ return Python4CPM(cls.__name__)
@@ -1,17 +1,11 @@
1
- import os
2
1
  import sys
3
2
  import atexit
4
3
  import logging
5
4
  from python4cpm.secret import Secret
6
5
  from python4cpm.args import Args
7
- from python4cpm.crypto import Crypto
8
6
  from python4cpm.logger import Logger
9
- from python4cpm.accounts import (
10
- BaseAccount,
11
- TargetAccount,
12
- LogonAccount,
13
- ReconcileAccount
14
- )
7
+ from python4cpm.accounts import TargetAccount, LogonAccount, ReconcileAccount
8
+
15
9
 
16
10
  class Python4CPM:
17
11
  ACTION_VERIFY = "verifypass"
@@ -29,18 +23,17 @@ class Python4CPM:
29
23
  _SUCCESS_CODE = 10
30
24
  _FAILED_RECOVERABLE_CODE = 81
31
25
  _FAILED_UNRECOVERABLE_CODE = 89
32
- _ENV_PREFIX = "PYTHON4CPM_"
33
26
 
34
27
  def __init__(self, name: str) -> None:
35
28
  self._name = name
36
- self._args = self._get_args()
29
+ self._args = Args.get()
30
+ self._target_account = TargetAccount.get()
31
+ self._logon_account = LogonAccount.get()
32
+ self._reconcile_account = ReconcileAccount.get()
37
33
  self._logger = Logger.get_logger(self._name, self._args.logging_level)
38
34
  self._logger.debug("Initiating...")
39
35
  self._log_obj(self._args)
40
36
  self._verify_action()
41
- self._target_account = self._get_account(TargetAccount)
42
- self._logon_account = self._get_account(LogonAccount)
43
- self._reconcile_account = self._get_account(ReconcileAccount)
44
37
  self._log_obj(self._target_account)
45
38
  self._log_obj(self._logon_account)
46
39
  self._log_obj(self._reconcile_account)
@@ -67,43 +60,22 @@ class Python4CPM:
67
60
  def reconcile_account(self) -> ReconcileAccount:
68
61
  return self._reconcile_account
69
62
 
70
- @classmethod
71
- def _get_env_key(cls, key: str) -> str:
72
- return f"{cls._ENV_PREFIX}{key.upper()}"
73
-
74
- @classmethod
75
- def _get_args(cls) -> Args:
76
- kwargs = {}
77
- for kwarg in Args.ARGS:
78
- _kwarg = os.environ.get(cls._get_env_key(kwarg))
79
- kwargs[kwarg] = _kwarg if _kwarg is not None else ""
80
- return Args(**kwargs)
81
-
82
- def _get_account(self, account_class: BaseAccount) -> BaseAccount:
83
- args = []
84
- for arg in account_class.ENV_VARS:
85
- _arg = os.environ.get(self._get_env_key(arg))
86
- args.append(_arg if _arg is not None else "")
87
- return account_class(*args)
88
-
89
63
  def _verify_action(self) -> None:
90
64
  if self._args.action not in self._VALID_ACTIONS:
91
65
  self._logger.warning(f"Unkonwn action -> '{self._args.action}'")
92
66
 
93
- def _log_obj(self, obj: object) -> None:
94
- for key, value in vars(obj).items():
95
- _key = f"{obj.__class__.__name__}.{key.strip('_')}"
96
- if value:
97
- if not isinstance(value, Secret):
98
- logging_value = f"'{value}'"
99
- else:
100
- if Crypto.ENABLED is True:
101
- 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}'"
102
74
  else:
103
- logging_value = "[SET]"
104
- else:
105
- logging_value = "[NOT SET]"
106
- self._logger.debug(f"{_key} -> {logging_value}")
75
+ logging_value = str(value)
76
+ else:
77
+ logging_value = "[NOT SET]"
78
+ self._logger.debug(f"{_key} -> {logging_value}")
107
79
 
108
80
  def close_fail(self, unrecoverable: bool = False) -> None:
109
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.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
@@ -11,9 +11,15 @@ Dynamic: license-file
11
11
 
12
12
  # Python4CPM
13
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.
14
+ A simple and secure way of using python scripts with CyberArk CPM/SRS password rotations.
15
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`.
16
+ ## How it works
17
+
18
+ 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 Python.
19
+
20
+ All objects are collected from the SDK and sent as environment context to be picked up by the `python4cpm` module during the subprocess execution of python. All secrets of such environment are protected and encrypted by [Data Protection API (DPAPI)](https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection), until they are explicitely retrieved in your python script runtime, invoking the `Secret.get()` method. Finally, python controls the termination signal sent back to the SDK, which is consequently used as the return code to CPM/SRS. Such as a successful or failed (recoverable or not) result of the requested action.
21
+
22
+ This platform allows you to duplicate it multiple times, simply changing its settings (with regular day two operations from Privilege Cloud/PVWA) to point to different venvs and/or python scripts.
17
23
 
18
24
  ## Installation
19
25
 
@@ -61,7 +67,7 @@ This platform allows you to duplicate it multiple times, simply changing its set
61
67
  from python4cpm import Python4CPMHandler
62
68
 
63
69
 
64
- class MyRotator(Python4CPMHandler): # create a subclass for the Handler
70
+ class MyRotator(Python4CPMHandler):
65
71
  """
66
72
  These are the usable properties and methods from Python4CPMHandler:
67
73
 
@@ -98,9 +104,7 @@ class MyRotator(Python4CPMHandler): # create a subclass for the Handler
98
104
  self.logger.info("this is an info message")
99
105
  self.logger.debug("this is a debug message")
100
106
 
101
- # logs are placed in Logs/ThirdParty/MyRotator.log
102
-
103
- ## The logging level comes from PythonLoggingLevel (platform parameters) (default is error)
107
+ # The logging level comes from PythonLoggingLevel (platform parameters) (default is error)
104
108
 
105
109
  =============================
106
110
  REQUIRED TERMINATION SIGNALS
@@ -108,75 +112,44 @@ class MyRotator(Python4CPMHandler): # create a subclass for the Handler
108
112
  Terminate signals -> MUST use one of the following three signals to terminate the script:
109
113
 
110
114
  self.close_success()
111
- # terminate with success state
115
+ # terminate and provide CPM/SRS with a success state
112
116
 
113
117
  self.close_fail()
114
- # terminate with recoverable failed state
118
+ # terminate and provide CPM/SRS with a failed recoverable state
115
119
 
116
120
  self.close_fail(unrecoverable=True)
117
- # terminate with unrecoverable failed state
121
+ # terminate and provide CPM/SRS with a failed unrecoverable state
118
122
 
119
123
  When calling a signal sys.exit is invoked and the script is terminated.
120
124
  If no signal is called, and the script finishes without any exception,
121
125
  it will behave like p4cpm.close_fail(unrecoverable=True) and log an error message.
126
+
122
127
  =============================
128
+ REQUIRED METHODS
123
129
  =============================
130
+ verify(), logon(), change(), prereconcile(), reconcile()
124
131
  """
125
132
 
126
- # =============================
127
- # REQUIRED METHODS (MUST DEFINE)
128
- # =============================
129
- # verify(), logon(), change(), prereconcile(), reconcile()
130
-
131
133
  def verify(self):
132
- self._verify()
133
- self.log_info("verification successful")
134
+ # TODO: use account objects for your logic
134
135
  self.close_success()
135
136
 
136
137
  def logon(self):
138
+ # TODO: use account objects for your logic
137
139
  self.close_success()
138
140
 
139
141
  def change(self):
140
- self._change()
141
- self.log_error("something went wrong")
142
- self.close_fail()
142
+ # TODO: use account objects for your logic
143
+ self.close_success()
143
144
 
144
145
  def prereconcile(self):
145
- self._verify(from_reconcile=True)
146
+ # TODO: use account objects for your logic
146
147
  self.close_success()
147
148
 
148
149
  def reconcile(self):
149
- self._change(from_reconcile=True)
150
+ # TODO: use account objects for your logic
150
151
  self.close_success()
151
152
 
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
153
 
181
154
  if __name__ == "__main__":
182
155
  MyRotator().run() # initializes the class and calls the action that was requested from CPM/SRS.
@@ -189,9 +162,10 @@ When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
189
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)`.
190
163
  3. Reconcile -> the sciprt will be executed twice, running first the `MyRotator.prereconcile()` method and secondly the `MyRotator.reconcile()` method.
191
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)`.
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.
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`.
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`).
195
169
 
196
170
 
197
171
  ### Installing dependencies in python venv
@@ -205,14 +179,9 @@ As with any python venv, you can install dependencies in your venv.
205
179
 
206
180
  ## Dev Helper:
207
181
 
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
- ```
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
- **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.
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
 
217
186
  ### Example:
218
187
 
@@ -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 can use an empty string if it does not apply
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
@@ -231,16 +200,16 @@ target_new_password = getpass("new_password: ") # new password for the rotation
231
200
 
232
201
  NETHelper.set(
233
202
  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",
203
+ target_username="jdoe", # -> will fall under MyRotator.target_account.username
204
+ target_address="myapp.corp.local", # -> will fall under MyRotator.target_account.address
205
+ target_port="8443", # -> will fall under MyRotator.target_account.port
206
+ logon_username="ldoe", # -> will fall under MyRotator.logon_account.username
207
+ reconcile_username="rdoe", # -> will fall under MyRotator.reconcile_account.username
239
208
  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
209
+ target_password=target_password, # -> will fall under MyRotator.target_account.password.get()
210
+ logon_password=logon_password, # -> will fall under MyRotator.logon_account.password.get()
211
+ reconcile_password=reconcile_password, # -> will fall under MyRotator.reconcile_account.password.get()
212
+ target_new_password=target_new_password # -> will fall under MyRotator.target_account.new_password.get()
244
213
  )
245
214
 
246
215
  class MyRotator(Python4CPMHandler):
@@ -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.
@@ -5,6 +5,7 @@ src/python4cpm/__init__.py
5
5
  src/python4cpm/accounts.py
6
6
  src/python4cpm/args.py
7
7
  src/python4cpm/crypto.py
8
+ src/python4cpm/envhandler.py
8
9
  src/python4cpm/logger.py
9
10
  src/python4cpm/nethelper.py
10
11
  src/python4cpm/python4cpm.py
@@ -1,70 +0,0 @@
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
- )
@@ -1,21 +0,0 @@
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
@@ -1,46 +0,0 @@
1
- from python4cpm.python4cpm import Python4CPM
2
- from python4cpm.crypto import Crypto
3
- import os
4
-
5
-
6
- class NETHelper:
7
- @classmethod
8
- def set(
9
- cls,
10
- action: str = "",
11
- logging_level: str = "",
12
- target_username: str = "",
13
- target_address: str = "",
14
- target_port: str = "",
15
- logon_username: str = "",
16
- reconcile_username: str = "",
17
- target_password: str = "",
18
- logon_password: str = "",
19
- reconcile_password: str = "",
20
- target_new_password: str = ""
21
- ) -> None:
22
- if Crypto.ENABLED:
23
- target_password = Crypto.encrypt(target_password)
24
- logon_password = Crypto.encrypt(logon_password)
25
- reconcile_password = Crypto.encrypt(reconcile_password)
26
- target_new_password = Crypto.encrypt(target_new_password)
27
- env = {
28
- "ACTION": action,
29
- "LOGGING_LEVEL": logging_level,
30
- "TARGET_USERNAME": target_username,
31
- "TARGET_ADDRESS": target_address,
32
- "TARGET_PORT": target_port,
33
- "LOGON_USERNAME": logon_username,
34
- "RECONCILE_USERNAME": reconcile_username,
35
- "TARGET_PASSWORD": target_password,
36
- "LOGON_PASSWORD": logon_password,
37
- "RECONCILE_PASSWORD": reconcile_password,
38
- "TARGET_NEW_PASSWORD": target_new_password
39
- }
40
- for key, value in env.items():
41
- env_var = Python4CPM._ENV_PREFIX + key
42
- os.environ[env_var] = value
43
-
44
- @classmethod
45
- def get(cls) -> Python4CPM:
46
- return Python4CPM(cls.__name__)
@@ -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