Skip to content

Commit 70d0cb8

Browse files
committed
Updates for multiregion changes
1 parent fe3daf2 commit 70d0cb8

9 files changed

Lines changed: 188 additions & 17 deletions

File tree

example_sdkrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# SPLUNK HOSTS
22
export SPLUNK_AUTH_HOST=<REDACTED>
3+
export SPLUNK_AUTH_HOST_2=<REDACTED>
34
export SPLUNK_AUTHN_URL=<REDACTED>
45
export SPLUNK_HOST=<REDACTED>
56
export SPLUNK_APP_SERVER=<REDACTED>
67
export SPLUNK_DEBUG=<REDACTED>
8+
export SPLUNK_HOST_2=<REDACTED>
79

810
# Choose one set of vars for PKCE or CLIENT auth flows
911

@@ -18,4 +20,7 @@ export SPLUNK_REDIRECT_URL=<REDACTED>
1820
# CLIENT CREDENTIAL FLOW
1921
export SPLUNK_APP_CLIENT_CRED_ID=<REDACTED>
2022
export SPLUNK_APP_CLIENT_CRED_SECRET=<REDACTED>
23+
export SPLUNK_APP_CLIENT_CRED_ID_2=<REDACTED>
24+
export SPLUNK_APP_CLIENT_CRED_SECRET_2=<REDACTED>
25+
export SPLUNK_TENANT_2=<REDACTED>
2126
export SPLUNK_SCOPE=<REDACTED>

splunk_sdk/auth/auth_manager.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,11 @@ class AuthManager(ABC):
161161
the flow that you need for your app.
162162
"""
163163

164-
def __init__(self, host, client_id, requests_hooks=None):
164+
def __init__(self, host, client_id, tenant=None, tenant_scoped=False, region=None, requests_hooks=None):
165165
self._host = host
166+
self._tenant = tenant
167+
self._tenant_scoped = tenant_scoped
168+
self._region = region
166169
self._client_id = client_id
167170
self._context = None
168171
self._requests_hooks = requests_hooks or []
@@ -190,6 +193,13 @@ def _post(self, url, auth=None, headers=None, data=None, cookies=None):
190193
return response
191194

192195
def _url(self, path):
196+
if self._tenant_scoped is True and self._tenant is not None and self._tenant != "system":
197+
self._host = self._tenant + "." + self._host
198+
# generate tenant scoped token
199+
os.path.join("/", self._tenant, path)
200+
# ToDo:Region+system scoped tokens will be added once implementation details are finalized in multi-cell
201+
# else:
202+
print("https://%s%s" % (self._host, path))
193203
return "https://%s%s" % (self._host, path)
194204

195205
@staticmethod
@@ -240,8 +250,8 @@ class ClientAuthManager(AuthManager):
240250
"""
241251

242252
# TODO: Host can be an optional value since it has a default
243-
def __init__(self, host, client_id, client_secret, scope="", requests_hooks=None):
244-
super().__init__(host, client_id, requests_hooks=requests_hooks)
253+
def __init__(self, host, client_id, client_secret, scope="", tenant=None, tenant_scoped=False, region=None, requests_hooks=None):
254+
super().__init__(host, client_id, tenant, tenant_scoped, region, requests_hooks=requests_hooks)
245255
self._client_secret = client_secret
246256
self._scope = scope
247257

