The indexer entrypoint is now only our CA-import block followed by exec of the image's own /entrypoint.sh, mounted at /infinito-entrypoint.sh with an explicit command. The certs entrypoint is our own script: it downloads the cert tool only from packages.wazuh.com and fails if that download fails, so there is no silent fallback to the dev bucket. The manager node name is passed in as WAZUH_MANAGER_NODE for both compose and swarm.
WAZUH_CERTS_TOOL_VERSION was a literal in vars/main.yml. It now reads the manager image version from meta/services.yml and keeps major.minor (4.14.6 gives 4.14), so it follows the Docker updater and needs no separate pin.
bcrypt_hash takes an optional salt_seed. Without a seed it keeps the random salt. With a seed the salt is derived from the seed and the secret, so the same password always gives the same hash. Wazuh passes a fixed seed for the indexer admin and the dashboard service hashes, so internal_users.yml no longer changes on every run and no longer triggers a restart.
Put roles/test-e2e-playwright back to origin/main: the uppercase TEST_E2E_PLAYWRIGHT_WAIT_RETRIES/_DELAY constants, with no lowercase defaults and no per-role override. Wazuh's own override is removed, since nothing reads it any more. The longer wait was added to fix 502s that were really the swarm proxy pointing at the wrong service name.
Tor: use the shared tor block (bond 0.5, enabled when svc-net-tor is in the node's groups, tor pinned on in both variants) so a node serves one network, onion on a Tor node and clearnet otherwise.
SPOT and sizing: WAZUH_INDEXER_URL replaces four hand-built copies of the indexer URL, and the indexer heap is half of mem_limit instead of a hardcoded 1g (unchanged for the current 2g limit).
Fixes: bcrypt_hash rejects secrets over 72 bytes; the Keycloak group-removal helper uses the same resolver as the add helper and checks every response (the realm is no longer URL-encoded, matching the add helper), and a failed cleanup fails an otherwise passing test; the swarm certs generator removes a stale container before each attempt; vm.max_map_count is set before the early handler flush; securityadmin failed_when now needs rc 0 and the success marker.
The regenerated role README also picks up generator drift (mailu to stalwart).
docker stack deploy ignores depends_on, so manager, indexer and dashboard were scheduled together with certs_generator and rejected with 'bind source path does not exist' for the cert files. stack_ready.sh treats a Rejected newest task as fatal, so the converge failed on its first poll whenever certs_generator had not finished first.
In swarm, the certs_generator service is no longer rendered. Instead the stack host pulls the generator image and runs it once with the same entrypoint, env and mounts, before the stack deploys. The run is checked against a completion marker (sha256 of certs.yml plus tool version) and all eight cert files. Compose mode renders the same output as before.
Existing swarm hosts: a host whose Wazuh stack is already running has no marker yet, so the next deploy stops with an explicit error rather than rotating the CA under the live services. Either run 'docker stack rm wazuh' and redeploy, or write the expected marker value given in the error to <instance>/certs.complete, then redeploy. Compose hosts are not affected.
Per Kevin's review: replaced hardcoded usernames (wazuh-wui, kibanaserver,
admin) across wazuh.yml.j2, compose.yml.j2, internal_users.yml.j2,
roles_mapping.yml.j2, config.yml.j2 and 00_core.yml with
lookup('users', ...) references, following the same pattern already
used by web-svc-coturn and web-app-semaphore. New meta/users.yml
declares web-app-wazuh-dashboard (kibanaserver) and web-app-wazuh-api
(wazuh-wui) as service accounts, and references the existing shared
admin key as a dependency (admin: {}), matching the same pattern used
by openbao, pgadmin, checkmk, semaphore and openldap.
Per CodeRabbit's security finding (CWE-269): scoped readonly_auditor's
index_patterns from '*' to 'wazuh-*', matching the role's own stated
'all Wazuh indices' description. The wildcard previously granted read
access to every index on the cluster, including other users' saved
objects and plugin state, beyond what the role needs or describes.
Wazuh carries no network-specific code any more: the onion front-proxy include, WAZUH_DASHBOARD_ONION_DOMAIN, the forked templates/proxy.conf.j2 vhost and the tor exclusive/primary overrides are gone. The role follows the Tor provider default like every other role with a Tor bond; serving one role on several networks returns generically in the multi-network change.
Without the forked vhost nothing forwards X-Forwarded-Host, so the dashboard OIDC redirect base is the static canonical url.base instead of trust_dynamic_headers.
Also: the dashboard port is read from one variable, the Playwright specs share wazuhLoginViaOidc and withBiberInGroup and assert worker-src blob:, PR-added explanatory comments are removed, and the root README lists the role.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Registers Wazuh's onion vhost with the front proxy and makes OIDC login
work correctly on either domain, following up on Kevin's request to
integrate this into the PR so CI can actually verify it.
- tasks/00_core.yml registers a second, guarded front-proxy include for
the onion domain (WAZUH_DASHBOARD_ONION_DOMAIN | length > 0), using
the same swarm-mode 'dashboard' compose-service-key fix already
applied to the clearnet include.
- The dashboard's OIDC redirect URL is now derived per request
(opensearch_security.openid.trust_dynamic_headers, driven by
X-Forwarded-Host) instead of a single static base_redirect_url that
could only ever match one of the two domains. Requires this role's
own templates/proxy.conf.j2 (forked from sys-svc-proxy's
basic.conf.j2) since the shared vhost template doesn't forward that
header.
- Restores WAZUH_DASHBOARD_ONION_DOMAIN in vars/main.yml, which had
been dropped during an earlier commit-splitting cleanup in this same
branch's history; removes the now-unused WAZUH_DASHBOARD_BASE_URL.
Trimmed on rebuild: the roles/sys-front-tls/ hunks are dropped; they
served only the dual-stack attempt.
Verification status: make lint-ansible / test-lint / test-unit-python
all pass. Not yet exercised against a live svc-net-tor deployment or
real CI onion-mode run - that's the actual verification this commit
unblocks, per Kevin's note that CI can't confirm the implementation
works while this sits uncommitted.
TEST_E2E_PLAYWRIGHT_WAIT_RETRIES/_DELAY being redefined in web-app-wazuh's
vars/main.yml violated test_uppercase_constants_unique.py - ALL-CAPS
top-level constants must be defined exactly once, project-wide,
regardless of Ansible's include_vars runtime precedence (which does make
the override work correctly at runtime, just not in a way this lint rule
permits).
Follows the existing test_e2e_playwright_run_timeout /
TEST_E2E_PLAYWRIGHT_RUN_TIMEOUT_EFFECTIVE precedent already in this role:
moved wait_retries/wait_delay to lowercase, overridable defaults in
test-e2e-playwright/defaults/main.yml, updated 02_run_one.yml to read the
lowercase names directly. Wazuh's vars/main.yml now sets the lowercase
override (120/5) instead of shadowing the ALL-CAPS constant.
test-lint's test_comments_valid.py requires a single-line doc comment
for vars/*.yml entries. The explanatory comment above
TEST_E2E_PLAYWRIGHT_WAIT_RETRIES/_DELAY was 10 lines - condensed to one,
keeping the essential facts.
Five Exception:-labeled comments in vars/main.yml restated trip-wires
already documented at their true origin elsewhere (meta/services.yml's
console-vs-dashboard naming, tasks/00_core.yml's DinD sysctl guard,
plugins/filter/bcrypt_hash.py's passlib writeup, docs/contributing/
design/iam/rbac.md's rbac_group_path rules). Per review feedback,
Exception: is reserved for the one true origin, not every reference to
it - condensed each to a single-line plain doc comment pointing back.
Separately: CI's shared test-e2e-playwright readiness probe defaults to
a ~150s budget for every app. Wazuh's own indexer healthcheck already
declares a 390s worst-case boot ceiling, and swarm mode has no
depends_on/service_healthy gating, so the dashboard races a
still-booting indexer with no shield. Raised
TEST_E2E_PLAYWRIGHT_WAIT_RETRIES/_DELAY to 120/5s (600s), the first
per-role use of this override.
Add single-node Wazuh stack (manager, indexer, dashboard) as a new
Ansible role, integrated with the platform's Keycloak realm via native
OIDC and secondary LDAP authorization, with three-tier RBAC.
- Pins wazuh/wazuh-{manager,indexer,dashboard}:4.14.6 and
wazuh-certs-generator:0.0.4 (one-shot cert bootstrap job)
- No external database: wazuh.indexer (OpenSearch) is self-contained
- Auth: native OIDC against Keycloak, working end-to-end after two
infra fixes (JVM cacerts trust for the indexer's outbound calls to
Keycloak, front-proxy vhost wiring)
- LDAP wired as a secondary, authentication-independent authorization
backend (group-based backend_roles enrichment only)
- RBAC: three roles (administrator, security-analyst, readonly-auditor)
via the platform's standard meta/rbac.yml + rbac_group_path pipeline
- Documents a genuine upstream Wazuh bug: wz-home as the default
landing route throws an uncaught client exception for any
non-all_access role; default route moved to OpenSearch Dashboards'
own /app/home
Addressed two full rounds of review feedback (Kevin, Alejandro):
WEB_PROTOCOL usage, service-account naming, comment cleanup, shared
Keycloak-admin-helper extraction, a real TOCTOU race in RBAC test
setup, a Mermaid auth-flow diagram, meta/services.yml placement for
the certs-generator, CA-trust-path SPOT usage, CSP tightening
(including a genuine worker-src: blob: requirement found via live
testing), and a loud (non-silent) dev-bucket fallback warning.
Rebased onto main after a 3-month gap; brought the role in line with
~30 platform-convention changes introduced in that window (swarm
compose-template rules, Tor/onion contract, Playwright timeout/goto
wrappers, file layout, task numbering, comment policy, and more).
Live-deploy verification (9 iterations) found and fixed 6 further
real bugs invisible to lint/unit tests alone:
- credentials.* / meta/schema.yml -> secrets.credentials.* /
meta/secrets.yml (another platform-drift casualty)
- notify: "compose up" -> "compose-up" (day-one handler-name typo)
- local.subnet collision with web-app-mailu's /24
- missing primary: false alongside tor.exclusive: false, which let
the onion domain shadow the clearnet vhost entirely
- a second front-proxy include for the onion domain, later reverted
(see below)
- shared sys-stk-front-proxy/sys-front-tls TLS-lookup keying bug
(app-id-keyed vs. domain-keyed), which silently applied TLS to
Wazuh's onion vhost
The last two are related: enabling genuine dual-stack (clearnet +
onion) reachability for tor.exclusive: false surfaced a real bug in
two shared platform roles. That fix was verified safe against all 103
apps currently in the repo, but is deliberately NOT included here -
shared-role changes deserve their own PR and review, not to ride in
on a single app's feature branch. Wazuh's second front-proxy include
was reverted accordingly; the resulting onion-reachability gap is
documented in the README as a known, platform-scoped limitation.
make test: 5508 passed, 3 failed (all three trace to untracked local
scratch files - Dockerfile.bak, scripts/lab-*.sh - not present in any
CI checkout).