Skip to content

Commit 20cd457

Browse files
committed
[ModelRegistry] ensure RunAsUser and RunAsGroup are not set explicitly
updates! 4813f2b
1 parent 8ab3be5 commit 20cd457

File tree

5 files changed

+174
-1
lines changed

5 files changed

+174
-1
lines changed

tests/model_registry/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class ModelRegistryEndpoints:
3131
"authProvider": "redhat-ods-applications-auth-provider",
3232
"gateway": {"grpc": {"tls": {}}, "rest": {"tls": {}}},
3333
}
34-
DB_RESOURCES_NAME: str = "model-registry-db"
34+
DB_RESOURCES_NAME: str = "db-model-registry"
3535
MR_DB_IMAGE_DIGEST: str = (
3636
"public.ecr.aws/docker/library/mysql@sha256:9de9d54fecee6253130e65154b930978b1fcc336bcc86dfd06e89b72a2588ebe"
3737
)

tests/model_registry/scc/__init__.py

Whitespace-only changes.
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
from __future__ import annotations
2+
import pytest
3+
from typing import Self
4+
from simple_logger.logger import get_logger
5+
from _pytest.fixtures import FixtureRequest
6+
7+
from ocp_resources.namespace import Namespace
8+
from ocp_resources.pod import Pod
9+
from ocp_resources.deployment import Deployment
10+
from ocp_resources.resource import NamespacedResource
11+
from tests.model_registry.scc.utils import get_uid_from_namespace, validate_pod_security_context
12+
from utilities.constants import DscComponents
13+
from tests.model_registry.constants import MR_NAMESPACE, MODEL_DICT, MR_INSTANCE_NAME
14+
15+
from kubernetes.dynamic import DynamicClient
16+
from ocp_utilities.infra import get_pods_by_name_prefix
17+
18+
KEYS_TO_VALIDATE = ["runAsGroup", "runAsUser"]
19+
LOGGER = get_logger(name=__name__)
20+
21+
22+
@pytest.fixture(scope="class")
23+
def model_registry_scc_namespace(model_registry_namespace: Namespace):
24+
mr_annotations = model_registry_namespace.instance.metadata.annotations
25+
return {
26+
"seLinuxOptions": mr_annotations.get("openshift.io/sa.scc.mcs"),
27+
"uid-range": mr_annotations.get("openshift.io/sa.scc.uid-range"),
28+
}
29+
30+
31+
@pytest.fixture(scope="class")
32+
def model_registry_resource(
33+
request: FixtureRequest, admin_client: DynamicClient, model_registry_namespace: Namespace
34+
) -> NamespacedResource:
35+
if request.param["kind"] == Deployment:
36+
return Deployment(name=MR_INSTANCE_NAME, namespace=model_registry_namespace.name, ensure_exists=True)
37+
elif request.param["kind"] == Pod:
38+
pods = get_pods_by_name_prefix(
39+
client=admin_client, pod_prefix=MR_INSTANCE_NAME, namespace=model_registry_namespace.name
40+
)
41+
if len(pods) != 1:
42+
pytest.fail(
43+
"Expected one model registry pod. Found: {[{pod.name: pod.status} for pod in pods] if pods else None}"
44+
)
45+
return pods[0]
46+
else:
47+
raise AssertionError(f"Invalid resource: {request.param['kind']}. Valid options: Deployment and Pod")
48+
49+
50+
@pytest.mark.parametrize(
51+
"model_registry_namespace, updated_dsc_component_state_scope_class, registered_model",
52+
[
53+
pytest.param(
54+
{"namespace_name": MR_NAMESPACE},
55+
{
56+
"component_patch": {
57+
DscComponents.MODELREGISTRY: {
58+
"managementState": DscComponents.ManagementState.MANAGED,
59+
"registriesNamespace": MR_NAMESPACE,
60+
},
61+
},
62+
},
63+
MODEL_DICT,
64+
),
65+
],
66+
indirect=True,
67+
)
68+
@pytest.mark.usefixtures("model_registry_namespace", "updated_dsc_component_state_scope_class", "registered_model")
69+
class TestModelRegistrySecurityContextValidation:
70+
@pytest.mark.parametrize(
71+
"model_registry_resource",
72+
[
73+
pytest.param({"kind": Deployment}),
74+
],
75+
indirect=["model_registry_resource"],
76+
)
77+
def test_model_registry_deployment_security_context_validation(
78+
self: Self,
79+
model_registry_resource: NamespacedResource,
80+
model_registry_namespace: Namespace,
81+
):
82+
"""
83+
Validate that model registry deployment does not set runAsUser/runAsGroup
84+
"""
85+
error = []
86+
for container in model_registry_resource.instance.spec.template.spec.containers:
87+
if not all([True for key in KEYS_TO_VALIDATE if not container.get(key)]):
88+
error.append({container.name: container.securityContext})
89+
90+
if error:
91+
pytest.fail(
92+
f"{model_registry_resource.name} {model_registry_resource.kind} containers expected to not "
93+
f"set {KEYS_TO_VALIDATE}, actual: {error}"
94+
)
95+
96+
@pytest.mark.parametrize(
97+
"model_registry_resource",
98+
[
99+
pytest.param({"kind": Pod}),
100+
],
101+
indirect=["model_registry_resource"],
102+
)
103+
def test_model_registry_pod_security_context_validation(
104+
self: Self,
105+
model_registry_resource: NamespacedResource,
106+
model_registry_scc_namespace: dict[str, str],
107+
):
108+
"""
109+
Validate that model registry pod gets runAsUser/runAsGroup from openshift and the values matches namespace
110+
annotations
111+
"""
112+
errors = []
113+
ns_uid = get_uid_from_namespace(namespace_scc=model_registry_scc_namespace)
114+
pod_spec = model_registry_resource.instance.spec
115+
errors = validate_pod_security_context(
116+
pod_security_context=pod_spec.securityContext,
117+
namespace_scc=model_registry_scc_namespace,
118+
model_registry_resource=model_registry_resource,
119+
ns_uid=ns_uid,
120+
)
121+
containers = model_registry_resource.instance.spec.containers
122+
for container in containers:
123+
expected_value = {
124+
"runAsUser": int(ns_uid) + 1 if "sidecar" in container.args else int(ns_uid),
125+
"runAsGroup": int(ns_uid) + 1 if "sidecar" in container.args else None,
126+
}
127+
128+
for key in KEYS_TO_VALIDATE:
129+
if container.securityContext.get(key) == expected_value[key]:
130+
LOGGER.info(
131+
f"For container: {container.name}, {key} validation: {expected_value[key]} completed "
132+
"successfully"
133+
)
134+
else:
135+
errors.append(
136+
f"For {container.name}, expected key {key} value: {expected_value[key]},"
137+
f" actual: {container.securityContext.get(key)}"
138+
)
139+
140+
if errors:
141+
pytest.fail(
142+
f"{model_registry_resource.name} {model_registry_resource.kind} pod security context validation failed"
143+
f" with error: {errors}"
144+
)