@@ -269,8 +279,9 @@ class PKCEAuthManager(AuthManager):
269279
and password, the app through the client_id and redirect_uri. For more details, see identity service documentation.
270280
"""
271281

272-
def __init__(self, host, client_id, redirect_uri, username, password, scope=DEFAULT_REFRESH_SCOPE, requests_hooks=None):
273-
super().__init__(host, client_id, requests_hooks=requests_hooks)
282+
def __init__(self, host, client_id, redirect_uri, username, password, scope=DEFAULT_REFRESH_SCOPE, tenant=None,
283+
tenant_scoped=False, region=None, requests_hooks=None):
284+
super().__init__(host, client_id, tenant, tenant_scoped, region, requests_hooks=requests_hooks)
274285
self._redirect_uri = redirect_uri
275286
self._username = username
276287
self._password = password
@@ -336,6 +347,8 @@ def _authenticate_user(self, username, password, csrfToken, cookies):
336347
data = {"username": username, "password": password, "csrfToken": csrfToken}
337348
response = self._post(self._url(PATH_AUTHN), data=json.dumps(data), cookies=cookies)
338349
if response.status_code != 200:
350+
print("RESPONSE")
351+
print(response)
339352
raise AuthnError("Authentication failed.", response)
340353
result = response.json()
341354
status = result.get("status", None)
@@ -432,11 +445,12 @@ def authenticate(self) -> AuthContext:
432445

433446

434447
class RefreshTokenAuthManager(AuthManager):
435-
def __init__(self, client_id, refresh_token, host, scope="openid", requests_hooks=None):
436-
super().__init__(host, client_id, requests_hooks=requests_hooks)
448+
def __init__(self, client_id, refresh_token, host, scope="openid", tenant=None, tenant_scoped=False, region=None, requests_hooks=None):
449+
super().__init__(host, client_id, tenant, tenant_scoped, region, requests_hooks=requests_hooks)
437450
self._refresh_token = refresh_token
438451
self._scope = scope
439452

453+
440454
def authenticate(self):
441455
"""Authenticate using the OAuth refresh_token grant type."""
442456
client_id = self._client_id
@@ -454,7 +468,8 @@ def authenticate(self):
454468

455469

456470
class ServicePrincipalAuthManager(AuthManager):
457-
def __init__(self, host, principal_name, key, kid, algorithm="ES256", **kwargs):
471+
def __init__(self, host, principal_name, key, kid, algorithm="ES256", tenant=None,
472+
tenant_scoped=False, region=None, **kwargs):
458473
"""
459474
Creates an AuthManager that uses Service Principals to authenticate.
460475
@@ -463,7 +478,7 @@ def __init__(self, host, principal_name, key, kid, algorithm="ES256", **kwargs):
463478
kid is the key_id of `key`
464479
algorithm is the algorithm that generated `key`
465480
"""
466-
super().__init__(host=host, client_id=None, **kwargs)
481+
super().__init__(host=host, client_id=None, tenant=tenant, tenant_scoped=tenant_scoped, region=region, **kwargs)
467482
self._principal_name = principal_name
468483
self._key = key
469484
self._kid = kid

splunk_sdk/base_client.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,13 @@ def build_url(self, route: str, **kwargs) -> str:
238238
:param kwargs: TODO DOCS
239239
:return: The full URL.
240240
"""
241-
url = self.context.scheme + "://" + self.context.host
241+
if self.context.tenant_scoped is True and self.context.tenant != "system" and route.startswith("/system") is False:
242+
url = self.context.scheme + "://" + self.context.tenant + "." + self.context.host
243+
elif self.context.tenant_scoped is True and route.startswith("/system/") and self.context.region is not None:
244+
url = self.context.scheme + "://region-" + self.context.region + "." + self.context.host
245+
else:
246+
url = self.context.scheme + "://" + self.context.host
247+
242248
if self.context.port is not None and self.context.port != "":
243249
url += ":" + self.context.port
244250

@@ -247,7 +253,6 @@ def build_url(self, route: str, **kwargs) -> str:
247253
if route.startswith("/system/") is False and omit_tenant is False:
248254
url += "/" + self.get_tenant()
249255
url += route
250-
251256
# set any url path vars
252257
if len(kwargs) > 0:
253258
url = url.format(**kwargs)

