Skip to content

Commit 2cf14d4

Browse files
authored
CLOUDP-356376 Allow Search to work with x509 cluster auth (#568)
# Summary With the switch to gRPC for mongot in #527 it's now possible for Search to be deployed against clusters that use x509 internal authentication. This removes the validation check for the internal cluster authentication mode and adds a new e2e test based on the existing `search_enterprise_tls` test that verifies existing functionality against a x509 cluster. ## Proof of Work New test. ## Checklist - [x] Have you linked a jira ticket and/or is the ticket in the title? - [x] Have you checked whether your jira ticket required DOCSP changes? - [x] Have you added changelog file? - use `skip-changelog` label if not needed - refer to [Changelog files and Release Notes](https://github.com/mongodb/mongodb-kubernetes/blob/master/CONTRIBUTING.md#changelog-files-and-release-notes) section in CONTRIBUTING.md for more details
1 parent fd25d7e commit 2cf14d4

File tree

6 files changed

+276
-6
lines changed

6 files changed

+276
-6
lines changed

.evergreen-tasks.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,3 +1307,8 @@ tasks:
13071307
tags: [ "patch-run" ]
13081308
commands:
13091309
- func: "e2e_test"
1310+
1311+
- name: e2e_search_enterprise_x509_cluster_auth
1312+
tags: [ "patch-run" ]
1313+
commands:
1314+
- func: "e2e_test"

.evergreen.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,7 @@ task_groups:
758758
# MongoDBSearch test group
759759
- e2e_search_enterprise_basic
760760
- e2e_search_enterprise_tls
761+
- e2e_search_enterprise_x509_cluster_auth
761762
<<: *teardown_group
762763

763764
# this task group contains just a one task, which is smoke testing whether the operator
@@ -1190,6 +1191,7 @@ task_groups:
11901191
<<: *setup_and_teardown_task
11911192
tasks:
11921193
- e2e_search_enterprise_tls
1194+
- e2e_search_enterprise_x509_cluster_auth
11931195
<<: *teardown_group
11941196

11951197
# Tests features only supported on OM70 and OM80, its only upgrade test as we test upgrading from 6 to 7 or 7 to 8
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
kind: feature
3+
date: 2025-11-03
4+
---
5+
6+
* **MongoDBSearch**: MongoDB deployments using X509 internal cluster authentication are now supported. Previously MongoDB Search required SCRAM authentication among members of a MongoDB replica set. Note: SCRAM client authentication is still required, this change merely relaxes the requirements on internal cluster authentication.
7+

controllers/searchcontroller/enterprise_search_source.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,5 @@ func (r EnterpriseResourceSearchSource) Validate() error {
8181
return xerrors.New("MongoDBSearch requires SCRAM authentication to be enabled")
8282
}
8383

84-
if r.Spec.Security.GetInternalClusterAuthenticationMode() == util.X509 {
85-
return xerrors.New("MongoDBSearch does not support X.509 internal cluster authentication")
86-
}
87-
8884
return nil
8985
}