tests/model_registry/scc/utils.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from typing import Any
2+
3+
from ocp_resources.resource import NamespacedResource
4+
5+
6+
def get_uid_from_namespace(namespace_scc: dict[str, str]) -> str:
7+
return namespace_scc["uid-range"].split("/")[0]
8+
9+
10+
def validate_pod_security_context(
11+
pod_security_context: dict[str, Any],
12+
namespace_scc: dict[str, str],
13+
model_registry_resource: NamespacedResource,
14+
ns_uid: str,
15+
) -> list[str]:
16+
errors = []
17+
pod_selinux_option = pod_security_context.get("seLinuxOptions", {}).get("level")
18+
if pod_selinux_option != namespace_scc["seLinuxOptions"]:
19+
errors.append(
20+
f"selinux option from pod {model_registry_resource.name} {pod_selinux_option},"
21+
f" namespace: {namespace_scc['seLinuxOptions']}"
22+
)
23+
if pod_security_context.get("fsGroup") != int(ns_uid):
24+
errors.append(
25+
f"UID-range from pod {model_registry_resource.name} {pod_security_context.get('fsGroup')},"
26+
f" namespace: {ns_uid}"
27+
)
28+
return errors

tests/model_registry/test_model_registry_creation.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from __future__ import annotations
12
import pytest
23
from typing import Self
34
from simple_logger.logger import get_logger

0 commit comments

Comments
 (0)