mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-09 12:17:13 +00:00
feat(web-app-wazuh): add Wazuh SIEM role with OIDC/LDAP RBAC
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).
This commit is contained in:
40 files changed
+3056
-2
No files matched your search
@@ -0,0 +1,53 @@
|
||||
import unittest
|
||||
|
||||
import bcrypt
|
||||
|
||||
from plugins.filter.bcrypt_hash import FilterModule
|
||||
|
||||
|
||||
class TestBcryptHash(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.f = FilterModule().filters()["bcrypt_hash"]
|
||||
|
||||
def test_hash_verifies_against_the_original_password(self):
|
||||
password = "correct horse battery staple"
|
||||
hashed = self.f(password)
|
||||
self.assertTrue(
|
||||
bcrypt.checkpw(password.encode("utf-8"), hashed.encode("utf-8"))
|
||||
)
|
||||
|
||||
def test_hash_does_not_verify_against_a_different_password(self):
|
||||
hashed = self.f("password-one")
|
||||
self.assertFalse(bcrypt.checkpw(b"password-two", hashed.encode("utf-8")))
|
||||
|
||||
def test_hash_is_randomized_salt_each_call(self):
|
||||
password = "same-password"
|
||||
self.assertNotEqual(self.f(password), self.f(password))
|
||||
|
||||
def test_hash_has_bcrypt_prefix(self):
|
||||
hashed = self.f("some-password")
|
||||
self.assertTrue(hashed.startswith(("$2b$", "$2a$")))
|
||||
|
||||
def test_64_character_password_hashes_successfully(self):
|
||||
# Exception: Ansible's password_hash('bcrypt') filter routes through
|
||||
# passlib, whose bcrypt self-test can misreport a well-under-72-byte
|
||||
# password as "too long" when passlib and the installed bcrypt
|
||||
# package's version detection drift apart. The raw bcrypt package
|
||||
# used here has no such self-test.
|
||||
password = "A" * 64
|
||||
hashed = self.f(password)
|
||||
self.assertTrue(
|
||||
bcrypt.checkpw(password.encode("utf-8"), hashed.encode("utf-8"))
|
||||
)
|
||||
|
||||
def test_none_raises(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.f(None)
|
||||
|
||||
def test_empty_string_raises(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.f("")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user