python4cpm 1.1.1__tar.gz → 1.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python4cpm
3
- Version: 1.1.1
3
+ Version: 1.1.2
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
 
@@ -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,13 +112,13 @@ 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,
@@ -189,9 +193,10 @@ When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
189
193
  - 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
194
  3. Reconcile -> the sciprt will be executed twice, running first the `MyRotator.prereconcile()` method and secondly the `MyRotator.reconcile()` method.
191
195
  - 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.
196
+ 4. When calling `MyRotator.verify()`, `MyRotator.logon()` or `MyRotator.prereconcile()`: `self.target_account.new_password.get()` will always return an empty string.
197
+ 5. If a logon account is not linked, `self.logon_account.username` and `self.logon_account.password.get()` will return empty strings.
198
+ 6. If a reconcile account is not linked, `self.reconcile_account.username` and `self.reconcile_account.password.get()` will return empty strings.
199
+ 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
200
 
196
201
 
197
202
  ### Installing dependencies in python venv
@@ -205,14 +210,9 @@ As with any python venv, you can install dependencies in your venv.
205
210
 
206
211
  ## Dev Helper:
207
212
 
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
- ```
213
+ For dev purposes, `NETHelper` is a companion helper to test your scripts before shipping to 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
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.
215
+ **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
216
 
217
217
  ### Example:
218
218
 
@@ -231,16 +231,16 @@ target_new_password = getpass("new_password: ") # new password for the rotation
231
231
 
232
232
  NETHelper.set(
233
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",
234
+ target_username="jdoe", # -> will fall under MyRotator.target_account.username
235
+ target_address="myapp.corp.local", # -> will fall under MyRotator.target_account.address
236
+ target_port="8443", # -> will fall under MyRotator.target_account.port
237
+ logon_username="ldoe", # -> will fall under MyRotator.logon_account.username
238
+ reconcile_username="rdoe", # -> will fall under MyRotator.reconcile_account.username
239
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
240
+ target_password=target_password, # -> will fall under MyRotator.target_account.password.get()
241
+ logon_password=logon_password, # -> will fall under MyRotator.logon_account.password.get()
242
+ reconcile_password=reconcile_password, # -> will fall under MyRotator.reconcile_account.password.get()
243
+ target_new_password=target_new_password # -> will fall under MyRotator.target_account.new_password.get()
244
244
  )
245
245
 
246
246
  class MyRotator(Python4CPMHandler):
@@ -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
 
@@ -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,13 +101,13 @@ 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,
@@ -178,9 +182,10 @@ When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
178
182
  - 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
183
  3. Reconcile -> the sciprt will be executed twice, running first the `MyRotator.prereconcile()` method and secondly the `MyRotator.reconcile()` method.
180
184
  - 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.
185
+ 4. When calling `MyRotator.verify()`, `MyRotator.logon()` or `MyRotator.prereconcile()`: `self.target_account.new_password.get()` will always return an empty string.
186
+ 5. If a logon account is not linked, `self.logon_account.username` and `self.logon_account.password.get()` will return empty strings.
187
+ 6. If a reconcile account is not linked, `self.reconcile_account.username` and `self.reconcile_account.password.get()` will return empty strings.
188
+ 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
189
 
185
190
 
186
191
  ### Installing dependencies in python venv
@@ -194,14 +199,9 @@ As with any python venv, you can install dependencies in your venv.
194
199
 
195
200
  ## Dev Helper:
196
201
 
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
- ```
202
+ For dev purposes, `NETHelper` is a companion helper to test your scripts before shipping to 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
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.
204
+ **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
205
 
206
206
  ### Example:
207
207
 
@@ -220,16 +220,16 @@ target_new_password = getpass("new_password: ") # new password for the rotation
220
220
 
