mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 19:56:53 +00:00
sys-svc-compose writes compose.ca.override.yml for exactly lookup('ca_injected', application_id), which since 1254e27c2d also answers true for a service that declares ca_client and only calls the self-signed endpoint. compose_file_args rebuilt that predicate instead of asking it, and its copy still demanded a domain, so for a domainless ca_client the file was written and never reached --compose-file. Swarm takes its file list from that lookup; compose finds the file on disk by itself, which is why only the swarm jobs of run 36064589578 failed while the compose job passed.
The broker's container in that run carries no SSL_CERT_FILE and no CA mount, while openwebui, same job and same node, carries seven CA env vars and four binds. Every outbound call to Keycloak then fails the handshake, which the CLI test reports as 503 and Open WebUI relays as HTTP 400.
Two corrections in ca_injected itself. Its templar.template() call was dead: as_str strips the TrustedAsTemplate tag and ansible-core 2.19+ refuses to render an untrusted string, so the line could not resolve what it advertised, and tls.py and cert.py never template TLS_MODE either. And the ca_client branch short-circuited on the global TLS_MODE, skipping resolve_mode's per-app server.tls.mode override; it now applies only where there is no domain and so no per-app answer to get.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
81 lines
3.2 KiB
Python
81 lines
3.2 KiB
Python
"""Lookup `ca_injected`: whether the self-signed root CA is mounted into an
|
|
application's containers.
|
|
|
|
{{ lookup('ca_injected', application_id) }}
|
|
|
|
The CA override that bind-mounts ``CA_TRUST.inject_cert_container`` is emitted
|
|
under three conditions, and anything that reads the mounted file must ask the
|
|
same three or it will point at a path that was never mounted. The conditions
|
|
lived as four hand-copied expressions - both `sys-svc-compose` handlers,
|
|
`sys-svc-compose-ca/vars/main.yml` and the msmtp template - which is one copy
|
|
per place that can drift.
|
|
|
|
Those three ask whether the application is *served* over the self-signed CA.
|
|
An application that only *calls* one has no domain of its own and would be
|
|
skipped, so a service may declare ``ca_client: true`` and be answered on the
|
|
deployment's TLS mode alone. Without it such a container verifies against an
|
|
empty trust store and fails every outbound call at the TLS handshake.
|
|
|
|
Returns a plain bool, so callers read as a predicate:
|
|
|
|
when: lookup('ca_injected', application_id)
|
|
{{ CA_TRUST.inject_cert_container if lookup('ca_injected', application_id) else ... }}
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from ansible.errors import AnsibleError
|
|
from ansible.module_utils.parsing.convert_bool import boolean as _to_bool
|
|
from ansible.plugins.loader import lookup_loader
|
|
from ansible.plugins.lookup import LookupBase
|
|
|
|
from plugins.filter.has.domain import has_domain
|
|
from utils.tls_common import AVAILABLE_FLAVORS, as_str, require
|
|
|
|
SELF_SIGNED_MODE = "self_signed"
|
|
|
|
|
|
class LookupModule(LookupBase):
|
|
def run(
|
|
self,
|
|
terms: list[Any] | None,
|
|
variables: dict[str, Any] | None = None,
|
|
**kwargs: Any,
|
|
) -> list[bool]:
|
|
if not terms or len(terms) != 1:
|
|
raise AnsibleError(
|
|
"ca_injected lookup requires exactly one term: the application id"
|
|
)
|
|
templar = getattr(self, "_templar", None)
|
|
raw = templar.template(terms[0]) if templar is not None else terms[0]
|
|
application_id = str(raw).strip()
|
|
if not application_id:
|
|
raise AnsibleError("ca_injected lookup: application id must be non-empty")
|
|
|
|
def _run(name: str, args: list[Any]) -> Any:
|
|
return lookup_loader.get(name, loader=self._loader, templar=templar).run(
|
|
args, variables=variables or {}
|
|
)[0]
|
|
|
|
if not has_domain(_run("domains", []), application_id):
|
|
services = _run("config", [application_id, "services", {}]) or {}
|
|
if not any(
|
|
_to_bool(service.get("ca_client", False))
|
|
for service in services.values()
|
|
if isinstance(service, dict)
|
|
):
|
|
return [False]
|
|
mode = as_str(require(variables or {}, "TLS_MODE", str))
|
|
if mode not in AVAILABLE_FLAVORS:
|
|
raise AnsibleError(
|
|
f"ca_injected: TLS_MODE must be one of "
|
|
f"{sorted(AVAILABLE_FLAVORS)}, got '{mode}'"
|
|
)
|
|
return [mode == SELF_SIGNED_MODE]
|
|
|
|
if not _to_bool(_run("tls", [application_id, "enabled"])):
|
|
return [False]
|
|
return [_run("tls", [application_id, "mode"]) == SELF_SIGNED_MODE]
|