blob: c329c51cf7fcade77c9316b08497ef238e4dfd3f [file]
############################################################################
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
############################################################################
"""Verify slurmdbd rejects clients that are not TLS wrapped.
Companion to test_166_1 and test_166_2, which cover the conmgr RPC
listeners. slurmdbd is fail-closed as well, but tls.shtml documents it as
reporting that differently: it negotiates TLS as soon as a connection is
accepted, so a client without TLS fails the negotiation and has its
connection closed, rather than being answered with ESLURM_TLS_REQUIRED.
That distinction is what these tests pin. Asserting only that the client
fails would pass even if slurmdbd had replied on an RPC listener, so the
reject leg also asserts the TLS-required error is absent.
slurmdbd takes its own TLSType from slurmdbd.conf, so it is required there
in addition to the cluster-wide setting the other modules need.
"""
import os
import pytest
import atf
# slurm_strerror(ESLURM_TLS_REQUIRED). What the RPC listeners answer and what
# slurmdbd, closing the connection instead, must not.
TLS_REQUIRED_MSG = "TLS missing but required for connection"
@pytest.fixture(scope="module", autouse=True)
def setup():
atf.require_tls()
if not atf.get_config_parameter(
"TLSType", default=None, live=False, source="slurmdbd", quiet=True
):
pytest.skip(
"Issue 51066: this test requires TLSType to be set in"
" slurmdbd.conf as well as slurm.conf",
allow_module_level=True,
)
atf.require_accounting()
atf.require_slurm_running()
@pytest.fixture(scope="module")
def non_tls_conf(module_setup):
"""Path to a slurm.conf that includes the real config but disables TLS.
Absolute because the tests consuming it run in their own directory. Only
the process pointed at this via SLURM_CONF speaks plaintext; slurmdbd
keeps the real configuration.
"""
real_conf = f"{atf.properties['slurm-config-dir']}/slurm.conf"
path = "non_tls_slurm.conf"
atf.run_command(
f"printf 'include {real_conf}\\nTLSType=tls/none\\n' > {path}",
fatal=True,
)
return os.path.abspath(path)
def test_slurmdbd_rejects_non_tls(non_tls_conf):
"""A non-TLS client cannot reach slurmdbd, and is not told why.
sacctmgr takes its TLSType from slurm.conf, so pointing it at the
non-TLS config makes it connect in plaintext. slurmdbd closes that
connection during negotiation, so the failure carries no
ESLURM_TLS_REQUIRED to explain itself.
"""
result = atf.run_command(
"sacctmgr -n show cluster",
env_vars=f"SLURM_CONF={non_tls_conf}",
xfail=True,
)
assert (
result["exit_code"] != 0
), f"Expected sacctmgr to fail without TLS, got:\n{result}"
output = result["stdout"] + result["stderr"]
assert TLS_REQUIRED_MSG not in output, (
"Expected slurmdbd to close the connection without an"
f" ESLURM_TLS_REQUIRED reply, got:\n{output}"
)
def test_slurmdbd_accepts_tls():
"""Control: the same query under the real (TLS) config succeeds."""
result = atf.run_command(
"sacctmgr -n show cluster",
fatal=True,
)
assert (
result["exit_code"] == 0
), f"Expected sacctmgr to succeed with TLS, got:\n{result}"