ark-sdk-python 1.0.4__py3-none-any.whl → 1.0.6__py3-none-any.whl
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.
- ark_sdk_python/actions/ark_login_action.py +14 -1
- ark_sdk_python/ark.py +1 -1
- ark_sdk_python/auth/ark_isp_auth.py +1 -0
- ark_sdk_python/auth/identity/ark_identity.py +24 -5
- ark_sdk_python/models/actions/ark_configure_action_consts.py +1 -0
- ark_sdk_python/models/auth/ark_auth_method.py +6 -0
- ark_sdk_python/services/dpa/sso/ark_dpa_sso_service.py +28 -18
- {ark_sdk_python-1.0.4.dist-info → ark_sdk_python-1.0.6.dist-info}/METADATA +1 -1
- ark_sdk_python-1.0.6.dist-info/NOTICES.md +2081 -0
- {ark_sdk_python-1.0.4.dist-info → ark_sdk_python-1.0.6.dist-info}/RECORD +13 -12
- {ark_sdk_python-1.0.4.dist-info → ark_sdk_python-1.0.6.dist-info}/WHEEL +1 -1
- {ark_sdk_python-1.0.4.dist-info → ark_sdk_python-1.0.6.dist-info}/LICENSE.txt +0 -0
- {ark_sdk_python-1.0.4.dist-info → ark_sdk_python-1.0.6.dist-info}/entry_points.txt +0 -0
|
@@ -16,6 +16,7 @@ from ark_sdk_python.models.auth import (
|
|
|
16
16
|
ArkAuthMethodsRequireCredentials,
|
|
17
17
|
ArkSecret,
|
|
18
18
|
ArkToken,
|
|
19
|
+
IdentityArkAuthMethodSettings,
|
|
19
20
|
)
|
|
20
21
|
|
|
21
22
|
|
|
@@ -138,7 +139,19 @@ class ArkLoginAction(ArkAction):
|
|
|
138
139
|
if (
|
|
139
140
|
authenticator_name == 'isp'
|
|
140
141
|
and auth_profile.auth_method == ArkAuthMethod.Identity
|
|
141
|
-
and ArkIdentity.is_idp_user(
|
|
142
|
+
and ArkIdentity.is_idp_user(
|
|
143
|
+
auth_profile.username,
|
|
144
|
+
(
|
|
145
|
+
auth_profile.auth_method_settings.identity_url
|
|
146
|
+
if isinstance(auth_profile.auth_method_settings, IdentityArkAuthMethodSettings)
|
|
147
|
+
else None
|
|
148
|
+
),
|
|
149
|
+
(
|
|
150
|
+
auth_profile.auth_method_settings.identity_tenant_subdomain
|
|
151
|
+
if isinstance(auth_profile.auth_method_settings, IdentityArkAuthMethodSettings)
|
|
152
|
+
else None
|
|
153
|
+
),
|
|
154
|
+
)
|
|
142
155
|
):
|
|
143
156
|
secret = ArkSecret(secret='')
|
|
144
157
|
else:
|
ark_sdk_python/ark.py
CHANGED
|
@@ -46,6 +46,7 @@ class ArkISPAuth(ArkAuth):
|
|
|
46
46
|
username=auth_profile.username,
|
|
47
47
|
password=secret.secret.get_secret_value() if secret else None,
|
|
48
48
|
identity_url=method_settings.identity_url,
|
|
49
|
+
identity_tenant_subdomain=method_settings.identity_tenant_subdomain,
|
|
49
50
|
mfa_type=method_settings.identity_mfa_method,
|
|
50
51
|
logger=self._logger,
|
|
51
52
|
cache_authentication=self._cache_authentication,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import codecs
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
|
+
import os
|
|
4
5
|
import re
|
|
5
6
|
import sys
|
|
6
7
|
import time
|
|
@@ -19,6 +20,7 @@ from requests import Session
|
|
|
19
20
|
from ark_sdk_python.args import ArkArgsFormatter, ArkInquirerRender
|
|
20
21
|
from ark_sdk_python.auth.identity.ark_identity_fqdn_resolver import ArkIdentityFQDNResolver
|
|
21
22
|
from ark_sdk_python.common import ArkKeyring, ArkSystemConfig, get_logger
|
|
23
|
+
from ark_sdk_python.common.env import AwsEnv
|
|
22
24
|
from ark_sdk_python.models import ArkException, ArkNonInteractiveException
|
|
23
25
|
from ark_sdk_python.models.ark_exceptions import ArkAuthException
|
|
24
26
|
from ark_sdk_python.models.ark_profile import ArkProfile
|
|
@@ -75,6 +77,7 @@ class ArkIdentity:
|
|
|
75
77
|
username: str,
|
|
76
78
|
password: Optional[str],
|
|
77
79
|
identity_url: Optional[str] = None,
|
|
80
|
+
identity_tenant_subdomain: Optional[str] = None,
|
|
78
81
|
mfa_type: Optional[str] = None,
|
|
79
82
|
logger: Optional[logging.Logger] = None,
|
|
80
83
|
cache_authentication: bool = True,
|
|
@@ -84,7 +87,7 @@ class ArkIdentity:
|
|
|
84
87
|
) -> None:
|
|
85
88
|
self.__username = username
|
|
86
89
|
self.__password = password
|
|
87
|
-
self.__identity_url =
|
|
90
|
+
self.__identity_url = self.__resolve_fqdn_from_username_or_subdomain(identity_url, identity_tenant_subdomain)
|
|
88
91
|
if not self.__identity_url.startswith('https://'):
|
|
89
92
|
self.__identity_url = f'https://{self.__identity_url}'
|
|
90
93
|
self.__mfa_type = mfa_type
|
|
@@ -158,7 +161,16 @@ class ArkIdentity:
|
|
|
158
161
|
f'{self.__username}_identity_session',
|
|
159
162
|
)
|
|
160
163
|
|
|
161
|
-
def
|
|
164
|
+
def __resolve_fqdn_from_username_or_subdomain(self, identity_url: Optional[str], identity_tenant_subdomain: Optional[str]) -> str:
|
|
165
|
+
if identity_tenant_subdomain and not identity_url:
|
|
166
|
+
try:
|
|
167
|
+
identity_url = ArkIdentityFQDNResolver.resolve_tenant_fqdn_from_tenant_subdomain(
|
|
168
|
+
identity_tenant_subdomain, AwsEnv(os.environ.get('DEPLOY_ENV', AwsEnv.PROD.value))
|
|
169
|
+
)
|
|
170
|
+
except Exception as ex:
|
|
171
|
+
self.__logger.warning(f'Failed to resolve url from tenant subdomain, falling back to user [{str(ex)}]')
|
|
172
|
+
if identity_url:
|
|
173
|
+
return identity_url
|
|
162
174
|
tenant_suffix = self.__username[self.__username.index('@') :]
|
|
163
175
|
return ArkIdentityFQDNResolver.resolve_tenant_fqdn_from_tenant_suffix(tenant_suffix=tenant_suffix)
|
|
164
176
|
|
|
@@ -166,7 +178,7 @@ class ArkIdentity:
|
|
|
166
178
|
self.__logger.info(f'Starting authentication with user {self.__username} and fqdn {self.__identity_url}')
|
|
167
179
|
response = self.__session.post(
|
|
168
180
|
url=f'{self.__identity_url}/Security/StartAuthentication',
|
|
169
|
-
json={'User': self.__username, 'Version': '1.0', 'PlatformTokenResponse': True},
|
|
181
|
+
json={'User': self.__username, 'Version': '1.0', 'PlatformTokenResponse': True, 'MfaRequestor': 'DeviceAgent'},
|
|
170
182
|
)
|
|
171
183
|
try:
|
|
172
184
|
parsed_res: StartAuthResponse = StartAuthResponse.parse_raw(response.text)
|
|
@@ -381,19 +393,26 @@ class ArkIdentity:
|
|
|
381
393
|
|
|
382
394
|
@classmethod
|
|
383
395
|
@cached(cache=LRUCache(maxsize=1024))
|
|
384
|
-
def is_idp_user(cls, username: str) -> bool:
|
|
396
|
+
def is_idp_user(cls, username: str, identity_url: Optional[str], identity_tenant_subdomain: Optional[str]) -> bool:
|
|
385
397
|
"""
|
|
386
398
|
Checks whether or not the specified username is from an external IDP.
|
|
387
399
|
|
|
388
400
|
Args:
|
|
389
401
|
username (str): _description_
|
|
402
|
+
identity_url (Optional[str]): _description_
|
|
403
|
+
identity_tenant_subdomain (Optional[str]): _description_
|
|
390
404
|
|
|
391
405
|
Returns:
|
|
392
406
|
bool: _description_
|
|
393
407
|
"""
|
|
394
408
|
if re.match('.*@cyberark\\.cloud\\.(\\d)+', username) is not None:
|
|
395
409
|
return False
|
|
396
|
-
identity = ArkIdentity(
|
|
410
|
+
identity = ArkIdentity(
|
|
411
|
+
username=username,
|
|
412
|
+
password='',
|
|
413
|
+
identity_url=identity_url,
|
|
414
|
+
identity_tenant_subdomain=identity_tenant_subdomain,
|
|
415
|
+
)
|
|
397
416
|
resp = identity.__start_authentication()
|
|
398
417
|
return resp.result.idp_redirect_url != None
|
|
399
418
|
|
|
@@ -31,6 +31,7 @@ CONFIGURATION_AUTHENTICATOR_IGNORED_INTERACTIVE_KEYS: Final[Dict[str, List[str]]
|
|
|
31
31
|
}
|
|
32
32
|
CONFIGURATION_ALLOWED_EMPTY_VALUES: Final[List[str]] = {
|
|
33
33
|
'isp_identity_url',
|
|
34
|
+
'isp_identity_tenant_subdomain',
|
|
34
35
|
}
|
|
35
36
|
CONFIGURATION_AUTHENTICATORS_DEFAULTS: Final[Dict[str, str]] = {}
|
|
36
37
|
CONFIGURATION_OVERRIDE_ALIASES: Final[Dict[str, str]] = {'region': 'Region', 'env': 'Environment'}
|
|
@@ -28,6 +28,12 @@ class IdentityArkAuthMethodSettings(ArkAuthMethodSettings):
|
|
|
28
28
|
identity_url: Optional[str] = Field(
|
|
29
29
|
description='Identity url to use for authentication instead of fqdn resolving', alias='Identity Url'
|
|
30
30
|
)
|
|
31
|
+
identity_tenant_subdomain: Optional[str] = Field(
|
|
32
|
+
description='Identity security platform tenant subdomain, '
|
|
33
|
+
'for exmaple mytenant.cyberark.cloud would be subdomained to mytenant. '
|
|
34
|
+
'this will be used instead of fqdn resolving from the username',
|
|
35
|
+
alias='Identity Tenant Subdomain',
|
|
36
|
+
)
|
|
31
37
|
|
|
32
38
|
|
|
33
39
|
class IdentityServiceUserArkAuthMethodSettings(ArkAuthMethodSettings):
|
|
@@ -66,9 +66,16 @@ class ArkDPASSOService(ArkService):
|
|
|
66
66
|
postfix=f'{claims["tenant_id"]}_{claims["unique_name"]}_dpa_sso_short_lived_{token_type}',
|
|
67
67
|
)
|
|
68
68
|
|
|
69
|
+
def __expand_folder(self, folder: str) -> str:
|
|
70
|
+
folder_path = os.path.expanduser(folder)
|
|
71
|
+
if not folder_path.endswith('/'):
|
|
72
|
+
folder_path += '/'
|
|
73
|
+
return folder_path
|
|
74
|
+
|
|
69
75
|
def __output_client_certificate(
|
|
70
76
|
self, folder: str, output_format: ArkDPASSOShortLiveClientCertificateFormat, result: ArkDPASSOAcquireTokenResponse
|
|
71
77
|
) -> None:
|
|
78
|
+
folder_path = self.__expand_folder(folder)
|
|
72
79
|
claims = get_unverified_claims(self.__client.session_token)
|
|
73
80
|
client_certificate = result.token['client_certificate']
|
|
74
81
|
private_key = result.token['private_key']
|
|
@@ -82,24 +89,24 @@ class ArkDPASSOService(ArkService):
|
|
|
82
89
|
)
|
|
83
90
|
ArkArgsFormatter.print_normal(f'client-key-data: {base64.b64encode(private_key.encode("utf-8")).decode("utf-8")}')
|
|
84
91
|
elif output_format == ArkDPASSOShortLiveClientCertificateFormat.FILE:
|
|
85
|
-
if not
|
|
92
|
+
if not folder_path:
|
|
86
93
|
raise ArkServiceException(
|
|
87
94
|
f'Folder parameter is required if format is {ArkDPASSOShortLiveClientCertificateFormat.FILE.value}'
|
|
88
95
|
)
|
|
89
|
-
if not os.path.exists(
|
|
90
|
-
os.makedirs(
|
|
91
|
-
with open(f'{
|
|
96
|
+
if not os.path.exists(folder_path):
|
|
97
|
+
os.makedirs(folder_path)
|
|
98
|
+
with open(f'{folder_path}{os.path.sep}{claims["unique_name"]}client_cert.crt', 'w', encoding='utf-8') as file_handle:
|
|
92
99
|
file_handle.write(client_certificate)
|
|
93
|
-
with open(f'{
|
|
100
|
+
with open(f'{folder_path}{os.path.sep}{claims["unique_name"]}client_key.pem', 'w', encoding='utf-8') as file_handle:
|
|
94
101
|
file_handle.write(private_key)
|
|
95
102
|
elif output_format == ArkDPASSOShortLiveClientCertificateFormat.SINGLE_FILE:
|
|
96
103
|
if not folder:
|
|
97
104
|
raise ArkServiceException(
|
|
98
105
|
f'Folder parameter is required if format is {ArkDPASSOShortLiveClientCertificateFormat.FILE.value}'
|
|
99
106
|
)
|
|
100
|
-
if not os.path.exists(
|
|
101
|
-
os.makedirs(
|
|
102
|
-
with open(f'{
|
|
107
|
+
if not os.path.exists(folder_path):
|
|
108
|
+
os.makedirs(folder_path)
|
|
109
|
+
with open(f'{folder_path}{os.path.sep}{claims["unique_name"]}client_cert.pem', 'w', encoding='utf-8') as file_handle:
|
|
103
110
|
file_handle.write(client_certificate)
|
|
104
111
|
file_handle.write('\n')
|
|
105
112
|
file_handle.write(private_key)
|
|
@@ -107,32 +114,35 @@ class ArkDPASSOService(ArkService):
|
|
|
107
114
|
raise ArkServiceException(f'Unknown format {output_format}')
|
|
108
115
|
|
|
109
116
|
def __save_oracle_sso_wallet(self, folder: str, unzip_wallet: bool, result: ArkDPASSOAcquireTokenResponse) -> None:
|
|
117
|
+
folder_path = self.__expand_folder(folder)
|
|
110
118
|
result.token['wallet'] = base64.b64decode(result.token['wallet'])
|
|
111
|
-
if not os.path.exists(
|
|
112
|
-
os.makedirs(
|
|
119
|
+
if not os.path.exists(folder_path):
|
|
120
|
+
os.makedirs(folder_path)
|
|
113
121
|
if not unzip_wallet:
|
|
114
|
-
with open(f'{
|
|
122
|
+
with open(f'{folder_path}{os.path.sep}wallet.zip', 'wb') as file_handle:
|
|
115
123
|
file_handle.write(result.token['wallet'])
|
|
116
124
|
else:
|
|
117
125
|
wallet_bytes = BytesIO(result.token['wallet'])
|
|
118
126
|
with zipfile.ZipFile(wallet_bytes, 'r') as zipf:
|
|
119
|
-
zipf.extractall(
|
|
127
|
+
zipf.extractall(folder_path)
|
|
120
128
|
|
|
121
129
|
def __save_oracle_pem_wallet(self, folder: str, result: ArkDPASSOAcquireTokenResponse) -> None:
|
|
130
|
+
folder_path = self.__expand_folder(folder)
|
|
122
131
|
claims = get_unverified_claims(self.__client.session_token)
|
|
123
132
|
pem_wallet = base64.b64decode(result.token['pem_wallet']).decode('utf-8')
|
|
124
|
-
if not os.path.exists(
|
|
125
|
-
os.makedirs(
|
|
126
|
-
with open(f'{
|
|
133
|
+
if not os.path.exists(folder_path):
|
|
134
|
+
os.makedirs(folder_path)
|
|
135
|
+
with open(f'{folder_path}{os.path.sep}{claims["unique_name"]}ewallet.pem', 'w', encoding='utf-8') as file_handle:
|
|
127
136
|
file_handle.write(pem_wallet)
|
|
128
137
|
|
|
129
138
|
def __save_rdp_file(self, get_short_lived_rdp_file: ArkDPASSOGetShortLivedRDPFile, result: ArkDPASSOAcquireTokenResponse) -> None:
|
|
130
|
-
|
|
131
|
-
|
|
139
|
+
folder_path = self.__expand_folder(get_short_lived_rdp_file.folder)
|
|
140
|
+
if not os.path.exists(folder_path):
|
|
141
|
+
os.makedirs(folder_path)
|
|
132
142
|
filename: str = f'dpa _a {get_short_lived_rdp_file.target_address}'
|
|
133
143
|
if get_short_lived_rdp_file.target_domain:
|
|
134
144
|
filename += f' _d {get_short_lived_rdp_file.target_domain}'
|
|
135
|
-
with open(f'{
|
|
145
|
+
with open(f'{folder_path}{filename}.rdp', 'w', encoding='utf-8') as file_handle:
|
|
136
146
|
file_handle.write(result.token['text'])
|
|
137
147
|
|
|
138
148
|
def short_lived_password(self, get_short_lived_password: ArkDPASSOGetShortLivedPassword) -> str:
|