splunk_sdk/common/context.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,23 @@ class Context(object):
1818
Example:
1919
client = SplunkCloud(Context(tenant="mytenant"), authManager)
2020
client.identity.validate()
21+
22+
Configure tenant_scoped of bool type to True if the hostnames are scoped to a specific tenant/region to support
23+
multi-cell environments, currently defaults to False
24+
Configure Region as the name of the region that the tenant is contained in
25+
}
2126
"""
2227

2328
DEFAULT_API_HOST = "api.scp.splunk.com"
2429
DEFAULT_HOST = DEFAULT_API_HOST
2530

2631
def __init__(self, host=DEFAULT_HOST, api_host=DEFAULT_API_HOST,
27-
port=None, scheme="https", tenant='system', debug=False):
32+
port=None, scheme="https", tenant='system', tenant_scoped=False, region=None, debug=False):
2833
self.host = host
2934
self.api_host = api_host
3035
self.port = port
3136
self.scheme = scheme
3237
self.tenant = tenant
3338
self.debug = debug
39+
self.tenant_scoped = tenant_scoped
40+
self.region = region

test/auth/test_auth_manager.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from splunk_sdk.auth import PKCEAuthManager, ClientAuthManager
1111
from test.fixtures import get_auth_manager as pkce_auth_manager # NOQA
1212
from test.fixtures import get_client_auth_manager as client_auth_manager # NOQA
13+
from test.fixtures import get_client_auth_manager_scoped as client_auth_manager_scoped
1314
from test.fixtures import get_service_principal_auth_manager as service_principal_auth_manager # NOQA
1415

1516

@@ -66,6 +67,10 @@ def test_client_credentials_authenticate(client_auth_manager):
6667
auth_context = client_auth_manager.authenticate()
6768
_assert_client_credentials_auth_context(auth_context)
6869

70+
@pytest.mark.usefixtures('client_auth_manager') # NOQA
71+
def test_client_credentials_authenticate_scoped(client_auth_manager_scoped):
72+
auth_context = client_auth_manager_scoped.authenticate()
73+
_assert_client_credentials_auth_context(auth_context)
6974

7075
@pytest.mark.usefixtures('client_auth_manager') # NOQA
7176
def test_token_authenticate(client_auth_manager):
@@ -192,7 +197,7 @@ def _assert_client_credentials_auth_context(auth_context, expires_in=43200):
192197

193198

194199
def _assert_sp_credentials_auth_context(auth_context):
195-
_assert_client_credentials_auth_context(auth_context, 3600)
200+
_assert_client_credentials_auth_context(auth_context)
196201

197202

198203
@pytest.mark.usefixtures('service_principal_auth_manager') # NOQA

test/fixtures.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,22 @@ def get_test_client():
9696
assert (service_client is not None)
9797
return service_client
9898

99+
@pytest.fixture(scope='session')
100+
def get_test_client_scoped_hosts():
101+
context = Context(host=os.environ.get('SPLUNK_HOST_2'),
102+
api_host=os.environ.get('SPLUNK_HOST_2'),
103+
tenant=os.environ.get('SPLUNK_TENANT_2'),
104+
tenant_scoped=True,
105+
# Uncomment when services are deployed at region scoped
106+
# region=os.environ.get('SPLUNK_REGION'),
107+
debug=os.environ.get(
108+
'SPLUNK_DEBUG', 'false').lower().strip() == 'true')
109+
110+
# integration tests use pkce by default
111+
service_client = get_client(context, _get_client_manager_scoped())
112+
assert (service_client is not None)
113+
return service_client
114+
99115
@pytest.fixture(scope='session')
100116
def get_test_client_ml():
101117
context = Context(host=os.environ.get('SPLUNK_HOST'),
@@ -138,6 +154,11 @@ def get_client_auth_manager():
138154
assert (auth_manager is not None)
139155
return auth_manager
140156

157+
@pytest.fixture(scope='session')
158+
def get_client_auth_manager_scoped():
159+
auth_manager = _get_client_manager_scoped()
160+
assert (auth_manager is not None)
161+
return auth_manager
141162

142163
@pytest.fixture(scope='session')
143164
def get_service_principal_auth_manager():
@@ -162,10 +183,19 @@ def _get_client_manager():
162183
'SPLUNK_APP_CLIENT_CRED_SECRET'),
163184
scope=os.environ.get('SPLUNK_SCOPE'))
164185

186+
def _get_client_manager_scoped():
187+
return ClientAuthManager(host=os.environ.get('SPLUNK_AUTH_HOST_2'),
188+
client_id=os.environ.get(
189+
'SPLUNK_APP_CLIENT_CRED_ID_2'),
190+
client_secret=os.environ.get(
191+
'SPLUNK_APP_CLIENT_CRED_SECRET_2'),
192+
scope=os.environ.get('SPLUNK_SCOPE'), tenant=os.environ.get('SPLUNK_TENANT_2'), tenant_scoped=True, region=os.environ.get(
193+
'SPLUNK_REGION'))
165194

166195
def _get_principal_manager():
167196
return ServicePrincipalAuthManager(host=os.environ.get('SPLUNK_AUTH_HOST'),
168197
principal_name=os.environ.get('SPLUNK_APP_PRINCIPAL_NAME'),
169198
key=os.environ.get('SPLUNK_APP_PRINCIPAL_PRIVATE_KEY'),
170199
kid=os.environ.get('SPLUNK_APP_PRINCIPAL_KEY_ID'),
171200
algorithm=os.environ.get('SPLUNK_APP_PRINCIPAL_KEY_ALG'))
201+

test/test_base_client.py

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@
1212
import pytest
1313

1414
from test.auth.test_auth_manager import \
15-
_assert_client_credentials_auth_context, _assert_pkce_auth_context, _assert_sp_credentials_auth_context # NOQA
15+
_assert_client_credentials_auth_context, _assert_pkce_auth_context, _assert_sp_credentials_auth_context # NOQA
1616
from test.fixtures import get_auth_manager as pkce_auth_manager # NOQA
1717
from test.fixtures import get_client_auth_manager as client_auth_manager # NOQA
18+
from test.fixtures import get_client_auth_manager_scoped as client_auth_manager_scoped # NOQA
1819
from test.fixtures import get_service_principal_auth_manager as service_principal_auth_manager # NOQA
1920
from splunk_sdk.common.context import Context
2021
from splunk_sdk.base_client import BaseClient
2122
from splunk_sdk.identity import Identity as IdentityAndAccessControl
2223

23-
2424
@pytest.mark.usefixtures("pkce_auth_manager") # NOQA
2525
def test_base_client_instance_with_pkce_auth(pkce_auth_manager):
2626
"""Get a base client with a context and a pkce auth manager."""
@@ -65,7 +65,6 @@ def test_base_client_empty_hooks_with_pkce_auth(pkce_auth_manager):
6565
IdentityAndAccessControl(base_client).validate_token()
6666
assert True
6767

68-
6968
@pytest.mark.usefixtures("pkce_auth_manager") # NOQA
7069
def test_base_client_no_list_hooks_with_pkce_auth(pkce_auth_manager):
7170
def test_hook(*args, **kwargs):
@@ -85,6 +84,105 @@ def test_base_client_instance_with_client_auth(client_auth_manager):
8584
assert (base_client is not None)
8685
_assert_client_credentials_auth_context(base_client.auth_manager.context)
8786

87+
@pytest.mark.usefixtures("client_auth_manager_scoped") # NOQA
88+
def test_base_client_instance_with_client_auth_tenant_scoped(client_auth_manager_scoped):
89+
"""Get a base client with a context and a client auth manager."""
90+
context = Context(tenant_scoped=True,
91+
tenant=os.environ.get('SPLUNK_TENANT_2'),
92+
host=os.environ.get('SPLUNK_HOST_2'),
93+
api_host=os.environ.get('SPLUNK_HOST_2'),
94+
region=os.environ.get('SPLUNK_REGION'),
95+
debug=os.environ.get(
96+
'SPLUNK_DEBUG', 'false').lower().strip() == 'true')
97+
98+
base_client = BaseClient(context=context,
99+
auth_manager=client_auth_manager_scoped)
100+
assert (base_client is not None)
101+
assert (base_client.context.tenant_scoped is True)
102+
assert (base_client.context.tenant == os.environ.get('SPLUNK_TENANT_2'))
103+
assert (base_client.context.region == os.environ.get('SPLUNK_REGION'))
104+
105+
tenant_path = "/exampleservice/path"
106+
system_path = "/system/exampleservice/path"
107+
108+
url_tenant = base_client.build_url(tenant_path)
109+
expected_tenant_url_path_template = 'https://%s.%s/%s%s'
110+
expected_system_url_path_template = 'https://region-%s.%s%s'
111+
112+
expected_tenant_url_path = expected_tenant_url_path_template % (os.environ.get('SPLUNK_TENANT_2'), os.environ.get('SPLUNK_HOST_2'), os.environ.get('SPLUNK_TENANT_2'), tenant_path)
113+
assert (url_tenant == expected_tenant_url_path)
114+
115+
expected_system_url_path = expected_system_url_path_template % (os.environ.get('SPLUNK_REGION'), os.environ.get('SPLUNK_HOST_2'), system_path)
116+
url_system = base_client.build_url(system_path)
117+
assert (url_system == expected_system_url_path)
118+
119+
_assert_client_credentials_auth_context(base_client.auth_manager.context)
120+
121+
@pytest.mark.usefixtures("client_auth_manager_scoped") # NOQA
122+
def test_base_client_instance_with_client_auth_scoped_off(client_auth_manager):
123+
"""Get a base client with a context and a client auth manager."""
124+
context = Context(tenant_scoped=False,
125+
tenant=os.environ.get('SPLUNK_TENANT'),
126+
host=os.environ.get('SPLUNK_HOST'),
127+
api_host=os.environ.get('SPLUNK_HOST'),
128+
region=os.environ.get('SPLUNK_REGION'),
129+
debug=os.environ.get(
130+
'SPLUNK_DEBUG', 'false').lower().strip() == 'true')
131+
132+
base_client = BaseClient(context=context,
133+
auth_manager=client_auth_manager)
134+
assert (base_client is not None)
135+
assert (base_client.context.tenant_scoped is False)
136+
assert (base_client.context.tenant == os.environ.get('SPLUNK_TENANT'))
137+
assert (base_client.context.region == os.environ.get('SPLUNK_REGION'))
138+
139+
tenant_path = "/exampleservice/path"
140+
system_path = "/system/exampleservice/path"
141+
142+
url_tenant = base_client.build_url(tenant_path)
143+
expected_tenant_url_path_template = 'https://%s/%s%s'
144+
expected_system_url_path_template = 'https://%s%s'
145+
146+
expected_tenant_url_path = expected_tenant_url_path_template % (os.environ.get('SPLUNK_HOST'), os.environ.get('SPLUNK_TENANT'), tenant_path)
147+
assert (url_tenant == expected_tenant_url_path)
148+
149+
expected_system_url_path = expected_system_url_path_template % (os.environ.get('SPLUNK_HOST'), system_path)
150+
url_system = base_client.build_url(system_path)
151+
assert (url_system == expected_system_url_path)
152+
153+
_assert_client_credentials_auth_context(base_client.auth_manager.context)
154+
155+
@pytest.mark.usefixtures("client_auth_manager_scoped") # NOQA
156+
def test_base_client_instance_with_client_auth_default(client_auth_manager):
157+
"""Get a base client with a context and a client auth manager."""
158+
context = Context(tenant=os.environ.get('SPLUNK_TENANT'),
159+
host=os.environ.get('SPLUNK_HOST'),
160+
api_host=os.environ.get('SPLUNK_HOST'),
161+
debug=os.environ.get(
162+
'SPLUNK_DEBUG', 'false').lower().strip() == 'true')
163+
164+
base_client = BaseClient(context=context,
165+
auth_manager=client_auth_manager)
166+
assert (base_client is not None)
167+
assert (base_client.context.tenant_scoped is False)
168+
assert (base_client.context.tenant == os.environ.get('SPLUNK_TENANT'))
169+
assert (base_client.context.region is None)
170+
171+
tenant_path = "/exampleservice/path"
172+
system_path = "/system/exampleservice/path"
173+
174+
url_tenant = base_client.build_url(tenant_path)
175+
expected_tenant_url_path_template = 'https://%s/%s%s'
176+
expected_system_url_path_template = 'https://%s%s'
177+
178+
expected_tenant_url_path = expected_tenant_url_path_template % (os.environ.get('SPLUNK_HOST'), os.environ.get('SPLUNK_TENANT'), tenant_path)
179+
assert (url_tenant == expected_tenant_url_path)
180+
181+
expected_system_url_path = expected_system_url_path_template % (os.environ.get('SPLUNK_HOST'), system_path)
182+
url_system = base_client.build_url(system_path)
183+
assert (url_system == expected_system_url_path)
184+
185+
_assert_client_credentials_auth_context(base_client.auth_manager.context)
88186

89187
@pytest.mark.usefixtures("service_principal_auth_manager") # NOQA
90188
def test_base_client_instance_with_sp_auth(service_principal_auth_manager):

test/test_forwarders.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
from splunk_sdk.base_client import BaseClient
2121
from splunk_sdk.forwarders import SplunkForwarderService, Certificate
22-
from test.fixtures import get_test_client as test_client # NOQA
22+
from test.fixtures import get_test_client_scoped_hosts as test_client # NOQA
2323

2424

2525
@pytest.mark.usefixtures("test_client") # NOQA

0 commit comments

Comments
 (0)