221
221
  NETHelper.set(
222
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",
223
+ target_username="jdoe", # -> will fall under MyRotator.target_account.username
224
+ target_address="myapp.corp.local", # -> will fall under MyRotator.target_account.address
225
+ target_port="8443", # -> will fall under MyRotator.target_account.port
226
+ logon_username="ldoe", # -> will fall under MyRotator.logon_account.username
227
+ reconcile_username="rdoe", # -> will fall under MyRotator.reconcile_account.username
228
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
229
+ target_password=target_password, # -> will fall under MyRotator.target_account.password.get()
230
+ logon_password=logon_password, # -> will fall under MyRotator.logon_account.password.get()
231
+ reconcile_password=reconcile_password, # -> will fall under MyRotator.reconcile_account.password.get()
232
+ target_new_password=target_new_password # -> will fall under MyRotator.target_account.new_password.get()
233
233
  )
234
234
 
235
235
  class MyRotator(Python4CPMHandler):
@@ -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.2"
8
8
  description = "Python for CPM"
9
9
  authors = [
10
10
  { name = "Gonzalo Atienza Rela", email = "gonatienza@gmail.com" }
@@ -1,9 +1,12 @@
1
+ from python4cpm.envhandler import EnvHandler, Props
1
2
  from python4cpm.secret import Secret
2
3
 
3
4
 
4
- class BaseAccount:
5
+ class BaseAccount(EnvHandler):
6
+ PROPS = Props("username", "password")
7
+
5
8
  def __init__(
6
- self: str,
9
+ self,
7
10
  username: str,
8
11
  password: str
9
12
  ) -> None:
@@ -20,15 +23,11 @@ class BaseAccount:
20
23
 
21
24
 
22
25
  class TargetAccount(BaseAccount):
23
- ENV_VARS = (
24
- "target_username",
25
- "target_password",
26
- "target_address",
27
- "target_port",
28
- "target_new_password"
29
- )
26
+ OBJ_PREFIX = "target_"
27
+ PROPS = Props("username", "password", "address", "port", "new_password")
28
+
30
29
  def __init__(
31
- self: str,
30
+ self,
32
31
  username: str,
33
32
  password: str,
34
33
  address: str,
@@ -57,14 +56,8 @@ class TargetAccount(BaseAccount):
57
56
 
58
57
 
59
58
  class LogonAccount(BaseAccount):
60
- ENV_VARS = (
61
- "logon_username",
62
- "logon_password"
63
- )
59
+ OBJ_PREFIX = "logon_"
64
60
 
65
61
 
66
62
  class ReconcileAccount(BaseAccount):
67
- ENV_VARS = (
68
- "reconcile_username",
69
- "reconcile_password"
70
- )
63
+ OBJ_PREFIX = "reconcile_"
@@ -1,11 +1,12 @@
1
- class Args:
2
- ARGS = (
3
- "action",
4
- "logging_level"
5
- )
1
+ from python4cpm.envhandler import EnvHandler, Props
2
+
3
+
4
+ class Args(EnvHandler):
5
+ OBJ_PREFIX = "args_"
6
+ PROPS = Props("action", "logging_level")
6
7
 
7
8
  def __init__(
8
- self: str,
9
+ self,
9
10
  action: str,
10
11
  logging_level: str
11
12
  ) -> None:
@@ -0,0 +1,29 @@
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(cls) -> object:
25
+ kwargs = {}
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)
@@ -18,14 +18,14 @@ class Logger:
18
18
  def get_logger(
19
19
  cls,
20
20
  name: str,
21
- args_logging_level: str
21
+ logging_level: str
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
+ if logging_level.lower() in cls._LOGGING_LEVELS:
28
+ logger.setLevel(cls._LOGGING_LEVELS[logging_level.lower()])
29
29
  else:
30
30
  logger.setLevel(cls._DEFAULT_LEVEL)
31
31
  handler = RotatingFileHandler(
@@ -0,0 +1,60 @@
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 = "",
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 = ""
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
+ os.environ.update({key: values[i]})
57
+
58
+ @classmethod
59
+ def get(cls) -> Python4CPM:
60
+ return Python4CPM(cls.__name__)
@@ -1,4 +1,3 @@
1
- import os
2
1
  import sys
3
2
  import atexit
4
3
  import logging
@@ -6,12 +5,8 @@ from python4cpm.secret import Secret
6
5
  from python4cpm.args import Args
7
6
  from python4cpm.crypto import Crypto
8
7
  from python4cpm.logger import Logger
9
- from python4cpm.accounts import (
10
- BaseAccount,
11
- TargetAccount,
12
- LogonAccount,
13
- ReconcileAccount
14
- )
8
+ from python4cpm.accounts import TargetAccount, LogonAccount, ReconcileAccount
9
+
15
10
 
16
11
  class Python4CPM:
17
12
  ACTION_VERIFY = "verifypass"
@@ -29,18 +24,17 @@ class Python4CPM:
29
24
  _SUCCESS_CODE = 10
30
25
  _FAILED_RECOVERABLE_CODE = 81
31
26
  _FAILED_UNRECOVERABLE_CODE = 89
32
- _ENV_PREFIX = "PYTHON4CPM_"
33
27
 
34
28
  def __init__(self, name: str) -> None:
35
29
  self._name = name
36
- self._args = self._get_args()
30
+ self._args = Args.get()
31
+ self._target_account = TargetAccount.get()
32
+ self._logon_account = LogonAccount.get()
33
+ self._reconcile_account = ReconcileAccount.get()
37
34
  self._logger = Logger.get_logger(self._name, self._args.logging_level)
38
35
  self._logger.debug("Initiating...")
39
36
  self._log_obj(self._args)
40
37
  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
38
  self._log_obj(self._target_account)
45
39
  self._log_obj(self._logon_account)
46
40
  self._log_obj(self._reconcile_account)
@@ -67,25 +61,6 @@ class Python4CPM:
67
61
  def reconcile_account(self) -> ReconcileAccount:
68
62
  return self._reconcile_account
69
63
 
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
64
  def _verify_action(self) -> None:
90
65
  if self._args.action not in self._VALID_ACTIONS:
91
66
  self._logger.warning(f"Unkonwn action -> '{self._args.action}'")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python4cpm
3
- Version: 1.1.1
3
+ Version: 1.1.2
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
 
@@ -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,13 +112,13 @@ 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,
@@ -189,9 +193,10 @@ When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
189
193
  - 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
194
  3. Reconcile -> the sciprt will be executed twice, running first the `MyRotator.prereconcile()` method and secondly the `MyRotator.reconcile()` method.
191
195
  - 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.
196
+ 4. When calling `MyRotator.verify()`, `MyRotator.logon()` or `MyRotator.prereconcile()`: `self.target_account.new_password.get()` will always return an empty string.
197
+ 5. If a logon account is not linked, `self.logon_account.username` and `self.logon_account.password.get()` will return empty strings.
198
+ 6. If a reconcile account is not linked, `self.reconcile_account.username` and `self.reconcile_account.password.get()` will return empty strings.
199
+ 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
200
 
196
201
 
197
202
  ### Installing dependencies in python venv
@@ -205,14 +210,9 @@ As with any python venv, you can install dependencies in your venv.
205
210
 
206
211
  ## Dev Helper:
207
212
 
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
- ```
213
+ For dev purposes, `NETHelper` is a companion helper to test your scripts before shipping to 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
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.
215
+ **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
216
 
217
217
  ### Example:
218
218
 
@@ -231,16 +231,16 @@ target_new_password = getpass("new_password: ") # new password for the rotation
231
231
 
232
232
  NETHelper.set(
233
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",
234
+ target_username="jdoe", # -> will fall under MyRotator.target_account.username
235
+ target_address="myapp.corp.local", # -> will fall under MyRotator.target_account.address
236
+ target_port="8443", # -> will fall under MyRotator.target_account.port
237
+ logon_username="ldoe", # -> will fall under MyRotator.logon_account.username
238
+ reconcile_username="rdoe", # -> will fall under MyRotator.reconcile_account.username
239
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
240
+ target_password=target_password, # -> will fall under MyRotator.target_account.password.get()
241
+ logon_password=logon_password, # -> will fall under MyRotator.logon_account.password.get()
242
+ reconcile_password=reconcile_password, # -> will fall under MyRotator.reconcile_account.password.get()
243
+ target_new_password=target_new_password # -> will fall under MyRotator.target_account.new_password.get()
244
244
  )
245
245
 
246
246
  class MyRotator(Python4CPMHandler):
@@ -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,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__)
File without changes
File without changes