controllers/searchcontroller/enterprise_search_source_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,7 @@ func TestEnterpriseResourceSearchSource_Validate(t *testing.T) {
223223
resourceType: mdbv1.ReplicaSet,
224224
authModes: []string{"SCRAM-SHA-256"},
225225
internalClusterAuth: "X509",
226-
expectError: true,
227-
expectedErrMsg: "MongoDBSearch does not support X.509 internal cluster authentication",
226+
expectError: false,
228227
},
229228
{
230229
name: "Valid internal cluster auth - empty",
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
import yaml
2+
from kubetester import create_or_update_secret, run_periodically, try_load
3+
from kubetester.certs import (
4+
create_agent_tls_certs,
5+
create_tls_certs,
6+
create_x509_mongodb_tls_certs,
7+
)
8+
from kubetester.kubetester import KubernetesTester
9+
from kubetester.kubetester import fixture as yaml_fixture
10+
from kubetester.mongodb import MongoDB
11+
from kubetester.mongodb_search import MongoDBSearch
12+
from kubetester.mongodb_user import MongoDBUser
13+
from kubetester.omtester import skip_if_cloud_manager
14+
from kubetester.phase import Phase
15+
from pytest import fixture, mark
16+
from tests import test_logger
17+
from tests.common.search import movies_search_helper
18+
from tests.common.search.search_tester import SearchTester
19+
from tests.conftest import get_default_operator, get_issuer_ca_filepath
20+
from tests.search.om_deployment import get_ops_manager
21+
22+
logger = test_logger.get_test_logger(__name__)
23+
24+
ADMIN_USER_NAME = "mdb-admin-user"
25+
ADMIN_USER_PASSWORD = f"{ADMIN_USER_NAME}-password"
26+
27+
MONGOT_USER_NAME = "search-sync-source"
28+
MONGOT_USER_PASSWORD = f"{MONGOT_USER_NAME}-password"
29+
30+
USER_NAME = "mdb-user"
31+
USER_PASSWORD = f"{USER_NAME}-password"
32+
33+
MDB_RESOURCE_NAME = "mdb-ent-tls"
34+
35+
# MongoDBSearch TLS configuration
36+
MDBS_TLS_SECRET_NAME = "mdbs-tls-secret"
37+
38+
39+
@fixture(scope="function")
40+
def mdb(namespace: str, issuer_ca_configmap: str) -> MongoDB:
41+
resource = MongoDB.from_yaml(
42+
yaml_fixture("enterprise-replicaset-sample-mflix.yaml"),
43+
name=MDB_RESOURCE_NAME,
44+
namespace=namespace,
45+
)
46+
47+
if try_load(resource):
48+
return resource
49+
50+
resource.configure(om=get_ops_manager(namespace), project_name=MDB_RESOURCE_NAME)
51+
resource.configure_custom_tls(issuer_ca_configmap, "certs")
52+
resource["spec"]["security"]["authentication"] = {
53+
"enabled": True,
54+
"modes": ["X509", "SCRAM"],
55+
"agents": {"mode": "X509"},
56+
"internalCluster": "X509",
57+
}
58+
59+
return resource
60+
61+
62+
@fixture(scope="function")
63+
def mdbs(namespace: str) -> MongoDBSearch:
64+
resource = MongoDBSearch.from_yaml(yaml_fixture("search-minimal.yaml"), namespace=namespace, name=MDB_RESOURCE_NAME)
65+
66+
if try_load(resource):
67+
return resource
68+
69+
# Add TLS configuration to MongoDBSearch
70+
if "spec" not in resource:
71+
resource["spec"] = {}
72+
73+
resource["spec"]["security"] = {"tls": {"certificateKeySecretRef": {"name": MDBS_TLS_SECRET_NAME}}}
74+
75+
return resource
76+
77+
78+
@fixture(scope="function")
79+
def admin_user(namespace: str) -> MongoDBUser:
80+
resource = MongoDBUser.from_yaml(
81+
yaml_fixture("mongodbuser-mdb-admin.yaml"), namespace=namespace, name=ADMIN_USER_NAME
82+
)
83+
84+
if try_load(resource):
85+
return resource
86+
87+
resource["spec"]["mongodbResourceRef"]["name"] = MDB_RESOURCE_NAME
88+
resource["spec"]["username"] = resource.name
89+
resource["spec"]["passwordSecretKeyRef"]["name"] = f"{resource.name}-password"
90+
91+
return resource
92+
93+
94+
@fixture(scope="function")
95+
def user(namespace: str) -> MongoDBUser:
96+
resource = MongoDBUser.from_yaml(yaml_fixture("mongodbuser-mdb-user.yaml"), namespace=namespace, name=USER_NAME)
97+
98+
if try_load(resource):
99+
return resource
100+
101+
resource["spec"]["mongodbResourceRef"]["name"] = MDB_RESOURCE_NAME
102+
resource["spec"]["username"] = resource.name
103+
resource["spec"]["passwordSecretKeyRef"]["name"] = f"{resource.name}-password"
104+
105+
return resource
106+
107+
108+
@fixture(scope="function")
109+
def mongot_user(namespace: str, mdbs: MongoDBSearch) -> MongoDBUser:
110+
resource = MongoDBUser.from_yaml(
111+
yaml_fixture("mongodbuser-search-sync-source-user.yaml"),
112+
namespace=namespace,
113+
name=f"{mdbs.name}-{MONGOT_USER_NAME}",
114+
)
115+
116+
if try_load(resource):
117+
return resource
118+
119+
resource["spec"]["mongodbResourceRef"]["name"] = MDB_RESOURCE_NAME
120+
resource["spec"]["username"] = MONGOT_USER_NAME
121+
resource["spec"]["passwordSecretKeyRef"]["name"] = f"{resource.name}-password"
122+
123+
return resource
124+
125+
126+
@mark.e2e_search_enterprise_x509_cluster_auth
127+
def test_install_operator(namespace: str, operator_installation_config: dict[str, str]):
128+
operator = get_default_operator(namespace, operator_installation_config=operator_installation_config)
129+
operator.assert_is_running()
130+
131+
132+
@mark.e2e_search_enterprise_x509_cluster_auth
133+
@skip_if_cloud_manager
134+
def test_create_ops_manager(namespace: str):
135+
ops_manager = get_ops_manager(namespace)
136+
ops_manager.update()
137+
ops_manager.om_status().assert_reaches_phase(Phase.Running, timeout=1200)
138+
ops_manager.appdb_status().assert_reaches_phase(Phase.Running, timeout=600)
139+
140+
141+
@mark.e2e_search_enterprise_x509_cluster_auth
142+
def test_install_tls_secrets_and_configmaps(namespace: str, mdb: MongoDB, mdbs: MongoDBSearch, issuer: str):
143+
create_agent_tls_certs(issuer, namespace, mdb.name, "certs")
144+
create_x509_mongodb_tls_certs(issuer, namespace, mdb.name, f"certs-{mdb.name}-clusterfile")
145+
create_x509_mongodb_tls_certs(issuer, namespace, mdb.name, f"certs-{mdb.name}-cert", mdb.get_members())
146+
147+
search_service_name = f"{mdbs.name}-search-svc"
148+
create_tls_certs(
149+
issuer,
150+
namespace,
151+
f"{mdbs.name}-search",
152+
replicas=1,
153+
service_name=search_service_name,
154+
additional_domains=[f"{search_service_name}.{namespace}.svc.cluster.local"],
155+
secret_name=MDBS_TLS_SECRET_NAME,
156+
)
157+
158+
159+
@mark.e2e_search_enterprise_x509_cluster_auth
160+
def test_create_database_resource(mdb: MongoDB):
161+
mdb.update()
162+
mdb.assert_reaches_phase(Phase.Running, timeout=300)
163+
164+
165+
@mark.e2e_search_enterprise_x509_cluster_auth
166+
def test_create_users(
167+
namespace: str, admin_user: MongoDBUser, user: MongoDBUser, mongot_user: MongoDBUser, mdb: MongoDB
168+
):
169+
create_or_update_secret(
170+
namespace, name=admin_user["spec"]["passwordSecretKeyRef"]["name"], data={"password": ADMIN_USER_PASSWORD}
171+
)
172+
admin_user.update()
173+
admin_user.assert_reaches_phase(Phase.Updated, timeout=300)
174+
175+
create_or_update_secret(
176+
namespace, name=user["spec"]["passwordSecretKeyRef"]["name"], data={"password": USER_PASSWORD}
177+
)
178+
user.update()
179+
user.assert_reaches_phase(Phase.Updated, timeout=300)
180+
181+
create_or_update_secret(
182+
namespace, name=mongot_user["spec"]["passwordSecretKeyRef"]["name"], data={"password": MONGOT_USER_PASSWORD}
183+
)
184+
mongot_user.update()
185+
mongot_user.assert_reaches_phase(Phase.Updated, timeout=300)
186+
187+
188+
@mark.e2e_search_enterprise_x509_cluster_auth
189+
def test_create_search_resource(mdbs: MongoDBSearch):
190+
mdbs.update()
191+
mdbs.assert_reaches_phase(Phase.Running, timeout=300)
192+
193+
194+
# After picking up MongoDBSearch CR, MongoDB reconciler will add mongod parameters to each process.
195+
# Due to how MongoDB reconciler works (blocking on waiting for agents and not changing the status to pending)
196+
# the phase won't be updated to Pending and we need to wait by checking agents' status directly in OM.
197+
@mark.e2e_search_enterprise_x509_cluster_auth
198+
def test_wait_for_agents_ready(mdb: MongoDB):
199+
mdb.get_om_tester().wait_agents_ready()
200+
mdb.assert_reaches_phase(Phase.Running, timeout=300)
201+
202+
203+
@mark.e2e_search_enterprise_x509_cluster_auth
204+
def test_wait_for_mongod_parameters(mdb: MongoDB):
205+
# After search CR is deployed, MongoDB controller will pick it up
206+
# and start adding search-related parameters to the automation config.
207+
def check_mongod_parameters():
208+
parameters_are_set = True
209+
pod_parameters = []
210+
for idx in range(mdb.get_members()):
211+
mongod_config = yaml.safe_load(
212+
KubernetesTester.run_command_in_pod_container(
213+
f"{mdb.name}-{idx}", mdb.namespace, ["cat", "/data/automation-mongod.conf"]
214+
)
215+
)
216+
set_parameter = mongod_config.get("setParameter", {})
217+
parameters_are_set = parameters_are_set and (
218+
"mongotHost" in set_parameter and "searchIndexManagementHostAndPort" in set_parameter
219+
)
220+
pod_parameters.append(f"pod {idx} setParameter: {set_parameter}")
221+
222+
return parameters_are_set, f'Not all pods have mongot parameters set:\n{"\n".join(pod_parameters)}'
223+
224+
run_periodically(check_mongod_parameters, timeout=600)
225+
226+
227+
@mark.e2e_search_enterprise_x509_cluster_auth
228+
def test_search_restore_sample_database(mdb: MongoDB):
229+
get_admin_sample_movies_helper(mdb).restore_sample_database()
230+
231+
232+
@mark.e2e_search_enterprise_x509_cluster_auth
233+
def test_search_create_search_index(mdb: MongoDB):
234+
get_user_sample_movies_helper(mdb).create_search_index()
235+
236+
237+
@mark.e2e_search_enterprise_x509_cluster_auth
238+
def test_search_assert_search_query(mdb: MongoDB):
239+
get_user_sample_movies_helper(mdb).assert_search_query(retry_timeout=60)
240+
241+
242+
def get_connection_string(mdb: MongoDB, user_name: str, user_password: str) -> str:
243+
return f"mongodb://{user_name}:{user_password}@{mdb.name}-0.{mdb.name}-svc.{mdb.namespace}.svc.cluster.local:27017/?replicaSet={mdb.name}"
244+
245+
246+
def get_admin_sample_movies_helper(mdb):
247+
return movies_search_helper.SampleMoviesSearchHelper(
248+
SearchTester(
249+
get_connection_string(mdb, ADMIN_USER_NAME, ADMIN_USER_PASSWORD),
250+
use_ssl=True,
251+
ca_path=get_issuer_ca_filepath(),
252+
)
253+
)
254+
255+
256+
def get_user_sample_movies_helper(mdb):
257+
return movies_search_helper.SampleMoviesSearchHelper(
258+
SearchTester(
259+
get_connection_string(mdb, USER_NAME, USER_PASSWORD), use_ssl=True, ca_path=get_issuer_ca_filepath()
260+
)
261+
)

0 commit comments

Comments
 (0)