Skip to content

feat(soc): author stacks/soc — Wazuh and Velociraptor for odin, ahead of the guest (#266, #267) - #434

Merged
Gerrrt merged 4 commits into
mainfrom
gerrrt/soc-stack-authored-266
Sep 10, 2026
Merged

Gerrrt merged 4 commits into
mainfrom
gerrrt/soc-stack-authored-266

Conversation

@Gerrrt

@Gerrrt Gerrrt commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Refs #266, #267, #101. Does not close either issue — the stack cannot be deployed until #414 builds the domain and odin exists, and an agentless Wazuh has nothing to say. This is the authoring half, the way stacks/lab (#264) landed before alexander (#262) and stacks/sensitive (#415) before trinity.

What is here

stacks/soc/, decided by ADR-0030 and honouring everything it fixed:

  • Wazuh indexer, manager, dashboard and Velociraptor in one compose stack for odin at 10.0.30.60, derived from wazuh-docker's single-node reference with every departure marked. 2 GiB heap. One shard, no replicas, 30 s refresh and a 30-day ISM delete policy on wazuh-alerts-*, as two JSON files the runbook applies — Wazuh ships no retention at all. vm.max_map_count is a sysctl.d step in the runbook, not compose. Ports published exactly per the ADR's table; 514/udp and 55000 are not.
  • Indexer health into the lab's Prometheus via Alloy's first-party prometheus.exporter.elasticsearch, in stacks/soc/alloy/opensearch.alloy — deliberately not beside the shared config, which the estate's Alloy mounts whole. alerts.json is never tailed into Loki. No Grafana OpenSearch datasource.
  • Six rules with firing and quiet promtool cases in stacks/lab/prometheus/, where the series arrive: indexer unreachable, red, yellow (which, with no replicas, is the alert that says the settings stopped applying), heap pressure, shard budget, and ingestion stalled — the Screenshot capture, and three faults it uncovered #62/ContainerHighMemory cannot fire — no service sets a memory limit #63 shape the crossing exists for.
  • Each tool keeps its own CA. The Wazuh mTLS material comes from wazuh-certs-generator behind a certs profile (so make up never runs it, and the image is pinned where check_image_pins.py can see it). Velociraptor's generated config, which holds its CA key, is gitignored under velociraptor/etc/.
  • Everything the ADR's consequence list names: a soc rule in .sops.yaml above the catch-all, secrets/soc.example.yaml (seven keys), a Dependabot entry grouping wazuh/*, seed values for the four new compose guards, an architecture.md row marked not built, and build-the-soc-guest.md in the shape of build-the-lab-guest.md.

Decided on the way, stated where it lives

  • Velociraptor's registry does publish versioned tags — the ADR made the container path conditional on this — so 0.77.2 is pinned by tag and digest and the native binary is not needed.
  • The Wazuh service names carry a dot (wazuh.indexer): the certs generator refuses a bare hostname as an invalid DNS name, and filebeat verifies the indexer's certificate in full mode, so SAN and service name must match. Measured, not assumed — wazuh/certs.yml says so.
  • The indexer's user database is rendered, not tracked. Two bcrypt hashes live in SOPS; render-config.sh writes internal_users.yml and authd.pass into .rendered/ the way it writes snmp.yaml, and because the file is in place before the first start, the demo passwords never exist on the segment. The indexer image runs as uid 1000 and mounts that 0600 file, so render refuses any other uid; the runbook says the first user Ubuntu creates is the one to deploy from.
  • init.vql is a copy of the image's, mounted over it, so Velociraptor's metrics listener binds every interface for alexander to scrape. The file's header carries the diff to run after each Dependabot bump.
  • The manager runs without docker-init: s6-overlay is its PID 1 and two supervisors is one too many. Every other service keeps init: true.
  • Memory limits from day one, as vendor minimums — the one place this departs from stacks/lab's refusal to guess, for the reason stacks/sensitive gave.

One deviation from the task as written

The brief asked for a row for odin in network.md's ImaginationLAN table. check_docs.py forbids that: a host marked Not built yet in architecture.md must be absent from network.md's table, and the marker is what makes the row self-clearing on build day. So odin gets a planned-guest note in that section, the way zion and the six domain machines do, and the runbook's §12 adds the row when the guest exists.

The lab's ports stay commented

ADR-0030 says the lab's Prometheus and Loki "become published for the first time" because odin's Alloy pushes. The blocks in stacks/lab/compose.yaml are still commented here: a receiver published with no client yet is exactly the ADR-0012 case, on the segment where it matters most. The comments now name the runbook step (§7) that uncomments them on the day, in the same change that brings odin up.

Verified

make validate passes with the three skips every non-deployment checkout records. That includes docker compose config on the seeded stack, alloy fmt --test on the exporter file, check_compose_health.py --probe exec'ing each healthcheck binary inside the four pinned images (/bin/sh in the three Wazuh images, busybox wget in Velociraptor's), promtool on all thirteen lab rules and both test files, check_sops_rules.py resolving secrets/soc.sops.yaml to its own rule, and check_docs.py on the new rows. The certs generator was run once into scratch to confirm its output names, modes and SANs, and the upstream image init scripts were read to confirm what each environment variable does.

