Files
computer-playbook/tests/unit/python/plugins/lookup/test_ca_injected.py
T
kevinveenbirkenbachandClaude Opus 5 1254e27c2d fix(ca): give the CA to a service that only calls the self-signed endpoint
ca_injected asked three questions, the first being whether the application
has a domain. That decides whether it is SERVED over the self-signed CA. An
application that only CALLS one has no domain of its own, so the predicate
answered false, sys-svc-compose-ca skipped its stack, and the container ran
with an empty trust store.

svc-ai-agent-broker is such a caller: it authorizes every request against
Keycloak group paths over the public HTTPS base. Without the CA every
outbound call died in the TLS handshake, the broker answered 503 "access
check unavailable", Open WebUI turned that into HTTP 400, and the spec
failed on a model it had just listed. Its container was the only one in the
rescue artifact without SSL_CERT_FILE, while litellm and open-webui carried
it.

A service may now declare ca_client: true, and the predicate then answers on
the deployment's TLS mode alone, because the domain question is meaningless
for a client. The three served-side conditions are untouched, so no
application that already received the CA stops receiving it.

The alternative of pointing the broker at an internal Keycloak URL was
rejected: the role declares no keycloak service, so the URL would render
without a route.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-24 13:52:12 +02:00

146 lines
4.3 KiB
Python

"""Unit tests for the ca_injected lookup plugin.
Pins the predicate that decides whether the self-signed root CA is bind-mounted
into an application's containers. Anything reading the mounted file must agree
with the handler gate that creates the mount, so all three terms are pinned
here, including their short-circuit order.
"""
from __future__ import annotations
import importlib.util
import unittest
import unittest.mock as mock
from ansible.errors import AnsibleError
from . import PROJECT_ROOT
MODULE = "lookup_ca_injected"
def _load_lookup():
spec = importlib.util.spec_from_file_location(
MODULE, str(PROJECT_ROOT / "plugins/lookup/ca_injected.py")
)
mod = importlib.util.module_from_spec(spec)
assert spec and spec.loader
spec.loader.exec_module(mod)
return mod
class _DummyTemplar:
def template(self, value):
return value
class _StubLookup:
def __init__(self, result):
self._result = result
def run(self, args, variables=None):
return [self._result]
class TestCaInjectedLookup(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.mod = _load_lookup()
def _resolve(
self,
*,
domains,
enabled,
mode,
app="web-app-moodle",
services=None,
tls_mode="self_signed",
):
calls: list[tuple[str, list]] = []
def fake_get(name, **_kwargs):
def run(args, variables=None):
calls.append((name, list(args)))
if name == "config":
return [services or {}]
if name == "domains":
return [domains]
return [enabled if args[1] == "enabled" else mode]
return mock.Mock(run=run)
plugin = self.mod.LookupModule()
plugin._loader = None
plugin._templar = _DummyTemplar()
with mock.patch.object(self.mod.lookup_loader, "get", side_effect=fake_get):
result = plugin.run([app], variables={"TLS_MODE": tls_mode})
return result[0], calls
def test_a_self_signed_app_with_a_domain_gets_the_ca(self):
verdict, _ = self._resolve(
domains={"web-app-moodle": "moodle.example"},
enabled=True,
mode="self_signed",
)
self.assertIs(verdict, True)
def test_letsencrypt_does_not_get_the_ca(self):
verdict, _ = self._resolve(
domains={"web-app-moodle": "moodle.example"},
enabled=True,
mode="letsencrypt",
)
self.assertIs(verdict, False)
def test_tls_disabled_does_not_get_the_ca(self):
verdict, _ = self._resolve(
domains={"web-app-moodle": "moodle.example"},
enabled=False,
mode="self_signed",
)
self.assertIs(verdict, False)
def test_an_app_without_a_domain_never_asks_about_tls(self):
verdict, calls = self._resolve(domains={}, enabled=True, mode="self_signed")
self.assertIs(verdict, False)
self.assertEqual([name for name, _ in calls], ["config", "domains"])
def test_a_domainless_ca_client_gets_the_ca_on_the_deployment_mode(self):
verdict, calls = self._resolve(
domains={},
enabled=True,
mode="self_signed",
services={"agent-broker": {"ca_client": True}, "socket-proxy": {}},
)
self.assertIs(
verdict,
True,
"a container that only calls the self-signed endpoint has no domain "
"of its own, and without the CA every outbound call fails the "
"handshake",
)
self.assertEqual([name for name, _ in calls], ["config"])
def test_a_ca_client_gets_nothing_when_the_ca_is_not_self_signed(self):
verdict, _ = self._resolve(
domains={},
enabled=True,
mode="self_signed",
services={"agent-broker": {"ca_client": True}},
tls_mode="letsencrypt",
)
self.assertIs(verdict, False)
def test_the_term_is_required(self):
plugin = self.mod.LookupModule()
plugin._templar = _DummyTemplar()
with self.assertRaises(AnsibleError):
plugin.run([], variables={})
with self.assertRaises(AnsibleError):
plugin.run([" "], variables={})
if __name__ == "__main__":
unittest.main()