Skip to content

Commit 3bca5f1

Browse files
KNOX-3293: Add integration tests: health metrics JSON, KnoxLDAP preauth/extauthz paths, RemoteAuth extauthz (#1194)
* KNOX-3293: Add integration tests: health metrics JSON, KnoxLDAP preauth/extauthz paths, RemoteAuth extauthz * Refactoered tests and made a common util file
1 parent fa75d21 commit 3bca5f1

7 files changed

Lines changed: 239 additions & 77 deletions
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one or more
2+
# contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright ownership.
4+
# The ASF licenses this file to you under the Apache License, Version 2.0
5+
# (the "License"); you may not use this file except in compliance with
6+
# the License. You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
from __future__ import annotations
17+
18+
import os
19+
import unittest
20+
from typing import Any
21+
22+
import requests
23+
import urllib3
24+
25+
# Default timeout for HTTP calls to the gateway (self-signed TLS, CI).
26+
KNOX_REQUEST_TIMEOUT = 30
27+
28+
HSTS_HEADER_NAME = "Strict-Transport-Security"
29+
HSTS_EXPECTED_VALUE = "max-age=300; includeSubDomains"
30+
31+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
32+
33+
34+
def gateway_base_url() -> str:
35+
"""Return KNOX_GATEWAY_URL with a trailing slash."""
36+
url = os.environ.get("KNOX_GATEWAY_URL", "https://localhost:8443/")
37+
return url if url.endswith("/") else (url + "/")
38+
39+
40+
def knox_get(url: str, **kwargs: Any) -> requests.Response:
41+
"""GET against Knox with verify=False and default timeout unless overridden."""
42+
opts: dict[str, Any] = {"verify": False, "timeout": KNOX_REQUEST_TIMEOUT}
43+
opts.update(kwargs)
44+
return requests.get(url, **opts)
45+
46+
47+
def knox_post(url: str, **kwargs: Any) -> requests.Response:
48+
"""POST against Knox with verify=False and default timeout unless overridden."""
49+
opts: dict[str, Any] = {"verify": False, "timeout": KNOX_REQUEST_TIMEOUT}
50+
opts.update(kwargs)
51+
return requests.post(url, **opts)
52+
53+
54+
def collect_actor_group_values(
55+
response: requests.Response, prefix: str = "x-knox-actor-groups"
56+
) -> list[str]:
57+
"""Comma-split values from all response headers whose names start with prefix (case-insensitive)."""
58+
prefix_lower = prefix.lower()
59+
all_groups: list[str] = []
60+
for name in response.headers:
61+
if name.lower().startswith(prefix_lower):
62+
all_groups.extend(response.headers[name].split(","))
63+
return all_groups
64+
65+
66+
def assert_hsts_header(testcase: unittest.TestCase, response: requests.Response) -> None:
67+
testcase.assertIn(HSTS_HEADER_NAME, response.headers)
68+
testcase.assertEqual(response.headers[HSTS_HEADER_NAME], HSTS_EXPECTED_VALUE)

.github/workflows/tests/test_health.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,47 @@
1212
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
15-
import os
15+
import json
1616
import unittest
17+
1718
import requests
18-
import urllib3
1919

20-
# Suppress InsecureRequestWarning
21-
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
20+
from common_utils import assert_hsts_header, gateway_base_url, knox_get
21+
2222

2323
class TestKnoxHealth(unittest.TestCase):
24-
def test_admin_api_health(self):
24+
def setUp(self):
25+
self.base_url = gateway_base_url()
26+
27+
def test_health_ping_ok_and_hsts(self):
2528
"""
2629
Basic health check to ensure Knox is up and running.
27-
We expect a response 200 to indicate the server is up.
2830
"""
29-
url = os.environ.get("KNOX_GATEWAY_URL", "https://localhost:8443/")
31+
url = self.base_url + "gateway/health/v1/ping"
3032
print(f"Checking connectivity to {url}...")
3133
try:
32-
response = requests.get(url + "health/v1/ping", verify=False, timeout=30)
34+
response = knox_get(url)
3335
print(f"Received status code: {response.status_code}")
3436
self.assertEqual(response.status_code, 200)
37+
self.assertEqual(response.text.strip(), "OK")
38+
39+
assert_hsts_header(self, response)
3540
except requests.exceptions.ConnectionError:
3641
self.fail("Failed to connect to Knox on port 8443 - Connection refused")
3742
except Exception as e:
3843
self.fail(f"Health check failed with unexpected error: {e}")
3944

45+
def test_health_metrics_returns_json(self):
46+
url = self.base_url + "gateway/health/v1/metrics?pretty=true"
47+
response = knox_get(url)
48+
self.assertEqual(response.status_code, 200)
49+
50+
content_type = response.headers.get("Content-Type", "")
51+
self.assertIn("application/json", content_type)
52+
53+
payload = json.loads(response.text)
54+
self.assertIsInstance(payload, dict)
55+
4056
if __name__ == '__main__':
4157
unittest.main()
4258

.github/workflows/tests/test_knox_auth_service_and_LDAP.py

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,11 @@
1212
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
15-
import os
1615
import unittest
17-
import requests
18-
import urllib3
1916
from requests.auth import HTTPBasicAuth
2017

18+
from common_utils import collect_actor_group_values, gateway_base_url, knox_get
19+
2120
########################################################
2221
# This test is verifying the behavior of the Knox Auth Service + LDAP authentication.
2322
# It is using the 'auth/api/v1/pre' endpoint to get the actor ID and group headers.
@@ -28,14 +27,9 @@
2827
# It is verifying that the actor ID and group headers are not None.
2928
########################################################
3029

31-
# Suppress InsecureRequestWarning
32-
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
33-
3430
class TestKnoxAuthService(unittest.TestCase):
3531
def setUp(self):
36-
self.base_url = os.environ.get("KNOX_GATEWAY_URL", "https://localhost:8443/")
37-
if not self.base_url.endswith("/"):
38-
self.base_url += "/"
32+
self.base_url = gateway_base_url()
3933
# The topology name is based on the filename knoxldap.xml
4034
self.topology_url = self.base_url + "gateway/knoxldap/auth/api/v1/extauthz"
4135

@@ -44,11 +38,9 @@ def test_auth_service_guest(self):
4438
Verify that guest user gets the correct actor ID header.
4539
"""
4640
print(f"\nTesting guest authentication against {self.topology_url}")
47-
response = requests.get(
48-
self.topology_url,
41+
response = knox_get(
42+
self.topology_url,
4943
auth=HTTPBasicAuth('guest', 'guest-password'),
50-
verify=False,
51-
timeout=30
5244
)
5345

5446
print(f"Status Code: {response.status_code}")
@@ -66,11 +58,9 @@ def test_auth_service_admin_groups(self):
6658
Verify that admin user gets actor ID and group headers.
6759
"""
6860
print(f"\nTesting admin authentication against {self.topology_url}")
69-
response = requests.get(
70-
self.topology_url,
61+
response = knox_get(
62+
self.topology_url,
7163
auth=HTTPBasicAuth('admin', 'admin-password'),
72-
verify=False,
73-
timeout=30
7464
)
7565

7666
print(f"Status Code: {response.status_code}")
@@ -82,22 +72,15 @@ def test_auth_service_admin_groups(self):
8272
self.assertEqual(response.headers[actor_id_header], 'admin')
8373
print(f"Verified {actor_id_header}: {response.headers[actor_id_header]}")
8474

85-
# Check for Group headers
8675
# Config: 'preauth.auth.header.actor.groups.prefix' = 'x-knox-actor-groups'
8776
# We mapped admin to 'longGroupName1,longGroupName2,longGroupName3,longGroupName4'
88-
89-
# We just verify that at least one header starting with the prefix exists
9077
prefix = 'x-knox-actor-groups'
91-
group_headers = [h for h in response.headers.keys() if h.lower().startswith(prefix.lower())]
92-
93-
self.assertTrue(len(group_headers) > 0, f"No headers found starting with {prefix}")
94-
95-
# Verify content of groups
96-
all_groups = []
97-
for h in group_headers:
98-
all_groups.extend(response.headers[h].split(','))
99-
print(f"Found group header {h}: {response.headers[h]}")
100-
78+
all_groups = collect_actor_group_values(response, prefix=prefix)
79+
self.assertTrue(len(all_groups) > 0, f"No headers found starting with {prefix}")
80+
for h in response.headers:
81+
if h.lower().startswith(prefix.lower()):
82+
print(f"Found group header {h}: {response.headers[h]}")
83+
10184
expected_groups = ['longGroupName1', 'longGroupName2', 'longGroupName3', 'longGroupName4']
10285
for group in expected_groups:
10386
self.assertIn(group, all_groups)

.github/workflows/tests/test_knox_configs.py

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,46 +12,36 @@
1212
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
15-
import os
1615
import unittest
17-
import requests
18-
import urllib3
1916
from requests.auth import HTTPBasicAuth
2017

18+
from common_utils import assert_hsts_header, gateway_base_url, knox_get
19+
2120

2221
########################################################
2322
# This test is verifying the global HSTS headers for 404 response.
2423
# It executes new GET request on non-existent Knox path
2524
# It verifies header is present with the correct value.
2625
########################################################
2726

28-
# Suppress InsecureRequestWarning
29-
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
30-
3127
class TestKnoxConfigs(unittest.TestCase):
3228
def setUp(self):
33-
self.base_url = os.environ.get("KNOX_GATEWAY_URL", "https://localhost:8443/")
34-
if not self.base_url.endswith("/"):
35-
self.base_url += "/"
29+
self.base_url = gateway_base_url()
3630
self.non_existent_path = self.base_url + "gateway/not-exists"
3731

3832
def test_auth_service_guest(self):
3933
"""
4034
Verifies header is present with the correct value
4135
"""
4236
print(f"\nTesting global HSTS config for 404 response")
43-
response = requests.get(
37+
response = knox_get(
4438
self.non_existent_path,
4539
auth=HTTPBasicAuth('admin', 'admin-password'),
46-
verify=False,
47-
timeout=30
4840
)
4941

5042
print(f"Status Code: {response.status_code}")
5143
self.assertEqual(response.status_code, 404)
5244

53-
hsts_header = 'Strict-Transport-Security'
54-
self.assertIn(hsts_header, response.headers)
55-
self.assertEqual(response.headers[hsts_header], 'max-age=300; includeSubDomains')
56-
print(f"Verified {hsts_header}: {response.headers[hsts_header]}")
45+
assert_hsts_header(self, response)
46+
print(f"Verified Strict-Transport-Security: {response.headers['Strict-Transport-Security']}")
5747

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one or more
2+
# contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright ownership.
4+
# The ASF licenses this file to you under the Apache License, Version 2.0
5+
# (the "License"); you may not use this file except in compliance with
6+
# the License. You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
import unittest
16+
17+
from requests.auth import HTTPBasicAuth
18+
19+
from common_utils import gateway_base_url, knox_get, knox_post
20+
21+
22+
class TestKnoxAuthServicePreAuthAndPaths(unittest.TestCase):
23+
def setUp(self):
24+
self.base_url = gateway_base_url()
25+
self.preauth_url = self.base_url + "gateway/knoxldap/auth/api/v1/pre"
26+
self.extauthz_url = self.base_url + "gateway/knoxldap/auth/api/v1/extauthz"
27+
28+
def test_preauth_requires_auth(self):
29+
response = knox_get(self.preauth_url)
30+
self.assertEqual(response.status_code, 401)
31+
32+
def test_preauth_bad_credentials_unauthorized(self):
33+
response = knox_get(
34+
self.preauth_url,
35+
auth=HTTPBasicAuth("baduser", "badpass"),
36+
)
37+
self.assertEqual(response.status_code, 401)
38+
39+
def test_preauth_post_supported(self):
40+
response = knox_post(
41+
self.preauth_url,
42+
auth=HTTPBasicAuth("guest", "guest-password"),
43+
)
44+
self.assertEqual(response.status_code, 200)
45+
46+
actor_id_header = "x-knox-actor-username"
47+
self.assertIn(actor_id_header, response.headers)
48+
self.assertEqual(response.headers[actor_id_header], "guest")
49+
50+
def test_extauthz_additional_path_not_ignored_in_knoxldap(self):
51+
response = knox_get(
52+
self.extauthz_url + "/does-not-exist",
53+
auth=HTTPBasicAuth("guest", "guest-password"),
54+
)
55+
self.assertEqual(response.status_code, 404)
56+
57+
58+
if __name__ == "__main__":
59+
unittest.main()
60+

0 commit comments

Comments
 (0)