Not verified, and stated in the README: that it runs. Nothing here has been deployed; every check is static.

Also fixes two stale sentences found on the way: README.md's "not yet deployed" against stacks/lab and observability.md's "built but not yet deployed".

🤖 Generated with Claude Code

… of the guest (#266, #267)

ADR-0030 decided where the security half of ADR-0007 runs and how: Wazuh
and Velociraptor in one stack on a second guest, odin, at 10.0.30.60. This
is that stack, authored and CI-validated before the guest exists, the way
stacks/lab landed before alexander and stacks/sensitive before trinity.
Neither issue closes on it: an agentless Wazuh has nothing to say, and the
agents are the domain's (#414).

What the ADR fixed and this honours: 2 GiB indexer heap; one shard, no
replicas, a 30 s refresh and a 30-day ISM delete policy on wazuh-alerts-*,
as two JSON files the runbook applies; vm.max_map_count as a sysctl.d step
on the guest; alerts.json never tailed into Loki; indexer health into the
lab's Prometheus through Alloy's first-party elasticsearch exporter, in
stacks/soc/alloy/ and not beside the shared config; six rules with firing
and quiet promtool cases where the series arrive, in stacks/lab; no Grafana
OpenSearch datasource; every port published or not per the ADR's table;
each tool keeps its own CA; agents by GPO. Nothing reaches 10.0.99.20.

Things decided on the way, each stated where it lives:

- Velociraptor's registry does publish versioned tags, so the container
  path the ADR made conditional stands; 0.77.2 is pinned by digest.
- The Wazuh service names carry a dot (wazuh.indexer) because the certs
  generator refuses a bare hostname and filebeat verifies in full mode.
- The indexer's user database is rendered from two bcrypt hashes in SOPS,
  in place before the first start, so the demo passwords never exist on
  the segment. render-config.sh writes it and authd.pass; the deploying
  user must be uid 1000, and render refuses otherwise.
- init.vql is a copy of the image's, mounted over it, so the metrics
  listener binds every interface for alexander to scrape.
- The manager runs without docker-init, because s6-overlay is its PID 1.
- Memory limits are vendor minimums, stated as bounds.

Follows the ADR's consequence list: a soc rule in .sops.yaml above the
catch-all, secrets/soc.example.yaml, a Dependabot entry grouping wazuh/*,
seed values for the four new guards, an architecture.md row marked not
built (so network.md gets a note, not a row — check_docs.py requires it),
and the lab's commented ports blocks now name the runbook step that
uncomments them.

Also fixes README.md's stale "not yet deployed" against stacks/lab and
observability.md's stale "built but not yet deployed".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ored-266

# Conflicts:
#	README.md
#	scripts/render-config.sh
#	scripts/seed-validation-env.sh
…ored-266

# Conflicts:
#	README.md
#	scripts/render-config.sh
#	scripts/seed-validation-env.sh
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant